From aa1e0b0f28be388be54e2915edf01d6bad4528e5 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 26 Sep 2023 12:03:00 +0100 Subject: [PATCH] Update audit log service actor v3 filter, status toggle permissions, add JWT service token secret, for ST V3 --- backend/src/config/index.ts | 1 + .../v3/serviceTokenDataController.ts | 3 +- .../ee/controllers/v1/workspaceController.ts | 30 +++++++++++++++---- backend/src/helpers/auth.ts | 26 ++++++++++------ frontend/src/hooks/api/auditLogs/enums.tsx | 3 +- frontend/src/hooks/api/auditLogs/types.tsx | 8 ++++- .../AuditLogsPage/components/LogsFilter.tsx | 6 ++++ .../AuditLogsPage/components/LogsTableRow.tsx | 7 +++++ .../ServiceTokenV3Section.tsx | 2 +- .../ServiceTokenV3Table.tsx | 26 ++++++++++------ 10 files changed, 85 insertions(+), 27 deletions(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index b5e50d9da..d84d2ff67 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -26,6 +26,7 @@ export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIG export const getJwtProviderAuthSecret = async () => (await client.getSecret("JWT_PROVIDER_AUTH_SECRET")).secretValue; export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m"; export const getJwtSignupSecret = async () => (await client.getSecret("JWT_SIGNUP_SECRET")).secretValue; +export const getJwtServiceTokenSecret = async () => (await client.getSecret("JWT_SERVICE_TOKEN_SECRET")).secretValue; export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue; export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production"; export const getVerboseErrorOutput = async () => (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true; diff --git a/backend/src/controllers/v3/serviceTokenDataController.ts b/backend/src/controllers/v3/serviceTokenDataController.ts index fdc68eddb..6d9ce7e5d 100644 --- a/backend/src/controllers/v3/serviceTokenDataController.ts +++ b/backend/src/controllers/v3/serviceTokenDataController.ts @@ -22,6 +22,7 @@ import { import { ForbiddenError } from "@casl/ability"; import { BadRequestError, ResourceNotFoundError } from "../../utils/errors"; import { EEAuditLogService } from "../../ee/services"; +import { getJwtServiceTokenSecret } from "../../config"; /** * Create service token data @@ -82,7 +83,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => { _id: serviceTokenData._id.toString() }, expiresIn, - secret: "hello" // TODO: replace with real secret + secret: await getJwtServiceTokenSecret() }); await EEAuditLogService.createAuditLog( diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index 7c9502656..861131106 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -5,6 +5,7 @@ import { Membership, Secret, ServiceTokenData, + ServiceTokenDataV3, TFolderSchema, User, Workspace @@ -20,6 +21,7 @@ import { SecretSnapshot, SecretVersion, ServiceActor, + ServiceActorV3, TFolderRootVersionSchema, TrustedIP, UserActor @@ -653,7 +655,7 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs ); - + const query = { workspace: new Types.ObjectId(workspaceId), ...(eventType @@ -668,13 +670,13 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { : {}), ...(actor ? { - "actor.type": actor.split("-", 2)[0], + "actor.type": actor.substring(0, actor.lastIndexOf("-")), ...(actor.split("-", 2)[0] === ActorType.USER ? { - "actor.metadata.userId": actor.split("-", 2)[1] + "actor.metadata.userId": actor.substring(actor.lastIndexOf("-") + 1) } : { - "actor.metadata.serviceId": actor.split("-", 2)[1] + "actor.metadata.serviceId": actor.substring(actor.lastIndexOf("-") + 1) }) } : {}), @@ -742,9 +744,27 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res name: serviceTokenData.name } })); + + const serviceV3Actors: ServiceActorV3[] = ( + await ServiceTokenDataV3.find({ + workspace: new Types.ObjectId(workspaceId) + }) + ).map((serviceTokenData) => ({ + type: ActorType.SERVICE_V3, + metadata: { + serviceId: serviceTokenData._id.toString(), + name: serviceTokenData.name + } + })); + + const actors = [ + ...userActors, + ...serviceActors, + ...serviceV3Actors + ]; return res.status(200).send({ - actors: [...userActors, ...serviceActors] + actors }); }; diff --git a/backend/src/helpers/auth.ts b/backend/src/helpers/auth.ts index 55d8eeb53..bf7e7d868 100644 --- a/backend/src/helpers/auth.ts +++ b/backend/src/helpers/auth.ts @@ -24,6 +24,7 @@ import { getJwtProviderAuthSecret, getJwtRefreshLifetime, getJwtRefreshSecret, + getJwtServiceTokenSecret } from "../config"; import { AuthMode @@ -87,7 +88,7 @@ export const validateAuthMode = ({ break; case "proj_token": authMode = AuthMode.SERVICE_TOKEN_V3; - authTokenValue = parts.slice(1).join('.'); + authTokenValue = parts.slice(1).join("."); break; default: authMode = AuthMode.JWT; @@ -239,21 +240,28 @@ export const getAuthSTDPayload = async ({ authTokenValue: string; }): Promise => { const decodedToken = ( - jwt.verify(authTokenValue, "hello") // TODO: replace with real secret + jwt.verify(authTokenValue, await getJwtServiceTokenSecret()) + ); + + const serviceTokenData = await ServiceTokenDataV3.findOneAndUpdate( + { + _id: new Types.ObjectId(decodedToken._id), + isActive: true + }, + { + lastUsed: new Date() + }, + { + new: true + } ); - - const serviceTokenData = await ServiceTokenDataV3.findOne({ - _id: new Types.ObjectId(decodedToken._id), - isActive: true - }); if (!serviceTokenData) { throw UnauthorizedRequestError({ - message: "Failed to authenticate" // standardize auth error messages + message: "Failed to authenticate" }); } else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) { // case: service token expired - // TODO: test expired token await ServiceTokenDataV3.findByIdAndUpdate( serviceTokenData._id, { diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx index 765b2b941..c292fdc24 100644 --- a/frontend/src/hooks/api/auditLogs/enums.tsx +++ b/frontend/src/hooks/api/auditLogs/enums.tsx @@ -1,6 +1,7 @@ export enum ActorType { USER = "user", - SERVICE = "service" + SERVICE = "service", + SERVICE_V3 = "service-v3" } export enum UserAgentType { diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index 8b9767295..f28f9004c 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -35,9 +35,15 @@ export interface ServiceActor { metadata: ServiceActorMetadata; } +export interface ServiceActorV3 { + type: ActorType.SERVICE_V3; + metadata: ServiceActorMetadata; +} + export type Actor = | UserActor - | ServiceActor; + | ServiceActor + | ServiceActorV3; interface GetSecretsEvent { type: EventType.GET_SECRETS; diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx index 43bb1e38f..ddc271ef0 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx @@ -50,6 +50,12 @@ export const LogsFilter = ({ {actor.metadata.name} ); + case ActorType.SERVICE_V3: + return ( + + {actor.metadata.name} + + ); default: return ( diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx b/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx index 52cd88cc1..52f35393a 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx +++ b/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx @@ -29,6 +29,13 @@ export const LogsTableRow = ({

Service token

); + case ActorType.SERVICE_V3: + return ( + +

{`${actor.metadata.name}`}

+

Service token V3

+ + ); default: return ( diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Section.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Section.tsx index a80a18a6d..574415742 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Section.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Section.tsx @@ -50,7 +50,7 @@ export const ServiceTokenV3Section = withProjectPermission(

- (New) Service Tokens + Service Tokens V3 (Beta)

{name} - handleToggleServiceTokenDataStatus({ - serviceTokenDataId: _id, - isActive: value - })} - isChecked={isActive} + -

{isActive ? "Active" : "Inactive"}

-
+ {(isAllowed) => ( + handleToggleServiceTokenDataStatus({ + serviceTokenDataId: _id, + isActive: value + })} + isChecked={isActive} + isDisabled={!isAllowed} + > +

{isActive ? "Active" : "Inactive"}

+
+ )} +
{scopes.map((scope) => {