diff --git a/backend/src/controllers/v1/serviceTokenDataController.ts b/backend/src/controllers/v1/serviceTokenDataController.ts index 3a4b94a83..d8f4d4eea 100644 --- a/backend/src/controllers/v1/serviceTokenDataController.ts +++ b/backend/src/controllers/v1/serviceTokenDataController.ts @@ -15,7 +15,7 @@ import { * @param res * @returns */ -export const getServiceTokenData = async (req: Request, res: Response) => ({ +export const getServiceTokenData = async (req: Request, res: Response) => res.status(200).send({ serviceTokenData: req.serviceTokenData }); @@ -38,35 +38,32 @@ export const createServiceTokenData = async (req: Request, res: Response) => { tag, expiresIn } = req.body; + + const secret = crypto.randomBytes(16).toString('hex'); + const secretHash = await bcrypt.hash(secret, SALT_ROUNDS); - // create 41-char service token with first 9-char being the prefix - serviceToken = `st.${crypto.randomBytes(19).toString('hex')}`; - - const serviceTokenHash = await bcrypt.hash(serviceToken, SALT_ROUNDS); - - // compute access token expiration date const expiresAt = new Date(); expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); - // create service token data - serviceTokenData = new ServiceTokenData({ + serviceTokenData = await new ServiceTokenData({ name, workspace: workspaceId, environment, user: req.user._id, expiresAt, - prefix: serviceToken.substring(0, 9), - serviceTokenHash, + secretHash, encryptedKey, iv, tag - }) + }).save(); - await serviceTokenData.save(); - // return service token data without sensitive data serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id); + if (!serviceTokenData) throw new Error('Failed to find service token data'); + + serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`; + } catch (err) { Sentry.setUser({ email: req.user.email }); Sentry.captureException(err); diff --git a/backend/src/helpers/auth.ts b/backend/src/helpers/auth.ts index c3b212916..ad63d41b4 100644 --- a/backend/src/helpers/auth.ts +++ b/backend/src/helpers/auth.ts @@ -15,7 +15,8 @@ import { import { AccountNotFoundError, ServiceTokenDataNotFoundError, - UnauthorizedRequestError + UnauthorizedRequestError, + BadRequestError } from '../utils/errors'; /** @@ -101,15 +102,30 @@ const getAuthSTDPayload = async ({ }) => { let serviceTokenData; try { - const serviceTokenHash = await bcrypt.hash(authTokenValue, SALT_ROUNDS); + const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3); + + // TODO: optimize double query + serviceTokenData = await ServiceTokenData + .findById(TOKEN_IDENTIFIER, 'secretHash expiresAt'); + + if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) { + // case: service token expired + await ServiceTokenData.findByIdAndDelete(serviceTokenData._id); + throw UnauthorizedRequestError({ + message: 'Failed to authenticate expired service token' + }); + } + + 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' + }); serviceTokenData = await ServiceTokenData - .findOne({ - serviceTokenHash - }) + .findById(TOKEN_IDENTIFIER) .select('+encryptedKey +iv +tag'); - - if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' }); } catch (err) { throw UnauthorizedRequestError({ diff --git a/backend/src/middleware/requireAuth.ts b/backend/src/middleware/requireAuth.ts index b91d2cd09..8253cb64e 100644 --- a/backend/src/middleware/requireAuth.ts +++ b/backend/src/middleware/requireAuth.ts @@ -6,8 +6,7 @@ import { getAuthUserPayload, getAuthSTDPayload } from '../helpers/auth'; -import { JWT_AUTH_SECRET } from '../config'; -import { AccountNotFoundError, BadRequestError, UnauthorizedRequestError } from '../utils/errors'; +import { BadRequestError } from '../utils/errors'; declare module 'jsonwebtoken' { export interface UserIDJwtPayload extends jwt.JwtPayload { diff --git a/backend/src/middleware/requireWorkspaceAuth.ts b/backend/src/middleware/requireWorkspaceAuth.ts index 2a9110c2c..68edec6a4 100644 --- a/backend/src/middleware/requireWorkspaceAuth.ts +++ b/backend/src/middleware/requireWorkspaceAuth.ts @@ -40,10 +40,10 @@ const requireWorkspaceAuth = ({ if ( req.serviceTokenData && req.serviceTokenData.workspace !== workspaceId - && req.serviceTokenData.environment !== req.body.environment - ) - // case: st auth + && req.serviceTokenData.environment !== req.query.environment + ) { next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'})) + } return next(); } catch (err) { diff --git a/backend/src/models/serviceTokenData .ts b/backend/src/models/serviceTokenData .ts index 1faecfaff..8e8ae5eab 100644 --- a/backend/src/models/serviceTokenData .ts +++ b/backend/src/models/serviceTokenData .ts @@ -7,8 +7,7 @@ export interface IServiceTokenData { environment: string; // TODO: adapt to upcoming environment id user: Types.ObjectId; expiresAt: Date; - prefix: string; - serviceTokenHash: string; + secretHash: string; encryptedKey: string; iv: string; tag: string; @@ -37,11 +36,7 @@ const serviceTokenDataSchema = new Schema( expiresAt: { type: Date }, - prefix: { - type: String, - required: true - }, - serviceTokenHash: { + secretHash: { type: String, unique: true, required: true, diff --git a/backend/src/routes/v1/serviceTokenData.ts b/backend/src/routes/v1/serviceTokenData.ts index 4223172d3..1d7700615 100644 --- a/backend/src/routes/v1/serviceTokenData.ts +++ b/backend/src/routes/v1/serviceTokenData.ts @@ -1,4 +1,4 @@ -import express, { Request, Response } from 'express'; +import express from 'express'; const router = express.Router(); import { requireAuth, @@ -20,8 +20,6 @@ router.get( requireAuth({ acceptedAuthModes: ['serviceToken'] }), - param('serviceTokenDataId').exists().trim(), - validateRequest, serviceTokenDataController.getServiceTokenData ); diff --git a/backend/src/routes/v2/workspace.ts b/backend/src/routes/v2/workspace.ts index df26749f3..b2c91bd1b 100644 --- a/backend/src/routes/v2/workspace.ts +++ b/backend/src/routes/v2/workspace.ts @@ -4,11 +4,9 @@ import { body, param, query } from 'express-validator'; import { requireAuth, requireWorkspaceAuth, - requireServiceTokenAuth, validateRequest } from '../../middleware'; import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables'; -import { membershipController } from '../../controllers/v1'; import { workspaceController } from '../../controllers/v2'; router.post(