Complete v1 API Key

This commit is contained in:
Tuan Dang
2023-01-15 14:25:23 +07:00
parent 32928bf45c
commit 74b76eda7e
17 changed files with 250 additions and 183 deletions

View File

@@ -111,7 +111,7 @@ app.use('/api/v2/workspace', v2WorkspaceRouter); // TODO: turn into plural route
app.use('/api/v2/secret', v2SecretRouter); // stop supporting, TODO: revise
app.use('/api/v2/secrets', v2SecretsRouter);
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
app.use('/api/v2/api-key-data', v2APIKeyDataRouter);
app.use('/api/v2/api-key', v2APIKeyDataRouter);
// api docs
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))

View File

@@ -12,7 +12,7 @@ import { ADMIN, MEMBER } from '../../../variables';
router.get(
'/:secretId/secret-versions',
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireSecretAuth({
acceptedRoles: [ADMIN, MEMBER]
@@ -27,7 +27,7 @@ router.get(
router.post(
'/:secretId/secret-versions/rollback',
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireSecretAuth({
acceptedRoles: [ADMIN, MEMBER]

View File

@@ -12,7 +12,7 @@ import { workspaceController } from '../../controllers/v1';
router.get(
'/:workspaceId/secret-snapshots',
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
@@ -40,7 +40,7 @@ router.get(
router.post(
'/:workspaceId/secret-snapshots/rollback',
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
@@ -54,7 +54,7 @@ router.post(
router.get(
'/:workspaceId/logs',
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]

View File

@@ -16,49 +16,66 @@ import {
AccountNotFoundError,
ServiceTokenDataNotFoundError,
APIKeyDataNotFoundError,
UnauthorizedRequestError
UnauthorizedRequestError,
BadRequestError
} from '../utils/errors';
// TODO 1: check if API key works
// TODO 2: optimize middleware
/**
* Validate that auth token value [authTokenValue] falls under one of
* accepted auth modes [acceptedAuthModes].
*
* @param {Object} obj
* @param {String} obj.authTokenValue - auth token value (e.g. JWT or service token value)
* @param {String[]} obj.acceptedAuthModes - accepted auth modes (e.g. jwt, serviceToken)
* @returns {String} authMode - auth mode
* @param {Object} obj.headers - HTTP request headers object
*/
const validateAuthMode = ({
authTokenValue,
headers,
acceptedAuthModes
}: {
authTokenValue: string;
acceptedAuthModes: string[];
headers: { [key: string]: string | string[] | undefined },
acceptedAuthModes: string[]
}) => {
let authMode;
try {
switch (authTokenValue.split('.', 1)[0]) {
case 'st':
authMode = 'serviceToken';
break;
case 'ak':
authMode = 'apiKey';
break;
default:
authMode = 'jwt';
break;
}
if (!acceptedAuthModes.includes(authMode))
throw UnauthorizedRequestError({ message: 'Failed to authenticated auth mode' });
// TODO: refactor middleware
const apiKey = headers['x-api-key'];
const authHeader = headers['authorization'];
} catch (err) {
throw UnauthorizedRequestError({ message: 'Failed to authenticated auth mode' });
let authTokenType, authTokenValue;
if (apiKey === undefined && authHeader === undefined) {
// case: no auth or X-API-KEY header present
throw BadRequestError({ message: 'Missing Authorization or X-API-KEY in request header.' });
}
return authMode;
if (typeof apiKey === 'string') {
// case: treat request authentication type as via X-API-KEY (i.e. API Key)
authTokenType = 'apiKey';
authTokenValue = apiKey;
}
if (typeof authHeader === 'string') {
// case: treat request authentication type as via Authorization header (i.e. either JWT or service token)
const [tokenType, tokenValue] = <[string, string]>authHeader.split(' ', 2) ?? [null, null]
if (tokenType === null)
throw BadRequestError({ message: `Missing Authorization Header in the request header.` });
if (tokenType.toLowerCase() !== 'bearer')
throw BadRequestError({ message: `The provided authentication type '${tokenType}' is not supported.` });
if (tokenValue === null)
throw BadRequestError({ message: 'Missing Authorization Body in the request header.' });
switch (tokenValue.split('.', 1)[0]) {
case 'st':
authTokenType = 'serviceToken';
break;
default:
authTokenType = 'jwt';
}
authTokenValue = tokenValue;
}
if (!authTokenType || !authTokenValue) throw BadRequestError({ message: 'Missing valid Authorization or X-API-KEY in request header.' });
if (!acceptedAuthModes.includes(authTokenType)) throw BadRequestError({ message: 'The provided authentication type is not supported.' });
return ({
authTokenType,
authTokenValue
});
}
/**

View File

@@ -7,7 +7,6 @@ import {
getAuthSTDPayload,
getAuthAPIKeyPayload
} from '../helpers/auth';
import { BadRequestError } from '../utils/errors';
declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -31,37 +30,28 @@ const requireAuth = ({
acceptedAuthModes: string[];
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
if (AUTH_TOKEN_TYPE === null)
return next(BadRequestError({ message: `Missing Authorization Header in the request header.` }))
if (AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer')
return next(BadRequestError({ message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.` }))
if (AUTH_TOKEN_VALUE === null)
return next(BadRequestError({ message: 'Missing Authorization Body in the request header' }))
// validate auth token against
const authMode = validateAuthMode({
authTokenValue: AUTH_TOKEN_VALUE,
// validate auth token against accepted auth modes [acceptedAuthModes]
// and return token type [authTokenType] and value [authTokenValue]
const { authTokenType, authTokenValue } = validateAuthMode({
headers: req.headers,
acceptedAuthModes
});
if (!acceptedAuthModes.includes(authMode)) throw new Error('Failed to validate auth mode');
// attach auth payloads
switch (authMode) {
switch (authTokenType) {
case 'serviceToken':
req.serviceTokenData = await getAuthSTDPayload({
authTokenValue: AUTH_TOKEN_VALUE
authTokenValue
});
break;
case 'apiKey':
req.user = await getAuthAPIKeyPayload({
authTokenValue: AUTH_TOKEN_VALUE
authTokenValue
});
break;
default:
req.user = await getAuthUserPayload({
authTokenValue: AUTH_TOKEN_VALUE
authTokenValue
});
break;
}

View File

@@ -61,7 +61,7 @@ router.post(
}),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -76,7 +76,7 @@ router.get(
query('environment').exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'serviceToken']
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -115,7 +115,7 @@ router.patch(
}),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireSecretsAuth({
acceptedRoles: [ADMIN, MEMBER]
@@ -143,7 +143,7 @@ router.delete(
.isEmpty(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireSecretsAuth({
acceptedRoles: [ADMIN, MEMBER]

View File

@@ -8,7 +8,7 @@ import { usersController } from '../../controllers/v2';
router.get(
'/me',
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
usersController.getMe
);

View File

@@ -45,7 +45,7 @@ router.get(
router.get(
'/:workspaceId/encrypted-key',
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
@@ -75,7 +75,7 @@ router.get( // new - TODO: rewire dashboard to this route
param('workspaceId').exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -89,7 +89,7 @@ router.delete( // TODO - rewire dashboard to this route
param('membershipId').exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
@@ -107,7 +107,7 @@ router.patch( // TODO - rewire dashboard to this route
body('role').exists().isString().trim().isIn([ADMIN, MEMBER]),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt']
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],