Complete v1 support for API key auth mode

This commit is contained in:
Tuan Dang
2023-01-04 20:27:16 +07:00
parent 8c7c41e091
commit c7fb9209c4
8 changed files with 119 additions and 16 deletions
+60 -9
View File
@@ -3,22 +3,25 @@ import * as Sentry from '@sentry/node';
import bcrypt from 'bcrypt';
import {
User,
ServiceTokenData
ServiceTokenData,
APIKeyData
} from '../models';
import {
JWT_AUTH_LIFETIME,
JWT_AUTH_SECRET,
JWT_REFRESH_LIFETIME,
JWT_REFRESH_SECRET,
SALT_ROUNDS
JWT_REFRESH_SECRET
} from '../config';
import {
AccountNotFoundError,
ServiceTokenDataNotFoundError,
UnauthorizedRequestError,
BadRequestError
APIKeyDataNotFoundError,
UnauthorizedRequestError
} 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].
@@ -40,6 +43,9 @@ const validateAuthMode = ({
case 'st':
authMode = 'serviceToken';
break;
case 'ak':
authMode = 'apiKey';
break;
default:
authMode = 'jwt';
break;
@@ -106,9 +112,11 @@ const getAuthSTDPayload = async ({
// TODO: optimize double query
serviceTokenData = await ServiceTokenData
.findById(TOKEN_IDENTIFIER, 'secretHash expiresAt');
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt');
if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
if (!serviceTokenData) {
throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
// case: service token expired
await ServiceTokenData.findByIdAndDelete(serviceTokenData._id);
throw UnauthorizedRequestError({
@@ -116,8 +124,6 @@ const getAuthSTDPayload = async ({
});
}
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceTokenData.secretHash);
if (!isMatch) throw UnauthorizedRequestError({
message: 'Failed to authenticate service token'
@@ -136,6 +142,50 @@ const getAuthSTDPayload = async ({
return serviceTokenData;
}
/**
* Return API key data payload corresponding to API key [authTokenValue]
* @param {Object} obj
* @param {String} obj.authTokenValue - API key value
* @returns {APIKeyData} apiKeyData - API key data
*/
const getAuthAPIKeyPayload = async ({
authTokenValue
}: {
authTokenValue: string;
}) => {
let user;
try {
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3);
const apiKeyData = await APIKeyData
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt')
.populate('user', '+publicKey');
if (!apiKeyData) {
throw APIKeyDataNotFoundError({ message: 'Failed to find API key data' });
} else if (apiKeyData?.expiresAt && new Date(apiKeyData.expiresAt) < new Date()) {
// case: API key expired
await APIKeyData.findByIdAndDelete(apiKeyData._id);
throw UnauthorizedRequestError({
message: 'Failed to authenticate expired API key'
});
}
const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKeyData.secretHash);
if (!isMatch) throw UnauthorizedRequestError({
message: 'Failed to authenticate API key'
});
user = apiKeyData.user;
} catch (err) {
throw UnauthorizedRequestError({
message: 'Failed to authenticate API key'
});
}
return user;
}
/**
* Return newly issued (JWT) auth and refresh tokens to user with id [userId]
* @param {Object} obj
@@ -229,6 +279,7 @@ export {
validateAuthMode,
getAuthUserPayload,
getAuthSTDPayload,
getAuthAPIKeyPayload,
createToken,
issueTokens,
clearTokens