Continue developing service token v2

This commit is contained in:
Tuan Dang
2023-01-01 07:10:47 +07:00
156 changed files with 4451 additions and 1470 deletions
+53 -26
View File
@@ -1,6 +1,9 @@
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import { User } from '../models';
import { User, ServiceTokenData } from '../models';
import {
attachAuthPayload
} from '../helpers/auth';
import { JWT_AUTH_SECRET } from '../config';
import { AccountNotFoundError, BadRequestError, UnauthorizedRequestError } from '../utils/errors';
@@ -11,34 +14,58 @@ declare module 'jsonwebtoken' {
}
/**
* Validate if JWT (auth) token on request is valid (e.g. not expired),
* if there is an associated user, and if that user is fully setup.
* @param req - express request object
* @param res - express response object
* @param next - express next function
* Validate if token on request is valid (e.g. not expired) for various auth modes:
* - If token is a JWT token, then check if there is an associated user
* and if user is fully setup.
* - If token is a service token (st), then check if there is associated
* service token data.
* @param {Object} obj
* @param {String[]} obj.acceptedAuthModes - accepted modes of authentication (jwt/st)
* @returns
*/
const requireAuth = async (req: Request, res: Response, next: NextFunction) => {
// JWT authentication middleware
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'}))
const requireAuth = ({
acceptedAuthModes = ['jwt']
}: {
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 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');
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, JWT_AUTH_SECRET)
);
// attach auth request payload
const payload = await attachAuthPayload({
authTokenValue: AUTH_TOKEN_VALUE
});
switch (authMode) {
case 'st':
req.serviceTokenData = payload;
break;
default:
req.user = payload;
break;
}
const user = await User.findOne({
_id: decodedToken.userId
}).select('+publicKey');
if (!user) return next(AccountNotFoundError({message: 'Failed to locate User account'}))
if (!user?.publicKey)
return next(UnauthorizedRequestError({message: 'Unable to authenticate due to partially set up account'}))
req.user = user;
return next();
};
return next();
}
}
export default requireAuth;
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from 'express';
import { ServiceToken, ServiceTokenData } from '../models';
import { validateMembership } from '../helpers/membership';
import { AccountNotFoundError } from '../utils/errors';
import { AccountNotFoundError, UnauthorizedRequestError } from '../utils/errors';
type req = 'params' | 'body' | 'query';
@@ -15,21 +15,25 @@ const requireServiceTokenDataAuth = ({
location?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const { serviceTokenDataId } = req[location];
const serviceTokenData = await ServiceTokenData
.findById(req[location].serviceTokenDataId)
.select('+encryptedKey +iv +tag');
if (!serviceTokenData) {
return next(AccountNotFoundError({message: 'Failed to locate service token data'}));
}
await validateMembership({
userId: req.user._id.toString(),
workspaceId: serviceTokenData.workspace.toString(),
acceptedRoles,
acceptedStatuses
});
if (req.user) {
// case: jwt auth
await validateMembership({
userId: req.user._id.toString(),
workspaceId: serviceTokenData.workspace.toString(),
acceptedRoles,
acceptedStatuses
});
}
req.serviceTokenData = serviceTokenData;
+22 -11
View File
@@ -8,8 +8,8 @@ type req = 'params' | 'body' | 'query';
* Validate if user on request is a member with proper roles for workspace
* on request params.
* @param {Object} obj
* @param {String[]} obj.acceptedRoles - accepted workspace roles
* @param {String[]} obj.acceptedStatuses - accepted workspace statuses
* @param {String[]} obj.acceptedRoles - accepted workspace roles for JWT auth
* @param {String[]} obj.acceptedStatuses - accepted workspace statuses for JWT auth
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
*/
const requireWorkspaceAuth = ({
@@ -22,17 +22,28 @@ const requireWorkspaceAuth = ({
location?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
// workspace authorization middleware
try {
const membership = await validateMembership({
userId: req.user._id.toString(),
workspaceId: req[location].workspaceId,
acceptedRoles,
acceptedStatuses
});
const { workspaceId } = req[location];
req.membership = membership;
if (req.user) {
// case: jwt auth
const membership = await validateMembership({
userId: req.user._id.toString(),
workspaceId,
acceptedRoles,
acceptedStatuses
});
req.membership = membership;
}
if (
req.serviceTokenData
&& req.serviceTokenData.workspace !== workspaceId
&& req.serviceTokenData.environment !== req.body.environment
)
// case: st auth
next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
return next();
} catch (err) {