mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
Complete v1 support for API key auth mode
This commit is contained in:
@ -38,6 +38,7 @@ import {
|
||||
secret as v2SecretRouter,
|
||||
workspace as v2WorkspaceRouter,
|
||||
serviceTokenData as v2ServiceTokenDataRouter,
|
||||
apiKeyData as v2APIKeyDataRouter,
|
||||
} from './routes/v2';
|
||||
|
||||
import { getLogger } from './utils/logger';
|
||||
@ -94,6 +95,7 @@ app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
||||
app.use('/api/v2/workspace', v2WorkspaceRouter);
|
||||
app.use('/api/v2/secret', v2SecretRouter);
|
||||
app.use('/api/v2/service-token-data', v2ServiceTokenDataRouter);
|
||||
app.use('/api/v2/api-key-data', v2APIKeyDataRouter);
|
||||
|
||||
//* Handle unrouted requests and respond with proper error message as well as status code
|
||||
app.use((req, res, next)=>{
|
||||
|
@ -10,11 +10,36 @@ import {
|
||||
} from '../../config';
|
||||
|
||||
/**
|
||||
* Create new API key for user with id [req.user._id]
|
||||
* Return API key data for user with id [req.user_id]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getAPIKeyData = async (req: Request, res: Response) => {
|
||||
let apiKeyData;
|
||||
try {
|
||||
apiKeyData = await APIKeyData.find({
|
||||
user: req.user._id
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: 'Failed to get API key data'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
apiKeyData
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new API key data for user with id [req.user._id]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const createAPIKey = async (req: Request, res: Response) => {
|
||||
export const createAPIKeyData = async (req: Request, res: Response) => {
|
||||
let apiKey, apiKeyData;
|
||||
try {
|
||||
const { name, expiresIn } = req.body;
|
||||
@ -30,7 +55,7 @@ export const createAPIKey = async (req: Request, res: Response) => {
|
||||
expiresAt,
|
||||
user: req.user._id,
|
||||
secretHash
|
||||
});
|
||||
}).save();
|
||||
|
||||
// return api key data without sensitive data
|
||||
apiKeyData = await APIKeyData.findById(apiKeyData._id);
|
||||
@ -40,10 +65,11 @@ export const createAPIKey = async (req: Request, res: Response) => {
|
||||
apiKey = `ak.${apiKeyData._id.toString()}.${secret}`;
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: 'Failed to create service token data'
|
||||
message: 'Failed to API key data'
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -4,7 +4,8 @@ import { User, ServiceTokenData } from '../models';
|
||||
import {
|
||||
validateAuthMode,
|
||||
getAuthUserPayload,
|
||||
getAuthSTDPayload
|
||||
getAuthSTDPayload,
|
||||
getAuthAPIKeyPayload
|
||||
} from '../helpers/auth';
|
||||
import { BadRequestError } from '../utils/errors';
|
||||
|
||||
@ -53,6 +54,11 @@ const requireAuth = ({
|
||||
authTokenValue: AUTH_TOKEN_VALUE
|
||||
});
|
||||
break;
|
||||
case 'apiKey':
|
||||
req.user = await getAuthAPIKeyPayload({
|
||||
authTokenValue: AUTH_TOKEN_VALUE
|
||||
});
|
||||
break;
|
||||
default:
|
||||
req.user = await getAuthUserPayload({
|
||||
authTokenValue: AUTH_TOKEN_VALUE
|
||||
|
@ -7,6 +7,14 @@ import {
|
||||
import { body } from 'express-validator';
|
||||
import { apiKeyDataController } from '../../controllers/v2';
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
}),
|
||||
apiKeyDataController.getAPIKeyData
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
requireAuth({
|
||||
@ -15,7 +23,7 @@ router.post(
|
||||
body('name').exists().trim(),
|
||||
body('expiresIn'), // measured in ms
|
||||
validateRequest,
|
||||
apiKeyDataController.createAPIKey
|
||||
apiKeyDataController.createAPIKeyData
|
||||
);
|
||||
|
||||
export default router;
|
@ -71,5 +71,4 @@ router.get(
|
||||
workspaceController.getWorkspaceServiceTokenData
|
||||
);
|
||||
|
||||
|
||||
export default router;
|
||||
|
1
backend/src/types/express/index.d.ts
vendored
1
backend/src/types/express/index.d.ts
vendored
@ -16,6 +16,7 @@ declare global {
|
||||
serviceToken: any;
|
||||
accessToken: any;
|
||||
serviceTokenData: any;
|
||||
apiKeyData: any;
|
||||
query?: any;
|
||||
}
|
||||
}
|
||||
|
@ -143,4 +143,14 @@ export const ServiceTokenDataNotFoundError = (error?: Partial<RequestErrorContex
|
||||
stack: error?.stack
|
||||
})
|
||||
|
||||
//* ----->[API KEY DATA ERRORS]<-----
|
||||
export const APIKeyDataNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||
statusCode: error?.statusCode ?? 404,
|
||||
type: error?.type ?? 'service_token_data_not_found_error',
|
||||
message: error?.message ?? 'The requested service token data was not found',
|
||||
context: error?.context,
|
||||
stack: error?.stack
|
||||
})
|
||||
|
||||
//* ----->[MISC ERRORS]<-----
|
||||
|
Reference in New Issue
Block a user