From 1c121ec30dbc18ae599cf50cf9ef9c71a9db63bf Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 10 Apr 2025 17:48:41 +0400 Subject: [PATCH 1/2] feat(project-identity): get project identity by membership ID --- .../routes/v2/identity-project-router.ts | 52 +++++++++++++++++++ .../identity-project-service.ts | 40 +++++++++++++- .../identity-project-types.ts | 4 ++ .../IdentityDetailsByIDPage.tsx | 15 +++++- 4 files changed, 109 insertions(+), 2 deletions(-) diff --git a/backend/src/server/routes/v2/identity-project-router.ts b/backend/src/server/routes/v2/identity-project-router.ts index 18244068c..4205d9326 100644 --- a/backend/src/server/routes/v2/identity-project-router.ts +++ b/backend/src/server/routes/v2/identity-project-router.ts @@ -351,4 +351,56 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) return { identityMembership }; } }); + + server.route({ + method: "GET", + url: "/identity-memberships/:identityMembershipId", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + identityMembershipId: z.string().trim() + }), + response: { + 200: z.object({ + identityMembership: z.object({ + id: z.string(), + identityId: z.string(), + createdAt: z.date(), + updatedAt: z.date(), + roles: z.array( + z.object({ + id: z.string(), + role: z.string(), + customRoleId: z.string().optional().nullable(), + customRoleName: z.string().optional().nullable(), + customRoleSlug: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional() + }) + ), + identity: IdentitiesSchema.pick({ name: true, id: true }).extend({ + authMethods: z.array(z.string()) + }), + project: SanitizedProjectSchema.pick({ name: true, id: true }) + }) + }) + } + }, + handler: async (req) => { + const identityMembership = await server.services.identityProject.getProjectIdentityByMembershipId({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + identityMembershipId: req.params.identityMembershipId + }); + return { identityMembership }; + } + }); }; diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index ba63f7afd..8fea80e31 100644 --- a/backend/src/services/identity-project/identity-project-service.ts +++ b/backend/src/services/identity-project/identity-project-service.ts @@ -21,6 +21,7 @@ import { TCreateProjectIdentityDTO, TDeleteProjectIdentityDTO, TGetProjectIdentityByIdentityIdDTO, + TGetProjectIdentityByMembershipIdDTO, TListProjectIdentityDTO, TUpdateProjectIdentityDTO } from "./identity-project-types"; @@ -370,11 +371,48 @@ export const identityProjectServiceFactory = ({ return identityMembership; }; + const getProjectIdentityByMembershipId = async ({ + identityMembershipId, + actor, + actorId, + actorAuthMethod, + actorOrgId + }: TGetProjectIdentityByMembershipIdDTO) => { + const membership = await identityProjectDAL.findOne({ id: identityMembershipId }); + + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId: membership.projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.Any + }); + + if (!membership) { + throw new NotFoundError({ + message: `Project membership with ID '${identityMembershipId}' not found` + }); + } + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionIdentityActions.Read, + subject(ProjectPermissionSub.Identity, { identityId: membership.identityId }) + ); + + const [identityMembership] = await identityProjectDAL.findByProjectId(membership.projectId, { + identityId: membership.identityId + }); + + return identityMembership; + }; + return { createProjectIdentity, updateProjectIdentity, deleteProjectIdentity, listProjectIdentities, - getProjectIdentityByIdentityId + getProjectIdentityByIdentityId, + getProjectIdentityByMembershipId }; }; diff --git a/backend/src/services/identity-project/identity-project-types.ts b/backend/src/services/identity-project/identity-project-types.ts index 607fd4823..bc85ca398 100644 --- a/backend/src/services/identity-project/identity-project-types.ts +++ b/backend/src/services/identity-project/identity-project-types.ts @@ -52,6 +52,10 @@ export type TGetProjectIdentityByIdentityIdDTO = { identityId: string; } & TProjectPermission; +export type TGetProjectIdentityByMembershipIdDTO = { + identityMembershipId: string; +} & Omit; + export enum ProjectIdentityOrderBy { Name = "name" } diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index 80ae5dd2e..825a34a33 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -86,7 +86,20 @@ const Page = () => { title={identityMembershipDetails?.identity?.name} description={`Identity joined on ${identityMembershipDetails?.createdAt && formatRelative(new Date(identityMembershipDetails?.createdAt || ""), new Date())}`} > -
+
+ Date: Thu, 10 Apr 2025 17:53:17 +0400 Subject: [PATCH 2/2] Update identity-project-service.ts --- .../identity-project/identity-project-service.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index 8fea80e31..14df0cd4a 100644 --- a/backend/src/services/identity-project/identity-project-service.ts +++ b/backend/src/services/identity-project/identity-project-service.ts @@ -380,6 +380,12 @@ export const identityProjectServiceFactory = ({ }: TGetProjectIdentityByMembershipIdDTO) => { const membership = await identityProjectDAL.findOne({ id: identityMembershipId }); + if (!membership) { + throw new NotFoundError({ + message: `Project membership with ID '${identityMembershipId}' not found` + }); + } + const { permission } = await permissionService.getProjectPermission({ actor, actorId, @@ -389,12 +395,6 @@ export const identityProjectServiceFactory = ({ actionProjectType: ActionProjectType.Any }); - if (!membership) { - throw new NotFoundError({ - message: `Project membership with ID '${identityMembershipId}' not found` - }); - } - ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Read, subject(ProjectPermissionSub.Identity, { identityId: membership.identityId })