mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 22:27:22 +00:00
Modify service token format
This commit is contained in:
@@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,34 +39,31 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
expiresIn
|
expiresIn
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
// create 41-char service token with first 9-char being the prefix
|
const secret = crypto.randomBytes(16).toString('hex');
|
||||||
serviceToken = `st.${crypto.randomBytes(19).toString('hex')}`;
|
const secretHash = await bcrypt.hash(secret, SALT_ROUNDS);
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
AccountNotFoundError,
|
AccountNotFoundError,
|
||||||
ServiceTokenDataNotFoundError,
|
ServiceTokenDataNotFoundError,
|
||||||
UnauthorizedRequestError
|
UnauthorizedRequestError,
|
||||||
|
BadRequestError
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,16 +102,31 @@ 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
|
serviceTokenData = await ServiceTokenData
|
||||||
.findOne({
|
.findById(TOKEN_IDENTIFIER, 'secretHash expiresAt');
|
||||||
serviceTokenHash
|
|
||||||
})
|
if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
|
||||||
.select('+encryptedKey +iv +tag');
|
// 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' });
|
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
|
||||||
|
.findById(TOKEN_IDENTIFIER)
|
||||||
|
.select('+encryptedKey +iv +tag');
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: 'Failed to authenticate service token'
|
message: 'Failed to authenticate service token'
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user