mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
Refactor auth middleware to accept multiple auth modes
This commit is contained in:
@ -48,7 +48,8 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||
|
||||
serviceTokenData = await new ServiceTokenData({
|
||||
// create service token data
|
||||
serviceTokenData = new ServiceTokenData({
|
||||
name,
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
@ -59,7 +60,12 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
encryptedKey,
|
||||
iv,
|
||||
tag
|
||||
}).save();
|
||||
})
|
||||
|
||||
await serviceTokenData.save();
|
||||
|
||||
// return service token data without sensitive data
|
||||
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
|
||||
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
|
@ -139,7 +139,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
||||
environment
|
||||
});
|
||||
|
||||
if (channel !== 'cli') { // TODO: fix frontend to get rid of this reformat bs
|
||||
if (channel !== 'cli') {
|
||||
secrets = reformatPullSecrets({ secrets });
|
||||
}
|
||||
|
||||
|
@ -12,56 +12,112 @@ import {
|
||||
JWT_REFRESH_SECRET,
|
||||
SALT_ROUNDS
|
||||
} from '../config';
|
||||
import {
|
||||
AccountNotFoundError,
|
||||
ServiceTokenDataNotFoundError,
|
||||
UnauthorizedRequestError
|
||||
} from '../utils/errors';
|
||||
|
||||
/**
|
||||
* Attach auth payload
|
||||
* Validate that auth token value [authTokenValue] falls under one of
|
||||
* accepted auth modes [acceptedAuthModes].
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.authTokenValue
|
||||
* @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
|
||||
*/
|
||||
const attachAuthPayload = async ({
|
||||
const validateAuthMode = ({
|
||||
authTokenValue,
|
||||
acceptedAuthModes
|
||||
}: {
|
||||
authTokenValue: string;
|
||||
acceptedAuthModes: string[];
|
||||
}) => {
|
||||
let authMode;
|
||||
try {
|
||||
switch (authTokenValue.split('.', 1)[0]) {
|
||||
case 'st':
|
||||
authMode = 'serviceToken';
|
||||
break;
|
||||
default:
|
||||
authMode = 'jwt';
|
||||
break;
|
||||
}
|
||||
|
||||
if (!acceptedAuthModes.includes(authMode))
|
||||
throw UnauthorizedRequestError({ message: 'Failed to authenticated auth mode' });
|
||||
|
||||
} catch (err) {
|
||||
throw UnauthorizedRequestError({ message: 'Failed to authenticated auth mode' });
|
||||
}
|
||||
|
||||
return authMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return user payload corresponding to JWT token [authTokenValue]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.authTokenValue - JWT token value
|
||||
* @returns {User} user - user corresponding to JWT token
|
||||
*/
|
||||
const getAuthUserPayload = async ({
|
||||
authTokenValue
|
||||
}: {
|
||||
authTokenValue: string;
|
||||
}) => {
|
||||
let serviceTokenHash, decodedToken; // intermediate variables
|
||||
let serviceTokenData, user; // payloads
|
||||
let user;
|
||||
try {
|
||||
switch (authTokenValue.split('.', 1)[0]) {
|
||||
case 'st':
|
||||
// case: service token auth mode
|
||||
serviceTokenHash = await bcrypt.hash(authTokenValue, SALT_ROUNDS);
|
||||
serviceTokenData = await ServiceTokenData
|
||||
.findOne({
|
||||
serviceTokenHash
|
||||
})
|
||||
.select('+encryptedKey +iv +tag');
|
||||
|
||||
if (!serviceTokenData) {
|
||||
throw new Error('Account not found error');
|
||||
}
|
||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||
jwt.verify(authTokenValue, JWT_AUTH_SECRET)
|
||||
);
|
||||
|
||||
return serviceTokenData;
|
||||
default:
|
||||
// case: JWT token auth mode
|
||||
decodedToken = <jwt.UserIDJwtPayload>(
|
||||
jwt.verify(authTokenValue, JWT_AUTH_SECRET)
|
||||
);
|
||||
|
||||
user = await User.findOne({
|
||||
_id: decodedToken.userId
|
||||
}).select('+publicKey');
|
||||
user = await User.findOne({
|
||||
_id: decodedToken.userId
|
||||
}).select('+publicKey');
|
||||
|
||||
if (!user)
|
||||
throw new Error('Account not found error');
|
||||
if (!user) throw AccountNotFoundError({ message: 'Failed to find User' });
|
||||
|
||||
if (!user?.publicKey)
|
||||
throw new Error('Unable to authenticate due to partially set up account');
|
||||
if (!user?.publicKey) throw UnauthorizedRequestError({ message: 'Failed to authenticate User with partially set up account' });
|
||||
|
||||
return user;
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error('Failed to attach auth payload');
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed to authenticate JWT token'
|
||||
});
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return service token data payload corresponding to service token [authTokenValue]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.authTokenValue - service token value
|
||||
* @returns {ServiceTokenData} serviceTokenData - service token data
|
||||
*/
|
||||
const getAuthSTDPayload = async ({
|
||||
authTokenValue
|
||||
}: {
|
||||
authTokenValue: string;
|
||||
}) => {
|
||||
let serviceTokenData;
|
||||
try {
|
||||
const serviceTokenHash = await bcrypt.hash(authTokenValue, SALT_ROUNDS);
|
||||
|
||||
serviceTokenData = await ServiceTokenData
|
||||
.findOne({
|
||||
serviceTokenHash
|
||||
})
|
||||
.select('+encryptedKey +iv +tag');
|
||||
|
||||
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
|
||||
|
||||
} catch (err) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed to authenticate service token'
|
||||
});
|
||||
}
|
||||
|
||||
return serviceTokenData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,7 +210,9 @@ const createToken = ({
|
||||
};
|
||||
|
||||
export {
|
||||
attachAuthPayload,
|
||||
validateAuthMode,
|
||||
getAuthUserPayload,
|
||||
getAuthSTDPayload,
|
||||
createToken,
|
||||
issueTokens,
|
||||
clearTokens
|
||||
|
@ -2,7 +2,9 @@ import jwt from 'jsonwebtoken';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { User, ServiceTokenData } from '../models';
|
||||
import {
|
||||
attachAuthPayload
|
||||
validateAuthMode,
|
||||
getAuthUserPayload,
|
||||
getAuthSTDPayload
|
||||
} from '../helpers/auth';
|
||||
import { JWT_AUTH_SECRET } from '../config';
|
||||
import { AccountNotFoundError, BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||
@ -37,30 +39,25 @@ const requireAuth = ({
|
||||
if(AUTH_TOKEN_VALUE === null)
|
||||
return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
|
||||
|
||||
// validate auth mode
|
||||
let authMode;
|
||||
switch (AUTH_TOKEN_VALUE.split('.', 1)[0]) {
|
||||
case 'st':
|
||||
authMode = 'st';
|
||||
break;
|
||||
default:
|
||||
authMode = 'jwt';
|
||||
break;
|
||||
}
|
||||
|
||||
if (!acceptedAuthModes.includes(authMode)) throw new Error('Failed to validate auth mode');
|
||||
|
||||
// attach auth request payload
|
||||
const payload = await attachAuthPayload({
|
||||
authTokenValue: AUTH_TOKEN_VALUE
|
||||
// validate auth token against
|
||||
const authMode = validateAuthMode({
|
||||
authTokenValue: AUTH_TOKEN_VALUE,
|
||||
acceptedAuthModes
|
||||
});
|
||||
|
||||
if (!acceptedAuthModes.includes(authMode)) throw new Error('Failed to validate auth mode');
|
||||
|
||||
// attach auth payloads
|
||||
switch (authMode) {
|
||||
case 'st':
|
||||
req.serviceTokenData = payload;
|
||||
case 'serviceToken':
|
||||
req.serviceTokenData = await getAuthSTDPayload({
|
||||
authTokenValue: AUTH_TOKEN_VALUE
|
||||
});
|
||||
break;
|
||||
default:
|
||||
req.user = payload;
|
||||
req.user = await getAuthUserPayload({
|
||||
authTokenValue: AUTH_TOKEN_VALUE
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -45,19 +45,19 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
||||
type: String,
|
||||
unique: true,
|
||||
required: true,
|
||||
select: true
|
||||
select: false
|
||||
},
|
||||
encryptedKey: {
|
||||
type: String,
|
||||
select: true
|
||||
select: false
|
||||
},
|
||||
iv: {
|
||||
type: String,
|
||||
select: true
|
||||
select: false
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
select: true
|
||||
select: false
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -18,7 +18,7 @@ import { serviceTokenDataController } from '../../controllers/v1';
|
||||
router.get(
|
||||
'/',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['st']
|
||||
acceptedAuthModes: ['serviceToken']
|
||||
}),
|
||||
param('serviceTokenDataId').exists().trim(),
|
||||
validateRequest,
|
||||
|
@ -11,7 +11,7 @@ import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||
import { membershipController } from '../../controllers/v1';
|
||||
import { workspaceController } from '../../controllers/v2';
|
||||
|
||||
router.post( // unfinished
|
||||
router.post(
|
||||
'/:workspaceId/secrets',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
@ -29,10 +29,10 @@ router.post( // unfinished
|
||||
workspaceController.pushWorkspaceSecrets
|
||||
);
|
||||
|
||||
router.get( // unfinished, check that it works with st
|
||||
router.get(
|
||||
'/:workspaceId/secrets',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'st']
|
||||
acceptedAuthModes: ['jwt', 'serviceToken']
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
|
@ -113,4 +113,14 @@ export const AccountNotFoundError = (error?: Partial<RequestErrorContext>) => ne
|
||||
stack: error?.stack
|
||||
})
|
||||
|
||||
//* ----->[SERVICE TOKEN DATA ERRORS]<-----
|
||||
export const ServiceTokenDataNotFoundError = (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