From 4bb13d592f81b82b0365e8dbcab5d7efc22842ca Mon Sep 17 00:00:00 2001 From: = Date: Thu, 6 Nov 2025 11:44:46 +0530 Subject: [PATCH] feat: fixed metadata to be single origin and return in all scopes equally and some ui improvement --- .../v2/identity-project-membership-router.ts | 10 +- .../src/services/identity-v2/identity-dal.ts | 8 +- .../src/services/identity/identity-org-dal.ts | 8 +- .../src/services/identity/identity-service.ts | 2 - .../membership-identity-dal.ts | 9 +- .../api/projectIdentityMembership/queries.ts | 15 ++ .../src/hooks/api/projects/query-keys.tsx | 2 + .../IdentitySection/OrgIdentityModal.tsx | 128 ++++++++++-------- .../IdentityDetailsByIDPage.tsx | 17 ++- ...ntityProjectAdditionalPrivilegeSection.tsx | 5 +- .../ProjectIdentityDetailsSection.tsx | 77 ++++++----- 11 files changed, 165 insertions(+), 116 deletions(-) diff --git a/backend/src/server/routes/v2/identity-project-membership-router.ts b/backend/src/server/routes/v2/identity-project-membership-router.ts index 2ea027736..6608b0ff2 100644 --- a/backend/src/server/routes/v2/identity-project-membership-router.ts +++ b/backend/src/server/routes/v2/identity-project-membership-router.ts @@ -141,7 +141,15 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod }) ), identity: IdentitiesSchema.pick({ name: true, id: true, orgId: true, projectId: true }).extend({ - authMethods: z.array(z.string()) + authMethods: z.array(z.string()), + metadata: z + .object({ + id: z.string().trim().min(1), + key: z.string().trim().min(1), + value: z.string().trim().min(1) + }) + .array() + .optional() }) }) }) diff --git a/backend/src/services/identity-v2/identity-dal.ts b/backend/src/services/identity-v2/identity-dal.ts index 2a741202b..b150cc51e 100644 --- a/backend/src/services/identity-v2/identity-dal.ts +++ b/backend/src/services/identity-v2/identity-dal.ts @@ -13,9 +13,7 @@ export const identityV2DALFactory = (db: TDbClient) => { const doc = await db .replicaNode()(TableName.Identity) .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { - void queryBuilder - .on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`) - .andOn(`${TableName.Identity}.orgId`, `${TableName.IdentityMetadata}.orgId`); + void queryBuilder.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`); }) .leftJoin( TableName.IdentityUniversalAuth, @@ -134,9 +132,7 @@ export const identityV2DALFactory = (db: TDbClient) => { const query = db .replicaNode()(TableName.Identity) .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { - void queryBuilder - .on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`) - .andOn(`${TableName.Identity}.orgId`, `${TableName.IdentityMetadata}.orgId`); + void queryBuilder.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`); }) .where(`${TableName.Identity}.orgId`, scopeData.orgId) .where((qb) => { diff --git a/backend/src/services/identity/identity-org-dal.ts b/backend/src/services/identity/identity-org-dal.ts index 2da536c36..117195ca7 100644 --- a/backend/src/services/identity/identity-org-dal.ts +++ b/backend/src/services/identity/identity-org-dal.ts @@ -189,9 +189,7 @@ export const identityOrgDALFactory = (db: TDbClient) => { ) .leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`) .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { - void queryBuilder - .on(`paginatedIdentity.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`) - .andOn(`paginatedIdentity.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); + void queryBuilder.on(`paginatedIdentity.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`); }) .leftJoin( TableName.IdentityUniversalAuth, @@ -449,9 +447,7 @@ export const identityOrgDALFactory = (db: TDbClient) => { .join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`) .leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`) .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { - void queryBuilder - .on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`) - .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); + void queryBuilder.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`); }) .leftJoin( TableName.IdentityUniversalAuth, diff --git a/backend/src/services/identity/identity-service.ts b/backend/src/services/identity/identity-service.ts index f59930ece..d9baaa2f5 100644 --- a/backend/src/services/identity/identity-service.ts +++ b/backend/src/services/identity/identity-service.ts @@ -222,8 +222,6 @@ export const identityServiceFactory = ({ const identityDetails = await identityDAL.findById(id); - console.log("has project id", identityDetails); - if (identityDetails.projectId) { throw new BadRequestError({ message: `Identity is managed by project` }); } diff --git a/backend/src/services/membership-identity/membership-identity-dal.ts b/backend/src/services/membership-identity/membership-identity-dal.ts index f4c83643b..4a90e1edd 100644 --- a/backend/src/services/membership-identity/membership-identity-dal.ts +++ b/backend/src/services/membership-identity/membership-identity-dal.ts @@ -40,9 +40,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => { .join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`) .leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`) .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { - void queryBuilder - .on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`) - .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); + void queryBuilder.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`); }) .where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId) .where(`${TableName.Membership}.actorIdentityId`, identityId) @@ -216,7 +214,10 @@ export const membershipIdentityDALFactory = (db: TDbClient) => { ] }); - return data?.[0]; + const el = data?.[0]; + if (!el) return el; + + return { ...el, identity: { ...el.identity, metadata: el.metadata } }; } catch (error) { throw new DatabaseError({ error, name: "MembershipGetByIdentityId" }); } diff --git a/frontend/src/hooks/api/projectIdentityMembership/queries.ts b/frontend/src/hooks/api/projectIdentityMembership/queries.ts index bf86bd53c..80b68db6c 100644 --- a/frontend/src/hooks/api/projectIdentityMembership/queries.ts +++ b/frontend/src/hooks/api/projectIdentityMembership/queries.ts @@ -99,3 +99,18 @@ export const useGetProjectIdentityMembership = (projectId: string, identityId: s } }); }; + +export const useGetProjectIdentityMembershipV2 = (projectId: string, identityId: string) => { + return useQuery({ + enabled: Boolean(projectId && identityId), + queryKey: projectKeys.getProjectIdentityMembershipDetailsV2(projectId, identityId), + queryFn: async () => { + const { + data: { identityMembership } + } = await apiRequest.get<{ identityMembership: IdentityProjectMembership }>( + `/api/v2/projects/${projectId}/identity-memberships/${identityId}` + ); + return identityMembership; + } + }); +}; diff --git a/frontend/src/hooks/api/projects/query-keys.tsx b/frontend/src/hooks/api/projects/query-keys.tsx index 04f14f90d..48830f9e2 100644 --- a/frontend/src/hooks/api/projects/query-keys.tsx +++ b/frontend/src/hooks/api/projects/query-keys.tsx @@ -23,6 +23,8 @@ export const projectKeys = { [{ projectId }, "project-identity-memberships"] as const, getProjectIdentityMembershipDetails: (projectId: string, identityId: string) => [{ projectId, identityId }, "project-identity-membership-details"] as const, + getProjectIdentityMembershipDetailsV2: (projectId: string, identityId: string) => + [{ projectId, identityId }, "project-identity-membership-details"] as const, // allows invalidation using above key without knowing params getProjectIdentityMembershipsWithParams: ({ projectId, ...params }: TListProjectIdentitiesDTO) => [...projectKeys.getProjectIdentityMemberships(projectId), params] as const, diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/OrgIdentityModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/OrgIdentityModal.tsx index 02101c191..5582cb894 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/OrgIdentityModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/OrgIdentityModal.tsx @@ -242,67 +242,75 @@ export const OrgIdentityModal = ({ popUp, handlePopUpToggle }: Props) => { )} /> )} -
- -
-
- {metadataFormFields.fields.map(({ id: metadataFieldId }, i) => ( -
-
- {i === 0 && Key} - ( - - - - )} - /> -
-
- {i === 0 && ( - - )} - ( - - - - )} - /> -
- metadataFormFields.remove(i)} - > - - + {isOrgIdentity && ( + <> +
+
- ))} -
- -
-
+
+ {metadataFormFields.fields.map(({ id: metadataFieldId }, i) => ( +
+
+ {i === 0 && Key} + ( + + + + )} + /> +
+
+ {i === 0 && ( + + )} + ( + + + + )} + /> +
+ metadataFormFields.remove(i)} + > + + +
+ ))} +
+ +
+
+ + )}
- {identity && ( + {identity ? (
{ refetchIdentity={() => refetchIdentity()} />
+ ) : ( +
+
+ +
+
)}
{ diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/ProjectIdentityDetailsSection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/ProjectIdentityDetailsSection.tsx index 8ce45dcfd..cbe95ea1c 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/ProjectIdentityDetailsSection.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/ProjectIdentityDetailsSection.tsx @@ -34,10 +34,11 @@ import { ProjectIdentityModal } from "@app/pages/project/AccessControlPage/compo type Props = { identity: TProjectIdentity; + isOrgIdentity?: boolean; membership: IdentityProjectMembership; }; -export const ProjectIdentityDetailsSection = ({ identity, membership }: Props) => { +export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, membership }: Props) => { const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset({ initialState: "Copy ID to clipboard" }); @@ -76,21 +77,23 @@ export const ProjectIdentityDetailsSection = ({ identity, membership }: Props) =

Identity Details

- - - + {!isOrgIdentity && ( + + + + )}
-

Last Login Auth Method

+

Managed By

- {membership.lastLoginAuthMethod - ? identityAuthToNameMap[membership.lastLoginAuthMethod] - : "-"} -

-
-
-

Last Login Time

-

- {membership.lastLoginTime ? format(membership.lastLoginTime, "PPpp") : "-"} -

-
-
-

Delete Protection

-

- {identity.hasDeleteProtection ? "On" : "Off"} + {identity.projectId ? "Project" : "Organization"}

+ {!isOrgIdentity && ( + <> +
+

Last Login Auth Method

+

+ {membership.lastLoginAuthMethod + ? identityAuthToNameMap[membership.lastLoginAuthMethod] + : "-"} +

+
+
+

Last Login Time

+

+ {membership.lastLoginTime ? format(membership.lastLoginTime, "PPpp") : "-"} +

+
+
+

Delete Protection

+

+ {identity.hasDeleteProtection ? "On" : "Off"} +

+
+ + )}

Metadata

{identity?.metadata?.length ? (