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 de3ce9af6..06fdeebcf 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -186,6 +186,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", ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth", UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth", @@ -1029,6 +1030,15 @@ interface GetTokensIdentityTokenAuthEvent { }; } +interface GetTokenIdentityTokenAuthEvent { + type: EventType.GET_TOKEN_IDENTITY_TOKEN_AUTH; + metadata: { + identityId: string; + identityName: string; + tokenId: string; + }; +} + interface AddIdentityTokenAuthEvent { type: EventType.ADD_IDENTITY_TOKEN_AUTH; metadata: { @@ -4214,6 +4224,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 5054226af..bbbcb3a04 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -592,6 +592,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..d7cd86330 100644 --- a/backend/src/server/routes/v1/identity-token-auth-router.ts +++ b/backend/src/server/routes/v1/identity-token-auth-router.ts @@ -314,7 +314,8 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider accessToken: z.string(), expiresIn: z.coerce.number(), accessTokenMaxTTL: z.coerce.number(), - tokenType: z.literal("Bearer") + tokenType: z.literal("Bearer"), + tokenData: IdentityAccessTokensSchema }) } }, @@ -346,7 +347,8 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider accessToken, tokenType: "Bearer" as const, expiresIn: identityTokenAuth.accessTokenTTL, - accessTokenMaxTTL: identityTokenAuth.accessTokenMaxTTL + accessTokenMaxTTL: identityTokenAuth.accessTokenMaxTTL, + tokenData: identityAccessToken }; } }); @@ -406,6 +408,60 @@ 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, + identityName: identityMembershipOrg.identity.name, + tokenId: token.id + } + } + }); + + return { token }; + } + }); + server.route({ method: "PATCH", url: "/token-auth/tokens/:tokenId", 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 20c692134..bdc8ab1c1 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 @@ -38,6 +38,7 @@ import { TAttachTokenAuthDTO, TCreateTokenAuthTokenDTO, TGetTokenAuthDTO, + TGetTokenAuthTokenByIdDTO, TGetTokenAuthTokensDTO, TRevokeTokenAuthDTO, TRevokeTokenAuthTokenDTO, @@ -618,6 +619,52 @@ 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, + [`${TableName.IdentityAccessToken}.identityId` as "identityId"]: identityId + }); + + if (!token) throw new NotFoundError({ message: `Token with ID ${tokenId} not found` }); + + return { token, identityMembershipOrg }; + }; + const updateTokenAuthToken = async ({ tokenId, name, @@ -797,6 +844,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 4a0f91f09..b28e9385b 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -840,9 +840,7 @@ export type CreateTokenIdentityTokenAuthDTO = { export type CreateTokenIdentityTokenAuthRes = { accessToken: string; - tokenType: string; - expiresIn: number; - accessTokenMaxTTL: number; + tokenData: IdentityAccessToken; }; export type UpdateTokenIdentityTokenAuthDTO = {