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