feat: adds GET endpoint for single auth token by ID

This commit is contained in:
Piyush Gupta
2025-11-12 21:50:21 +05:30
parent 6e622c8f8c
commit 8d9775c42a
6 changed files with 123 additions and 9 deletions
@@ -173,6 +173,7 @@ export enum EventType {
CREATE_TOKEN_IDENTITY_TOKEN_AUTH = "create-token-identity-token-auth", CREATE_TOKEN_IDENTITY_TOKEN_AUTH = "create-token-identity-token-auth",
UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-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_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", CREATE_SUB_ORGANIZATION = "create-sub-organization",
UPDATE_SUB_ORGANIZATION = "update-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 { interface AddIdentityTokenAuthEvent {
type: EventType.ADD_IDENTITY_TOKEN_AUTH; type: EventType.ADD_IDENTITY_TOKEN_AUTH;
metadata: { metadata: {
@@ -4128,6 +4137,7 @@ export type Event =
| CreateTokenIdentityTokenAuthEvent | CreateTokenIdentityTokenAuthEvent
| UpdateTokenIdentityTokenAuthEvent | UpdateTokenIdentityTokenAuthEvent
| GetTokensIdentityTokenAuthEvent | GetTokensIdentityTokenAuthEvent
| GetTokenIdentityTokenAuthEvent
| AddIdentityTokenAuthEvent | AddIdentityTokenAuthEvent
| UpdateIdentityTokenAuthEvent | UpdateIdentityTokenAuthEvent
| GetIdentityTokenAuthEvent | GetIdentityTokenAuthEvent
+4
View File
@@ -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.", offset: "The offset to start from. If you enter 10, it will start from the 10th token.",
limit: "The number of tokens to return." 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: { CREATE_TOKEN: {
identityId: "The ID of the machine identity to create the token for.", identityId: "The ID of the machine identity to create the token for.",
name: "The name of the token to create." name: "The name of the token to create."
@@ -312,9 +312,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
response: { response: {
200: z.object({ 200: z.object({
accessToken: z.string(), accessToken: z.string(),
expiresIn: z.coerce.number(), tokenData: IdentityAccessTokensSchema
accessTokenMaxTTL: z.coerce.number(),
tokenType: z.literal("Bearer")
}) })
} }
}, },
@@ -344,9 +342,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
return { return {
accessToken, accessToken,
tokenType: "Bearer" as const, tokenData: identityAccessToken
expiresIn: identityTokenAuth.accessTokenTTL,
accessTokenMaxTTL: identityTokenAuth.accessTokenMaxTTL
}; };
} }
}); });
@@ -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({ server.route({
method: "PATCH", method: "PATCH",
url: "/token-auth/tokens/:tokenId", url: "/token-auth/tokens/:tokenId",
@@ -31,6 +31,7 @@ import {
TAttachTokenAuthDTO, TAttachTokenAuthDTO,
TCreateTokenAuthTokenDTO, TCreateTokenAuthTokenDTO,
TGetTokenAuthDTO, TGetTokenAuthDTO,
TGetTokenAuthTokenByIdDTO,
TGetTokenAuthTokensDTO, TGetTokenAuthTokensDTO,
TRevokeTokenAuthDTO, TRevokeTokenAuthDTO,
TRevokeTokenAuthTokenDTO, TRevokeTokenAuthTokenDTO,
@@ -499,6 +500,51 @@ export const identityTokenAuthServiceFactory = ({
return { tokens, identityMembershipOrg }; 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 ({ const updateTokenAuthToken = async ({
tokenId, tokenId,
name, name,
@@ -642,6 +688,7 @@ export const identityTokenAuthServiceFactory = ({
revokeIdentityTokenAuth, revokeIdentityTokenAuth,
createTokenAuthToken, createTokenAuthToken,
getTokenAuthTokens, getTokenAuthTokens,
getTokenAuthTokenById,
updateTokenAuthToken, updateTokenAuthToken,
revokeTokenAuthToken revokeTokenAuthToken
}; };
@@ -40,6 +40,12 @@ export type TGetTokenAuthTokensDTO = {
isActorSuperAdmin?: boolean; isActorSuperAdmin?: boolean;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TGetTokenAuthTokenByIdDTO = {
tokenId: string;
identityId: string;
isActorSuperAdmin?: boolean;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateTokenAuthTokenDTO = { export type TUpdateTokenAuthTokenDTO = {
tokenId: string; tokenId: string;
name?: string; name?: string;
+1 -3
View File
@@ -765,9 +765,7 @@ export type CreateTokenIdentityTokenAuthDTO = {
export type CreateTokenIdentityTokenAuthRes = { export type CreateTokenIdentityTokenAuthRes = {
accessToken: string; accessToken: string;
tokenType: string; tokenData: IdentityAccessToken;
expiresIn: number;
accessTokenMaxTTL: number;
}; };
export type UpdateTokenIdentityTokenAuthDTO = { export type UpdateTokenIdentityTokenAuthDTO = {