diff --git a/backend/src/helpers/serviceTokenData.ts b/backend/src/helpers/serviceTokenData.ts index 8616ba237..9a8bb1288 100644 --- a/backend/src/helpers/serviceTokenData.ts +++ b/backend/src/helpers/serviceTokenData.ts @@ -1,9 +1,94 @@ import { Types } from 'mongoose'; import { ISecret, - IServiceTokenData + IServiceTokenData, + ServiceTokenData, + IUser, + User, + IServiceAccount, + ServiceAccount, } from '../models'; -import { UnauthorizedRequestError } from '../utils/errors'; +import { + UnauthorizedRequestError, + ServiceTokenDataNotFoundError +} from '../utils/errors'; +import { + AUTH_MODE_JWT, + AUTH_MODE_SERVICE_ACCOUNT, + AUTH_MODE_SERVICE_TOKEN, + AUTH_MODE_API_KEY +} from '../variables'; +import { validateUserClientForWorkspace } from '../helpers/user'; +import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount'; + +/** + * Validate authenticated clients for service token with id [serviceTokenId] based + * on any known permissions. + * @param {Object} obj + * @param {Object} obj.authData - authenticated client details + * @param {Types.ObjectId} obj.serviceTokenData - id of service token to validate against + * @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles + */ +const validateClientForServiceTokenData = async ({ + authData, + serviceTokenDataId, + acceptedRoles +}: { + authData: { + authMode: string; + authPayload: IUser | IServiceAccount | IServiceTokenData; + }; + serviceTokenDataId: Types.ObjectId; + acceptedRoles: Array<'admin' | 'member'>; +}) => { + const serviceTokenData = await ServiceTokenData + .findById(serviceTokenDataId) + .select('+encryptedKey +iv +tag') + .populate<{ user: IUser }>('user'); + + if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ + message: 'Failed to find service token data' + }); + + if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { + await validateUserClientForWorkspace({ + user: authData.authPayload, + workspaceId: serviceTokenData.workspace, + acceptedRoles + }); + + return serviceTokenData; + } + + if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { + await validateServiceAccountClientForWorkspace({ + serviceAccount: authData.authPayload, + workspaceId: serviceTokenData.workspace + }); + + return serviceTokenData; + } + + if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { + throw UnauthorizedRequestError({ + message: 'Failed service token authorization for service token data' + }); + } + + if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { + await validateUserClientForWorkspace({ + user: authData.authPayload, + workspaceId: serviceTokenData.workspace, + acceptedRoles + }); + + return serviceTokenData; + } + + throw UnauthorizedRequestError({ + message: 'Failed client authorization for service token data' + }); +} /** * Validate that service token (client) can access workspace @@ -98,6 +183,7 @@ import { UnauthorizedRequestError } from '../utils/errors'; } export { + validateClientForServiceTokenData, validateServiceTokenDataClientForWorkspace, validateServiceTokenDataClientForSecrets } \ No newline at end of file diff --git a/backend/src/middleware/requireServiceTokenDataAuth.ts b/backend/src/middleware/requireServiceTokenDataAuth.ts index f474e23c0..7715991ba 100644 --- a/backend/src/middleware/requireServiceTokenDataAuth.ts +++ b/backend/src/middleware/requireServiceTokenDataAuth.ts @@ -1,5 +1,7 @@ import { Request, Response, NextFunction } from 'express'; +import { Types } from 'mongoose'; import { ServiceToken, ServiceTokenData } from '../models'; +import { validateClientForServiceTokenData } from '../helpers/serviceTokenData'; import { validateMembership } from '../helpers/membership'; import { AccountNotFoundError, UnauthorizedRequestError } from '../utils/errors'; @@ -14,25 +16,12 @@ const requireServiceTokenDataAuth = ({ }) => { return async (req: Request, res: Response, next: NextFunction) => { const { serviceTokenDataId } = req[location]; - - const serviceTokenData = await ServiceTokenData - .findById(req[location].serviceTokenDataId) - .select('+encryptedKey +iv +tag').populate('user'); - - if (!serviceTokenData) { - return next(AccountNotFoundError({ message: 'Failed to locate service token data' })); - } - - if (req.user) { - // case: jwt auth - await validateMembership({ - userId: req.user._id, - workspaceId: serviceTokenData.workspace, - acceptedRoles - }); - } - - req.serviceTokenData = serviceTokenData; + + req.serviceTokenData = await validateClientForServiceTokenData({ + authData: req.authData, + serviceTokenDataId: new Types.ObjectId(serviceTokenDataId), + acceptedRoles + }); next(); }