Modify service token format

This commit is contained in:
Tuan Dang
2023-01-02 22:43:00 +07:00
parent b8a64714d2
commit e99ee94a7b
7 changed files with 41 additions and 38 deletions
@@ -15,7 +15,7 @@ import {
* @param res * @param res
* @returns * @returns
*/ */
export const getServiceTokenData = async (req: Request, res: Response) => ({ export const getServiceTokenData = async (req: Request, res: Response) => res.status(200).send({
serviceTokenData: req.serviceTokenData serviceTokenData: req.serviceTokenData
}); });
@@ -38,35 +38,32 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
tag, tag,
expiresIn expiresIn
} = req.body; } = 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(); const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
// create service token data serviceTokenData = await new ServiceTokenData({
serviceTokenData = new ServiceTokenData({
name, name,
workspace: workspaceId, workspace: workspaceId,
environment, environment,
user: req.user._id, user: req.user._id,
expiresAt, expiresAt,
prefix: serviceToken.substring(0, 9), secretHash,
serviceTokenHash,
encryptedKey, encryptedKey,
iv, iv,
tag tag
}) }).save();
await serviceTokenData.save();
// return service token data without sensitive data // return service token data without sensitive data
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id); 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) { } catch (err) {
Sentry.setUser({ email: req.user.email }); Sentry.setUser({ email: req.user.email });
Sentry.captureException(err); Sentry.captureException(err);
+23 -7
View File
@@ -15,7 +15,8 @@ import {
import { import {
AccountNotFoundError, AccountNotFoundError,
ServiceTokenDataNotFoundError, ServiceTokenDataNotFoundError,
UnauthorizedRequestError UnauthorizedRequestError,
BadRequestError
} from '../utils/errors'; } from '../utils/errors';
/** /**
@@ -101,15 +102,30 @@ const getAuthSTDPayload = async ({
}) => { }) => {
let serviceTokenData; let serviceTokenData;
try { 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 serviceTokenData = await ServiceTokenData
.findOne({ .findById(TOKEN_IDENTIFIER)
serviceTokenHash
})
.select('+encryptedKey +iv +tag'); .select('+encryptedKey +iv +tag');
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
} catch (err) { } catch (err) {
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
+1 -2
View File
@@ -6,8 +6,7 @@ import {
getAuthUserPayload, getAuthUserPayload,
getAuthSTDPayload getAuthSTDPayload
} from '../helpers/auth'; } from '../helpers/auth';
import { JWT_AUTH_SECRET } from '../config'; import { BadRequestError } from '../utils/errors';
import { AccountNotFoundError, BadRequestError, UnauthorizedRequestError } from '../utils/errors';
declare module 'jsonwebtoken' { declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -40,10 +40,10 @@ const requireWorkspaceAuth = ({
if ( if (
req.serviceTokenData req.serviceTokenData
&& req.serviceTokenData.workspace !== workspaceId && req.serviceTokenData.workspace !== workspaceId
&& req.serviceTokenData.environment !== req.body.environment && req.serviceTokenData.environment !== req.query.environment
) ) {
// case: st auth
next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'})) next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
}
return next(); return next();
} catch (err) { } catch (err) {
+2 -7
View File
@@ -7,8 +7,7 @@ export interface IServiceTokenData {
environment: string; // TODO: adapt to upcoming environment id environment: string; // TODO: adapt to upcoming environment id
user: Types.ObjectId; user: Types.ObjectId;
expiresAt: Date; expiresAt: Date;
prefix: string; secretHash: string;
serviceTokenHash: string;
encryptedKey: string; encryptedKey: string;
iv: string; iv: string;
tag: string; tag: string;
@@ -37,11 +36,7 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
expiresAt: { expiresAt: {
type: Date type: Date
}, },
prefix: { secretHash: {
type: String,
required: true
},
serviceTokenHash: {
type: String, type: String,
unique: true, unique: true,
required: true, required: true,
+1 -3
View File
@@ -1,4 +1,4 @@
import express, { Request, Response } from 'express'; import express from 'express';
const router = express.Router(); const router = express.Router();
import { import {
requireAuth, requireAuth,
@@ -20,8 +20,6 @@ router.get(
requireAuth({ requireAuth({
acceptedAuthModes: ['serviceToken'] acceptedAuthModes: ['serviceToken']
}), }),
param('serviceTokenDataId').exists().trim(),
validateRequest,
serviceTokenDataController.getServiceTokenData serviceTokenDataController.getServiceTokenData
); );
-2
View File
@@ -4,11 +4,9 @@ import { body, param, query } from 'express-validator';
import { import {
requireAuth, requireAuth,
requireWorkspaceAuth, requireWorkspaceAuth,
requireServiceTokenAuth,
validateRequest validateRequest
} from '../../middleware'; } from '../../middleware';
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables'; import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
import { membershipController } from '../../controllers/v1';
import { workspaceController } from '../../controllers/v2'; import { workspaceController } from '../../controllers/v2';
router.post( router.post(