Skip to content

Commit 81324cb

Browse files
SK-2056: add unions for credentials (#214)
1 parent 180e63e commit 81324cb

File tree

7 files changed

+233
-121
lines changed

7 files changed

+233
-121
lines changed

src/error/codes/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const SKYFLOW_ERROR_CODE = {
6161
MISSING_PRIVATE_KEY: { http_code: 400, message: errorMessages.MISSING_PRIVATE_KEY },
6262

6363
INVALID_ROLES_KEY_TYPE: { http_code: 400, message: errorMessages.INVALID_ROLES_KEY_TYPE },
64+
INVALID_CONTEXT: { http_code: 400, message: errorMessages.INVALID_CONTEXT },
6465
EMPTY_ROLES: { http_code: 400, message: errorMessages.EMPTY_ROLES },
6566

6667
INVALID_JSON_FORMAT: { http_code: 400, message: errorMessages.INVALID_JSON_FORMAT },

src/error/messages/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const errorMessages = {
6161
MISSING_TOKEN_URI: `${errorPrefix} Validation error. Unable to read token URI in credentials. Verify your token URI.`,
6262

6363
INVALID_ROLES_KEY_TYPE: `${errorPrefix} Validation error. Invalid roles. Specify roles as an array.`,
64+
INVALID_CONTEXT: `${errorPrefix} Validation error. Invalid context. Specify context as a string.`,
6465
EMPTY_ROLES: `${errorPrefix} Validation error. Invalid roles. Specify at least one role.`,
6566

6667
INVALID_JSON_FORMAT: `${errorPrefix} Validation error. Credentials is not in valid JSON format. Verify the credentials.`,

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import UpdateRequest from './vault/model/request/update';
1212
import FileUploadRequest from './vault/model/request/file-upload';
1313
import QueryRequest from './vault/model/request/query';
1414
import Credentials from './vault/config/credentials';
15+
import { TokenCredentials, ApiKeyCredentials, PathCredentials, StringCredentials } from './vault/config/credentials';
1516
import TokenizeRequest from './vault/model/request/tokenize';
1617
import TokenizeResponse from './vault/model/response/tokenize';
1718
import { BearerTokenOptions, generateBearerToken, generateBearerTokenFromCreds, generateSignedDataTokens, generateSignedDataTokensFromCreds, GenerateTokenOptions, SignedDataTokensOptions } from './service-account';
@@ -64,6 +65,10 @@ export {
6465
generateSignedDataTokensFromCreds,
6566
isExpired,
6667
Credentials,
68+
ApiKeyCredentials,
69+
TokenCredentials,
70+
PathCredentials,
71+
StringCredentials,
6772
RedactionType,
6873
OrderByEnum,
6974
TokenMode,

src/utils/index.ts

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import SkyflowError from "../error";
22
import * as sdkDetails from "../../package.json";
33
import { generateBearerToken, generateBearerTokenFromCreds } from "../service-account";
4-
import Credentials from "../vault/config/credentials";
4+
import Credentials, { ApiKeyCredentials, PathCredentials, StringCredentials, TokenCredentials } from "../vault/config/credentials";
55
import dotenv from "dotenv";
66
import logs from "./logs";
77
import os from 'os';
@@ -265,21 +265,23 @@ export function removeSDKVersion(message: string): string {
265265
}
266266

267267
// Helper function to generate token based on credentials
268-
export async function getToken(credentials?: Credentials, logLevel?: LogLevel) {
269-
if (credentials?.credentialsString) {
268+
export async function getToken(credentials: Credentials, logLevel?: LogLevel): Promise<{ accessToken: string }> {
269+
if ('credentialsString' in credentials) {
270+
const stringCred = credentials as StringCredentials;
270271
printLog(logs.infoLogs.USING_CREDENTIALS_STRING, MessageType.LOG, logLevel);
271-
return generateBearerTokenFromCreds(credentials.credentialsString, {
272-
roleIDs: credentials.roles,
273-
ctx: credentials.context,
272+
return generateBearerTokenFromCreds(stringCred.credentialsString, {
273+
roleIDs: stringCred.roles,
274+
ctx: stringCred.context,
274275
logLevel,
275276
});
276277
}
277278

278-
if (credentials?.path) {
279+
if ('path' in credentials) {
280+
const pathCred = credentials as PathCredentials;
279281
printLog(logs.infoLogs.USING_PATH, MessageType.LOG, logLevel);
280-
return generateBearerToken(credentials.path, {
281-
roleIDs: credentials.roles,
282-
ctx: credentials.context,
282+
return generateBearerToken(pathCred.path, {
283+
roleIDs: pathCred.roles,
284+
ctx: pathCred.context,
283285
logLevel,
284286
});
285287
}
@@ -293,42 +295,45 @@ export async function getBearerToken(credentials?: Credentials, logLevel?: LogLe
293295
if (!credentials && process.env.SKYFLOW_CREDENTIALS === undefined) {
294296
throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_CREDENTIALS);
295297
}
298+
296299
// If credentials are missing but environment variable exists, use it
297300
if (!credentials && process.env.SKYFLOW_CREDENTIALS) {
298301
printLog(logs.infoLogs.USING_SKYFLOW_CREDENTIALS_ENV, MessageType.LOG, logLevel);
299302
credentials = {
300303
credentialsString: process.env.SKYFLOW_CREDENTIALS
301-
}
304+
} as StringCredentials;
302305
}
303306

304-
// If token already exists, resolve immediately
305-
if (credentials?.apiKey && credentials.apiKey.trim().length > 0) {
306-
if(isValidAPIKey(credentials?.apiKey)){
307+
// Handle ApiKeyCredentials
308+
if ('apiKey' in credentials!) {
309+
const apiKeyCred = credentials as ApiKeyCredentials;
310+
if (apiKeyCred.apiKey.trim().length > 0 && isValidAPIKey(apiKeyCred.apiKey)) {
307311
printLog(logs.infoLogs.USING_API_KEY, MessageType.LOG, logLevel);
308-
return { type: AuthType.API_KEY, key: credentials.apiKey };
312+
return { type: AuthType.API_KEY, key: apiKeyCred.apiKey };
309313
}
310314
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_API_KEY);
311315
}
312316

313-
// If token already exists, resolve immediately
314-
if (credentials?.token) {
317+
// Handle TokenCredentials
318+
if ('token' in credentials!) {
319+
const tokenCred = credentials as TokenCredentials;
315320
printLog(logs.infoLogs.USING_BEARER_TOKEN, MessageType.LOG, logLevel);
316-
return { type: AuthType.TOKEN, key: validateToken(credentials.token) };
321+
return { type: AuthType.TOKEN, key: validateToken(tokenCred.token) };
317322
}
318323

319324
printLog(logs.infoLogs.BEARER_TOKEN_LISTENER, MessageType.LOG, logLevel);
320325

321326
// Generate token based on provided credentials
322-
const token = await getToken(credentials, logLevel);
327+
const token = await getToken(credentials!, logLevel);
323328

324329
printLog(logs.infoLogs.BEARER_TOKEN_RESOLVED, MessageType.LOG, logLevel);
325330

326331
return { type: AuthType.TOKEN, key: token.accessToken };
327332

328333
} catch (err) {
329-
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CREDENTIALS); // rethrow any errors that occur
334+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CREDENTIALS);
330335
}
331-
};
336+
}
332337

333338
export function getBaseUrl(url: string): string {
334339
try {

src/utils/validations/index.ts

Lines changed: 106 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { V1Byot } from "../../ _generated_/rest/api";
33
import SkyflowError from "../../error";
44
import SKYFLOW_ERROR_CODE from "../../error/codes";
55
import ConnectionConfig from "../../vault/config/connection";
6-
import Credentials from "../../vault/config/credentials";
6+
import Credentials, { ApiKeyCredentials, PathCredentials, StringCredentials, TokenCredentials } from "../../vault/config/credentials";
77
import VaultConfig from "../../vault/config/vault";
88
import DetokenizeOptions from "../../vault/model/options/detokenize";
99
import GetOptions from "../../vault/model/options/get";
@@ -130,42 +130,74 @@ export const validateSkyflowConfig = (config: SkyflowConfig, logLevel: LogLevel
130130

131131

132132
export const validateCredentialsWithId = (credentials: Credentials, type: string, typeId: string, id: string, logLevel: LogLevel = LogLevel.ERROR) => {
133-
// validates types for ctx roles
134-
const { token, path, credentialsString, apiKey } = credentials;
133+
if (!credentials) {
134+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CREDENTIALS_WITH_ID, [type, typeId, id]);
135+
}
136+
137+
const isTokenCred = 'token' in credentials;
138+
const isPathCred = 'path' in credentials;
139+
const isStringCred = 'credentialsString' in credentials;
140+
const isApiKeyCred = 'apiKey' in credentials;
135141

136-
// Count how many of the fields are defined
137-
const definedFields = [token, path, credentialsString, apiKey].filter(Boolean).length;
142+
// Check if exactly one credential type is provided
143+
const definedTypes = [isTokenCred, isPathCred, isStringCred, isApiKeyCred].filter(Boolean).length;
138144

139-
// If none are present
140-
if (definedFields === 0) {
145+
if (definedTypes === 0) {
141146
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CREDENTIALS_WITH_ID, [type, typeId, id]);
142147
}
143148

144-
// If more than one is present
145-
if (definedFields > 1) {
149+
if (definedTypes > 1) {
146150
throw new SkyflowError(SKYFLOW_ERROR_CODE.MULTIPLE_CREDENTIALS_PASSED_WITH_ID, [type, typeId, id]);
147151
}
148152

149-
if (credentials?.token && (typeof credentials?.token !== 'string' || isExpired(credentials?.token))) {
150-
printLog(logs.errorLogs.EMPTY_TOKEN_VALUE, MessageType.ERROR, logLevel);
151-
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BEARER_TOKEN_WITH_ID, [type, typeId, id]);
153+
// Validate TokenCredentials
154+
if (isTokenCred) {
155+
const tokenCred = credentials as TokenCredentials;
156+
if (typeof tokenCred.token !== 'string' || isExpired(tokenCred.token)) {
157+
printLog(logs.errorLogs.EMPTY_TOKEN_VALUE, MessageType.ERROR, logLevel);
158+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BEARER_TOKEN_WITH_ID, [type, typeId, id]);
159+
}
152160
}
153161

154-
if (credentials?.credentialsString && (typeof credentials?.credentialsString !== 'string' || !isValidCredentialsString(credentials?.credentialsString))) {
155-
printLog(logs.errorLogs.EMPTY_CREDENTIALS_STRING, MessageType.ERROR, logLevel);
156-
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_PARSED_CREDENTIALS_STRING_WITH_ID, [type, typeId, id]);
162+
// Validate PathCredentials
163+
if (isPathCred) {
164+
console.log("PathCredentials");
165+
const pathCred = credentials as PathCredentials;
166+
if (typeof pathCred.path !== 'string' || !isValidPath(pathCred.path)) {
167+
printLog(logs.errorLogs.EMPTY_CREDENTIALS_PATH, MessageType.ERROR, logLevel);
168+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_PATH_WITH_ID, [type, typeId, id]);
169+
}
170+
if (pathCred.roles !== undefined && !Array.isArray(pathCred.roles)) {
171+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ROLES_KEY_TYPE, [type, typeId, id]);
172+
}
173+
if (pathCred.context !== undefined && typeof pathCred.context !== 'string') {
174+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONTEXT, [type, typeId, id]);
175+
}
157176
}
158177

159-
if (credentials?.apiKey && (typeof credentials?.apiKey !== 'string' || !isValidAPIKey(credentials?.apiKey))) {
160-
printLog(logs.errorLogs.INVALID_API_KEY, MessageType.ERROR, logLevel);
161-
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_API_KEY_WITH_ID, [type, typeId, id]);
178+
// Validate StringCredentials
179+
if (isStringCred) {
180+
const stringCred = credentials as StringCredentials;
181+
if (typeof stringCred.credentialsString !== 'string' || !isValidCredentialsString(stringCred.credentialsString)) {
182+
printLog(logs.errorLogs.EMPTY_CREDENTIALS_STRING, MessageType.ERROR, logLevel);
183+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_PARSED_CREDENTIALS_STRING_WITH_ID, [type, typeId, id]);
184+
}
185+
if (stringCred.roles !== undefined && !Array.isArray(stringCred.roles)) {
186+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ROLES_KEY_TYPE, [type, typeId, id]);
187+
}
188+
if (stringCred.context !== undefined && typeof stringCred.context !== 'string') {
189+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONTEXT, [type, typeId, id]);
190+
}
162191
}
163192

164-
if (credentials?.path && (typeof credentials?.path !== 'string' || !isValidPath(credentials?.path))) {
165-
printLog(logs.errorLogs.EMPTY_CREDENTIALS_PATH, MessageType.ERROR, logLevel);
166-
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_PATH_WITH_ID, [type, typeId, id]);
193+
// Validate ApiKeyCredentials
194+
if (isApiKeyCred) {
195+
const apiKeyCred = credentials as ApiKeyCredentials;
196+
if (typeof apiKeyCred.apiKey !== 'string' || !isValidAPIKey(apiKeyCred.apiKey)) {
197+
printLog(logs.errorLogs.INVALID_API_KEY, MessageType.ERROR, logLevel);
198+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_API_KEY_WITH_ID, [type, typeId, id]);
199+
}
167200
}
168-
169201
};
170202

171203
export const validateVaultConfig = (vaultConfig: VaultConfig, logLevel: LogLevel = LogLevel.ERROR) => {
@@ -225,41 +257,73 @@ export const validateUpdateVaultConfig = (vaultConfig: VaultConfig, logLevel: Lo
225257
};
226258

227259
export const validateSkyflowCredentials = (credentials: Credentials, logLevel: LogLevel = LogLevel.ERROR) => {
228-
const { token, path, credentialsString, apiKey } = credentials;
260+
if (!credentials) {
261+
throw new SkyflowError(SKYFLOW_ERROR_CODE.CREDENTIALS_WITH_NO_VALID_KEY);
262+
}
263+
264+
const isTokenCred = 'token' in credentials;
265+
const isPathCred = 'path' in credentials;
266+
const isStringCred = 'credentialsString' in credentials;
267+
const isApiKeyCred = 'apiKey' in credentials;
229268

230-
// Count how many of the fields are defined
231-
const definedFields = [token, path, credentialsString, apiKey].filter(Boolean).length;
269+
// Check if exactly one credential type is provided
270+
const definedTypes = [isTokenCred, isPathCred, isStringCred, isApiKeyCred].filter(Boolean).length;
232271

233-
// If none are present
234-
if (definedFields === 0) {
272+
if (definedTypes === 0) {
235273
throw new SkyflowError(SKYFLOW_ERROR_CODE.CREDENTIALS_WITH_NO_VALID_KEY);
236274
}
237275

238-
// If more than one is present
239-
if (definedFields > 1) {
276+
if (definedTypes > 1) {
240277
throw new SkyflowError(SKYFLOW_ERROR_CODE.MULTIPLE_CREDENTIALS_PASSED);
241278
}
242279

243-
if (credentials?.token && (typeof credentials?.token !== 'string' || isExpired(credentials?.token))) {
244-
printLog(logs.errorLogs.EMPTY_TOKEN_VALUE, MessageType.ERROR, logLevel);
245-
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BEARER_TOKEN);
280+
// Validate TokenCredentials
281+
if (isTokenCred) {
282+
const tokenCred = credentials as TokenCredentials;
283+
if (typeof tokenCred.token !== 'string' || isExpired(tokenCred.token)) {
284+
printLog(logs.errorLogs.EMPTY_TOKEN_VALUE, MessageType.ERROR, logLevel);
285+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BEARER_TOKEN);
286+
}
246287
}
247288

248-
if (credentials?.credentialsString && (typeof credentials?.credentialsString !== 'string' || !isValidCredentialsString(credentials?.credentialsString))) {
249-
printLog(logs.errorLogs.EMPTY_CREDENTIALS_STRING, MessageType.ERROR, logLevel);
250-
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_PARSED_CREDENTIALS_STRING);
289+
// Validate PathCredentials
290+
if (isPathCred) {
291+
const pathCred = credentials as PathCredentials;
292+
if (typeof pathCred.path !== 'string' || !isValidPath(pathCred.path)) {
293+
printLog(logs.errorLogs.EMPTY_CREDENTIALS_PATH, MessageType.ERROR, logLevel);
294+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_PATH);
295+
}
296+
if (pathCred.roles !== undefined && !Array.isArray(pathCred.roles)) {
297+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ROLES_KEY_TYPE);
298+
}
299+
if (pathCred.context !== undefined && typeof pathCred.context !== 'string') {
300+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONTEXT);
301+
}
251302
}
252303

253-
if (credentials?.apiKey && (typeof credentials?.apiKey !== 'string' || !isValidAPIKey(credentials?.apiKey))) {
254-
printLog(logs.errorLogs.INVALID_API_KEY, MessageType.ERROR, logLevel);
255-
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_API_KEY);
304+
// Validate StringCredentials
305+
if (isStringCred) {
306+
const stringCred = credentials as StringCredentials;
307+
if (typeof stringCred.credentialsString !== 'string' || !isValidCredentialsString(stringCred.credentialsString)) {
308+
printLog(logs.errorLogs.EMPTY_CREDENTIALS_STRING, MessageType.ERROR, logLevel);
309+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_PARSED_CREDENTIALS_STRING);
310+
}
311+
if (stringCred.roles !== undefined && !Array.isArray(stringCred.roles)) {
312+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ROLES_KEY_TYPE);
313+
}
314+
if (stringCred.context !== undefined && typeof stringCred.context !== 'string') {
315+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONTEXT);
316+
}
256317
}
257318

