From 8d9775c42a67c563e080402b492d2c570c8ae146 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Wed, 12 Nov 2025 21:50:21 +0530 Subject: [PATCH 1/4] feat: adds GET endpoint for single auth token by ID --- .../ee/services/audit-log/audit-log-types.ts | 10 +++ backend/src/lib/api-docs/constants.ts | 4 ++ .../routes/v1/identity-token-auth-router.ts | 61 +++++++++++++++++-- .../identity-token-auth-service.ts | 47 ++++++++++++++ .../identity-token-auth-types.ts | 6 ++ frontend/src/hooks/api/identities/types.ts | 4 +- 6 files changed, 123 insertions(+), 9 deletions(-) diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index bcc2a0770..97dfdb9fa 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -173,6 +173,7 @@ export enum EventType { CREATE_TOKEN_IDENTITY_TOKEN_AUTH = "create-token-identity-token-auth", UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth", GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth", + GET_TOKEN_IDENTITY_TOKEN_AUTH = "get-token-identity-token-auth", CREATE_SUB_ORGANIZATION = "create-sub-organization", UPDATE_SUB_ORGANIZATION = "update-sub-organization", @@ -1013,6 +1014,14 @@ interface GetTokensIdentityTokenAuthEvent { }; } +interface GetTokenIdentityTokenAuthEvent { + type: EventType.GET_TOKEN_IDENTITY_TOKEN_AUTH; + metadata: { + identityId: string; + tokenId: string; + }; +} + interface AddIdentityTokenAuthEvent { type: EventType.ADD_IDENTITY_TOKEN_AUTH; metadata: { @@ -4128,6 +4137,7 @@ export type Event = | CreateTokenIdentityTokenAuthEvent | UpdateTokenIdentityTokenAuthEvent | GetTokensIdentityTokenAuthEvent + | GetTokenIdentityTokenAuthEvent | AddIdentityTokenAuthEvent | UpdateIdentityTokenAuthEvent | GetIdentityTokenAuthEvent diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 927694ef4..09adf2b8b 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -577,6 +577,10 @@ export const TOKEN_AUTH = { offset: "The offset to start from. If you enter 10, it will start from the 10th token.", limit: "The number of tokens to return." }, + GET_TOKEN: { + identityId: "The ID of the machine identity to get the token for.", + tokenId: "The ID of the token to get metadata for." + }, CREATE_TOKEN: { identityId: "The ID of the machine identity to create the token for.", name: "The name of the token to create." diff --git a/backend/src/server/routes/v1/identity-token-auth-router.ts b/backend/src/server/routes/v1/identity-token-auth-router.ts index aafffdfdb..9906b6b5d 100644 --- a/backend/src/server/routes/v1/identity-token-auth-router.ts +++ b/backend/src/server/routes/v1/identity-token-auth-router.ts @@ -312,9 +312,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider response: { 200: z.object({ accessToken: z.string(), - expiresIn: z.coerce.number(), - accessTokenMaxTTL: z.coerce.number(), - tokenType: z.literal("Bearer") + tokenData: IdentityAccessTokensSchema }) } }, @@ -344,9 +342,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider return { accessToken, - tokenType: "Bearer" as const, - expiresIn: identityTokenAuth.accessTokenTTL, - accessTokenMaxTTL: identityTokenAuth.accessTokenMaxTTL + tokenData: identityAccessToken }; } }); @@ -406,6 +402,59 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider } }); + server.route({ + method: "GET", + url: "/token-auth/identities/:identityId/tokens/:tokenId", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.TokenAuth], + description: "Get token for machine identity with Token Auth", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + identityId: z.string().describe(TOKEN_AUTH.GET_TOKEN.identityId), + tokenId: z.string().describe(TOKEN_AUTH.GET_TOKEN.tokenId) + }), + response: { + 200: z.object({ + token: IdentityAccessTokensSchema + }) + } + }, + handler: async (req) => { + const { token, identityMembershipOrg } = await server.services.identityTokenAuth.getTokenAuthTokenById({ + identityId: req.params.identityId, + tokenId: req.params.tokenId, + actor: req.permission.type, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + isActorSuperAdmin: isSuperAdmin(req.auth) + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: identityMembershipOrg.scopeOrgId, + event: { + type: EventType.GET_TOKEN_IDENTITY_TOKEN_AUTH, + metadata: { + identityId: token.identityId, + tokenId: token.id + } + } + }); + + return { token }; + } + }); + server.route({ method: "PATCH", url: "/token-auth/tokens/:tokenId", diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index e6969da61..8c2c90711 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -31,6 +31,7 @@ import { TAttachTokenAuthDTO, TCreateTokenAuthTokenDTO, TGetTokenAuthDTO, + TGetTokenAuthTokenByIdDTO, TGetTokenAuthTokensDTO, TRevokeTokenAuthDTO, TRevokeTokenAuthTokenDTO, @@ -499,6 +500,51 @@ export const identityTokenAuthServiceFactory = ({ return { tokens, identityMembershipOrg }; }; + const getTokenAuthTokenById = async ({ + tokenId, + identityId, + isActorSuperAdmin, + actorId, + actor, + actorAuthMethod, + actorOrgId + }: TGetTokenAuthTokenByIdDTO) => { + await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin); + + const identityMembershipOrg = await membershipIdentityDAL.getIdentityById({ + scopeData: { + scope: AccessScope.Organization, + orgId: actorOrgId + }, + identityId + }); + if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { + throw new BadRequestError({ + message: "The identity does not have Token Auth" + }); + } + const { permission } = await permissionService.getOrgPermission({ + scope: OrganizationActionScope.Any, + actor, + actorId, + orgId: identityMembershipOrg.scopeOrgId, + actorAuthMethod, + actorOrgId + }); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); + + const token = await identityAccessTokenDAL.findOne({ + [`${TableName.IdentityAccessToken}.id` as "id"]: tokenId, + [`${TableName.IdentityAccessToken}.authMethod` as "authMethod"]: IdentityAuthMethod.TOKEN_AUTH + }); + + if (!token) throw new NotFoundError({ message: `Token with ID ${tokenId} not found` }); + + return { token, identityMembershipOrg }; + }; + const updateTokenAuthToken = async ({ tokenId, name, @@ -642,6 +688,7 @@ export const identityTokenAuthServiceFactory = ({ revokeIdentityTokenAuth, createTokenAuthToken, getTokenAuthTokens, + getTokenAuthTokenById, updateTokenAuthToken, revokeTokenAuthToken }; diff --git a/backend/src/services/identity-token-auth/identity-token-auth-types.ts b/backend/src/services/identity-token-auth/identity-token-auth-types.ts index 16cd60db7..fdecc6d4c 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-types.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-types.ts @@ -40,6 +40,12 @@ export type TGetTokenAuthTokensDTO = { isActorSuperAdmin?: boolean; } & Omit; +export type TGetTokenAuthTokenByIdDTO = { + tokenId: string; + identityId: string; + isActorSuperAdmin?: boolean; +} & Omit; + export type TUpdateTokenAuthTokenDTO = { tokenId: string; name?: string; diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts index a0eb828e8..b7be75e1c 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -765,9 +765,7 @@ export type CreateTokenIdentityTokenAuthDTO = { export type CreateTokenIdentityTokenAuthRes = { accessToken: string; - tokenType: string; - expiresIn: number; - accessTokenMaxTTL: number; + tokenData: IdentityAccessToken; }; export type UpdateTokenIdentityTokenAuthDTO = { From 4091dc53a570faa5f6a5cf8eff53c55bb367fa79 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Wed, 12 Nov 2025 23:00:58 +0530 Subject: [PATCH 2/4] refactor: simplify token retrieval by using findById method --- .../identity-token-auth/identity-token-auth-service.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index 8c2c90711..1ece312ea 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -535,10 +535,7 @@ export const identityTokenAuthServiceFactory = ({ }); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); - const token = await identityAccessTokenDAL.findOne({ - [`${TableName.IdentityAccessToken}.id` as "id"]: tokenId, - [`${TableName.IdentityAccessToken}.authMethod` as "authMethod"]: IdentityAuthMethod.TOKEN_AUTH - }); + const token = await identityAccessTokenDAL.findById(tokenId); if (!token) throw new NotFoundError({ message: `Token with ID ${tokenId} not found` }); From a76e323b6cf4528aea3b5769624e2ddbe36620ba Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Wed, 12 Nov 2025 23:27:19 +0530 Subject: [PATCH 3/4] fix: identityAccessTokenDALFactory.findOne dal function --- .../identity-access-token/identity-access-token-dal.ts | 1 - .../identity-token-auth/identity-token-auth-service.ts | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/services/identity-access-token/identity-access-token-dal.ts b/backend/src/services/identity-access-token/identity-access-token-dal.ts index ffdb78645..74b624a7e 100644 --- a/backend/src/services/identity-access-token/identity-access-token-dal.ts +++ b/backend/src/services/identity-access-token/identity-access-token-dal.ts @@ -18,7 +18,6 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => { .where(filter) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`) .select(selectAllTableCols(TableName.IdentityAccessToken)) - .select(db.ref("name").withSchema(TableName.Identity)) .select(db.ref("orgId").withSchema(TableName.Identity).as("identityScopeOrgId")) .first(); diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index 1ece312ea..97717c4f3 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -535,7 +535,11 @@ export const identityTokenAuthServiceFactory = ({ }); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); - const token = await identityAccessTokenDAL.findById(tokenId); + const token = await identityAccessTokenDAL.findOne({ + [`${TableName.IdentityAccessToken}.id` as "id"]: tokenId, + [`${TableName.IdentityAccessToken}.authMethod` as "authMethod"]: IdentityAuthMethod.TOKEN_AUTH, + [`${TableName.IdentityAccessToken}.identityId` as "identityId"]: identityId + }); if (!token) throw new NotFoundError({ message: `Token with ID ${tokenId} not found` }); From 7d6f52d2aed7a74918556f2c18c1468896e181f6 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Mon, 17 Nov 2025 20:48:57 +0530 Subject: [PATCH 4/4] feat: add identityName to audit log event and enhance identity token response structure --- backend/src/ee/services/audit-log/audit-log-types.ts | 1 + backend/src/server/routes/v1/identity-token-auth-router.ts | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 021da107f..06fdeebcf 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -1034,6 +1034,7 @@ interface GetTokenIdentityTokenAuthEvent { type: EventType.GET_TOKEN_IDENTITY_TOKEN_AUTH; metadata: { identityId: string; + identityName: string; tokenId: string; }; } diff --git a/backend/src/server/routes/v1/identity-token-auth-router.ts b/backend/src/server/routes/v1/identity-token-auth-router.ts index 9906b6b5d..d7cd86330 100644 --- a/backend/src/server/routes/v1/identity-token-auth-router.ts +++ b/backend/src/server/routes/v1/identity-token-auth-router.ts @@ -312,6 +312,9 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider response: { 200: z.object({ accessToken: z.string(), + expiresIn: z.coerce.number(), + accessTokenMaxTTL: z.coerce.number(), + tokenType: z.literal("Bearer"), tokenData: IdentityAccessTokensSchema }) } @@ -342,6 +345,9 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider return { accessToken, + tokenType: "Bearer" as const, + expiresIn: identityTokenAuth.accessTokenTTL, + accessTokenMaxTTL: identityTokenAuth.accessTokenMaxTTL, tokenData: identityAccessToken }; } @@ -446,6 +452,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider type: EventType.GET_TOKEN_IDENTITY_TOKEN_AUTH, metadata: { identityId: token.identityId, + identityName: identityMembershipOrg.identity.name, tokenId: token.id } }