diff --git a/backend/src/helpers/auth.ts b/backend/src/helpers/auth.ts index be10dbbfa..58f8c0e15 100644 --- a/backend/src/helpers/auth.ts +++ b/backend/src/helpers/auth.ts @@ -7,6 +7,7 @@ import { ITokenVersion, IUser, ServiceTokenData, + ServiceTokenDataV3, TokenVersion, User, } from "../models"; @@ -29,6 +30,7 @@ import { } from "../variables"; import { ServiceTokenAuthData, + ServiceTokenV3AuthData, UserAuthData } from "../interfaces/middleware"; @@ -47,6 +49,9 @@ export const validateAuthMode = ({ headers: { [key: string]: string | string[] | undefined }, acceptedAuthModes: AuthMode[] }) => { + + // TODO: update this to accept service token v3 + const apiKey = headers["x-api-key"]; const authHeader = headers["authorization"]; @@ -76,6 +81,9 @@ export const validateAuthMode = ({ case "st": authMode = AuthMode.SERVICE_TOKEN; break; + case "proj_token": + authMode = AuthMode.SERVICE_TOKEN_V3; + break; default: authMode = AuthMode.JWT; } @@ -211,8 +219,55 @@ export const getAuthSTDPayload = async ({ userAgent: req.headers["user-agent"] ?? "", userAgentType: getUserAgentType(req.headers["user-agent"]) } +} - // return serviceTokenDataToReturn; +/** + * Return service token data V3 payload corresponding to service token [authTokenValue] + * @param {Object} obj + * @param {String} obj.authTokenValue - service token value + * @returns {ServiceTokenData} serviceTokenData - service token data + */ + export const getAuthSTDV3Payload = async ({ + req, + authTokenValue, +}: { + req: Request, + authTokenValue: string; +}): Promise => { + const decodedToken = ( + jwt.verify(authTokenValue, "hello") // TODO: change this + ); + + // perhaps turn this one into a find one and update call? + const serviceTokenData = await ServiceTokenDataV3.findOne({ + _id: new Types.ObjectId(decodedToken.serviceTokenDataId), + }); + + if (!serviceTokenData) { + throw UnauthorizedRequestError({ + message: "Failed to authenticate" // standardize auth error messages + }); + } else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) { + // case: service token expired + await ServiceTokenDataV3.findByIdAndDelete(serviceTokenData._id); + throw UnauthorizedRequestError({ + message: "Failed to authenticate", + }); + } + + return { + actor: { + type: ActorType.SERVICE, // should this be servicev3 bc the shape of it is different? + metadata: { + serviceId: serviceTokenData._id.toString(), + name: serviceTokenData.name + } + }, + authPayload: serviceTokenData, + ipAddress: req.realIP, + userAgent: req.headers["user-agent"] ?? "", + userAgentType: getUserAgentType(req.headers["user-agent"]) + } } /** diff --git a/backend/src/interfaces/middleware/index.ts b/backend/src/interfaces/middleware/index.ts index e8dd1b91b..f873eff26 100644 --- a/backend/src/interfaces/middleware/index.ts +++ b/backend/src/interfaces/middleware/index.ts @@ -1,6 +1,7 @@ import { Types } from "mongoose"; import { IServiceTokenData, + IServiceTokenDataV3, IUser, } from "../../models"; import { @@ -21,6 +22,11 @@ export interface UserAuthData extends BaseAuthData { authPayload: IUser; } +export interface ServiceTokenV3AuthData extends BaseAuthData { + actor: ServiceActor; + authPayload: IServiceTokenDataV3; +} + export interface ServiceTokenAuthData extends BaseAuthData { actor: ServiceActor; authPayload: IServiceTokenData; @@ -28,4 +34,5 @@ export interface ServiceTokenAuthData extends BaseAuthData { export type AuthData = | UserAuthData + | ServiceTokenV3AuthData | ServiceTokenAuthData; \ No newline at end of file diff --git a/backend/src/middleware/requireAuth.ts b/backend/src/middleware/requireAuth.ts index e256f6665..aa74b0dc7 100644 --- a/backend/src/middleware/requireAuth.ts +++ b/backend/src/middleware/requireAuth.ts @@ -3,6 +3,7 @@ import { NextFunction, Request, Response } from "express"; import { getAuthAPIKeyPayload, getAuthSTDPayload, + getAuthSTDV3Payload, getAuthUserPayload, validateAuthMode, } from "../helpers/auth"; @@ -49,6 +50,12 @@ const requireAuth = ({ }); req.serviceTokenData = authData.authPayload; break; + case AuthMode.SERVICE_TOKEN_V3: + authData = await getAuthSTDV3Payload({ + req, + authTokenValue + }); + break; case AuthMode.API_KEY: authData = await getAuthAPIKeyPayload({ req, @@ -61,9 +68,7 @@ const requireAuth = ({ req, authTokenValue }); - // authPayload = authUserPayload.user; req.user = authData.authPayload; - // req.tokenVersionId = authUserPayload.tokenVersionId; // TODO break; } diff --git a/backend/src/models/serviceTokenDataV3.ts b/backend/src/models/serviceTokenDataV3.ts index 049c2a33c..e30420e04 100644 --- a/backend/src/models/serviceTokenDataV3.ts +++ b/backend/src/models/serviceTokenDataV3.ts @@ -17,7 +17,8 @@ export interface IServiceTokenDataV3 extends Document { workspace: Types.ObjectId; publicKey: string; isActive: boolean; - lastUsed: Date; + lastUsed?: Date; + expiresAt?: Date; scopes: Array; } diff --git a/backend/src/validation/integrationAuth.ts b/backend/src/validation/integrationAuth.ts index cf3f936a9..af73257c6 100644 --- a/backend/src/validation/integrationAuth.ts +++ b/backend/src/validation/integrationAuth.ts @@ -154,7 +154,7 @@ export const DeleteIntegrationAuthV1 = z.object({ export const GetIntegrationAuthTeamCityBuildConfigsV1 = z.object({ params:z.object({ - appId:z.string().trim(), + appId:z.string().trim().optional(), integrationAuthId:z.string().trim() }) }) diff --git a/backend/src/variables/authentication.ts b/backend/src/variables/authentication.ts index be2f35c81..1376c977e 100644 --- a/backend/src/variables/authentication.ts +++ b/backend/src/variables/authentication.ts @@ -1,6 +1,7 @@ export enum AuthMode { JWT = "jwt", SERVICE_TOKEN = "serviceToken", + SERVICE_TOKEN_V3 = "serviceTokenV3", API_KEY = "apiKey" }