258-
if (credentials?.path && (typeof credentials?.path !== 'string' || !isValidPath(credentials?.path))) {
259-
printLog(logs.errorLogs.EMPTY_CREDENTIALS_PATH, MessageType.ERROR, logLevel);
260-
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_PATH);
319+
// Validate ApiKeyCredentials
320+
if (isApiKeyCred) {
321+
const apiKeyCred = credentials as ApiKeyCredentials;
322+
if (typeof apiKeyCred.apiKey !== 'string' || !isValidAPIKey(apiKeyCred.apiKey)) {
323+
printLog(logs.errorLogs.INVALID_API_KEY, MessageType.ERROR, logLevel);
324+
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_API_KEY);
325+
}
261326
}
262-
263327
};
264328

265329
export const validateConnectionConfig = (connectionConfig: ConnectionConfig, logLevel: LogLevel = LogLevel.ERROR) => {
Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
1+
export type Credentials =
2+
| TokenCredentials
3+
| PathCredentials
4+
| StringCredentials
5+
| ApiKeyCredentials;
16

2-
interface Credentials {
3-
token?: string;
4-
path?: string;
5-
credentialsString?: string;
6-
apiKey?: string;
7-
roles?: Array<string>;
8-
context?: string;
7+
export interface TokenCredentials {
8+
token: string;
9+
}
10+
11+
export interface PathCredentials {
12+
path: string;
13+
roles?: Array<string>;
14+
context?: string;
15+
}
16+
17+
export interface StringCredentials {
18+
credentialsString: string;
19+
roles?: Array<string>;
20+
context?: string;
21+
}
22+
23+
export interface ApiKeyCredentials {
24+
apiKey: string;
925
}
1026

1127
export default Credentials;

0 commit comments

Comments
 (0)