From 3d69d488351d287dfad87a651ff264b57b70dba7 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 11 Nov 2025 11:59:11 +0530 Subject: [PATCH] feat: corrected endpoints to be more restic --- ...ated-identity-project-membership-router.ts | 441 ++++++++++++++++++ .../v1/identity-project-membership-router.ts | 220 +++++---- backend/src/server/routes/v1/index.ts | 9 +- .../v2/identity-project-membership-router.ts | 176 ------- backend/src/server/routes/v2/index.ts | 8 - 5 files changed, 552 insertions(+), 302 deletions(-) create mode 100644 backend/src/server/routes/v1/deprecated-identity-project-membership-router.ts delete mode 100644 backend/src/server/routes/v2/identity-project-membership-router.ts diff --git a/backend/src/server/routes/v1/deprecated-identity-project-membership-router.ts b/backend/src/server/routes/v1/deprecated-identity-project-membership-router.ts new file mode 100644 index 000000000..2ac530d00 --- /dev/null +++ b/backend/src/server/routes/v1/deprecated-identity-project-membership-router.ts @@ -0,0 +1,441 @@ +import { z } from "zod"; + +import { + AccessScope, + IdentitiesSchema, + IdentityProjectMembershipsSchema, + ProjectMembershipRole, + ProjectUserMembershipRolesSchema, + TemporaryPermissionMode +} from "@app/db/schemas"; +import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs"; +import { BadRequestError } from "@app/lib/errors"; +import { ms } from "@app/lib/ms"; +import { OrderByDirection } from "@app/lib/types"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; +import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types"; + +import { SanitizedProjectSchema } from "../sanitizedSchemas"; + +export const registerDeprecatedIdentityProjectMembershipRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/:projectId/identity-memberships/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + description: "Create project identity membership", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim(), + identityId: z.string().trim() + }), + body: z.object({ + // @depreciated + role: z.string().trim().optional().default(ProjectMembershipRole.NoAccess), + roles: z + .array( + z.union([ + z.object({ + role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role), + isTemporary: z + .literal(false) + .default(false) + .describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role) + }), + z.object({ + role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role), + isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role), + temporaryMode: z + .nativeEnum(TemporaryPermissionMode) + .describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role), + temporaryRange: z + .string() + .refine((val) => ms(val) > 0, "Temporary range must be a positive number") + .describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role), + temporaryAccessStartTime: z + .string() + .datetime() + .describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role) + }) + ]) + ) + .describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.description) + .optional() + }), + response: { + 200: z.object({ + identityMembership: IdentityProjectMembershipsSchema + }) + } + }, + handler: async (req) => { + const { role, roles } = req.body; + if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" }); + + const { membership } = await server.services.membershipIdentity.createMembership({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + data: { + identityId: req.params.identityId, + roles: roles || [{ role, isTemporary: false }] + } + }); + + return { + identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId } + }; + } + }); + + server.route({ + method: "PATCH", + url: "/:projectId/identity-memberships/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + description: "Update project identity memberships", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.projectId), + identityId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.identityId) + }), + body: z.object({ + roles: z + .array( + z.union([ + z.object({ + role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role), + isTemporary: z + .literal(false) + .default(false) + .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary) + }), + z.object({ + role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role), + isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary), + temporaryMode: z + .nativeEnum(TemporaryPermissionMode) + .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode), + temporaryRange: z + .string() + .refine((val) => ms(val) > 0, "Temporary range must be a positive number") + .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryRange), + temporaryAccessStartTime: z + .string() + .datetime() + .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryAccessStartTime) + }) + ]) + ) + .min(1) + .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.description) + }), + response: { + 200: z.object({ + roles: ProjectUserMembershipRolesSchema.array() + }) + } + }, + handler: async (req) => { + const { membership } = await server.services.membershipIdentity.updateMembership({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + selector: { + identityId: req.params.identityId + }, + data: { + roles: req.body.roles + } + }); + + return { + roles: membership.roles.map((el) => ({ ...el, projectMembershipId: membership.id })) + }; + } + }); + + server.route({ + method: "DELETE", + url: "/:projectId/identity-memberships/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + description: "Delete project identity memberships", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.projectId), + identityId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.identityId) + }), + response: { + 200: z.object({ + identityMembership: IdentityProjectMembershipsSchema + }) + } + }, + handler: async (req) => { + const { membership } = await server.services.membershipIdentity.deleteMembership({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + selector: { + identityId: req.params.identityId + } + }); + + return { + identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId } + }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/identity-memberships", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + description: "Return project identity memberships", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId) + }), + querystring: z.object({ + offset: z.coerce + .number() + .min(0) + .default(0) + .describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.offset) + .optional(), + limit: z.coerce + .number() + .min(1) + .max(20000) // TODO: temp limit until combobox added to add identity to project modal, reduce once added + .default(100) + .describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.limit) + .optional(), + orderBy: z + .nativeEnum(ProjectIdentityOrderBy) + .default(ProjectIdentityOrderBy.Name) + .describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderBy) + .optional(), + orderDirection: z + .nativeEnum(OrderByDirection) + .default(OrderByDirection.ASC) + .describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderDirection) + .optional(), + search: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.search).optional() + }), + response: { + 200: z.object({ + identityMemberships: 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, projectId: true, orgId: true }).extend({ + authMethods: z.array(z.string()) + }), + project: SanitizedProjectSchema.pick({ name: true, id: true }) + }) + .array(), + totalCount: z.number() + }) + } + }, + handler: async (req) => { + const { identityMemberships, totalCount } = await server.services.identityProject.listProjectIdentities({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.projectId, + limit: req.query.limit, + offset: req.query.offset, + orderBy: req.query.orderBy, + orderDirection: req.query.orderDirection, + search: req.query.search + }); + + return { identityMemberships, totalCount }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/identity-memberships/:identityId", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + description: "Return project identity membership", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.projectId), + identityId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.identityId) + }), + 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() + }) + ), + lastLoginAuthMethod: z.string().nullable().optional(), + lastLoginTime: z.date().nullable().optional(), + identity: IdentitiesSchema.pick({ name: true, id: true, projectId: true, orgId: true }).extend({ + authMethods: z.array(z.string()) + }), + project: SanitizedProjectSchema.pick({ name: true, id: true }) + }) + }) + } + }, + handler: async (req) => { + const identityMembership = await server.services.identityProject.getProjectIdentityByIdentityId({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.projectId, + identityId: req.params.identityId + }); + return { identityMembership }; + } + }); + + server.route({ + method: "GET", + url: "/identity-memberships/:identityMembershipId", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + 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/server/routes/v1/identity-project-membership-router.ts b/backend/src/server/routes/v1/identity-project-membership-router.ts index 473a27f22..3ac43efe4 100644 --- a/backend/src/server/routes/v1/identity-project-membership-router.ts +++ b/backend/src/server/routes/v1/identity-project-membership-router.ts @@ -5,24 +5,20 @@ import { IdentitiesSchema, IdentityProjectMembershipsSchema, ProjectMembershipRole, - ProjectUserMembershipRolesSchema, TemporaryPermissionMode } from "@app/db/schemas"; -import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES, PROJECT_IDENTITY_MEMBERSHIP } from "@app/lib/api-docs"; +import { ApiDocsTags, PROJECT_IDENTITIES, PROJECT_IDENTITY_MEMBERSHIP } from "@app/lib/api-docs"; import { BadRequestError } from "@app/lib/errors"; import { ms } from "@app/lib/ms"; -import { OrderByDirection } from "@app/lib/types"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types"; - -import { SanitizedProjectSchema } from "../sanitizedSchemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; export const registerIdentityProjectMembershipRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", - url: "/:projectId/identity-memberships/:identityId", + url: "/identities/:identityId", config: { rateLimit: writeLimit }, @@ -96,6 +92,19 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod } }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: req.params.projectId, + event: { + type: EventType.CREATE_IDENTITY_PROJECT_MEMBERSHIP, + metadata: { + identityId: req.params.identityId, + roles: req.body.roles + } + } + }); + return { identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId } }; @@ -104,7 +113,7 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod server.route({ method: "PATCH", - url: "/:projectId/identity-memberships/:identityId", + url: "/identities/:identityId", config: { rateLimit: writeLimit }, @@ -155,7 +164,7 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod }), response: { 200: z.object({ - roles: ProjectUserMembershipRolesSchema.array() + identityMembership: IdentityProjectMembershipsSchema }) } }, @@ -175,15 +184,28 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod } }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: req.params.projectId, + event: { + type: EventType.UPDATE_IDENTITY_PROJECT_MEMBERSHIP, + metadata: { + identityId: req.params.identityId, + roles: req.body.roles + } + } + }); + return { - roles: membership.roles.map((el) => ({ ...el, projectMembershipId: membership.id })) + identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId } }; } }); server.route({ method: "DELETE", - url: "/:projectId/identity-memberships/:identityId", + url: "/identities/:identityId", config: { rateLimit: writeLimit }, @@ -220,6 +242,18 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod } }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: req.params.projectId, + event: { + type: EventType.DELETE_IDENTITY_PROJECT_MEMBERSHIP, + metadata: { + identityId: req.params.identityId + } + } + }); + return { identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId } }; @@ -228,48 +262,47 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod server.route({ method: "GET", - url: "/:projectId/identity-memberships", + url: "/identities", config: { rateLimit: readLimit }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { hide: false, - tags: [ApiDocsTags.ProjectIdentities], - description: "Return project identity memberships", + tags: [ApiDocsTags.IdentityProjectMembership], + description: "List project identity memberships", security: [ { bearerAuth: [] } ], params: z.object({ - projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId) + projectId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_IDENTITY_MEMBERSHIPS.projectId) }), querystring: z.object({ offset: z.coerce .number() .min(0) .default(0) - .describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.offset) + .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_IDENTITY_MEMBERSHIPS.offset) .optional(), limit: z.coerce .number() .min(1) - .max(20000) // TODO: temp limit until combobox added to add identity to project modal, reduce once added - .default(100) - .describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.limit) + .max(100) + .default(20) + .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_IDENTITY_MEMBERSHIPS.limit) .optional(), - orderBy: z - .nativeEnum(ProjectIdentityOrderBy) - .default(ProjectIdentityOrderBy.Name) - .describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderBy) + identityName: z + .string() + .trim() + .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_IDENTITY_MEMBERSHIPS.identityName) .optional(), - orderDirection: z - .nativeEnum(OrderByDirection) - .default(OrderByDirection.ASC) - .describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderDirection) - .optional(), - search: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.search).optional() + roles: z + .string() + .transform((val) => val.split(",").map((role) => role.trim())) + .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_IDENTITY_MEMBERSHIPS.roles) + .optional() }), response: { 200: z.object({ @@ -293,10 +326,7 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod temporaryAccessEndTime: z.date().nullable().optional() }) ), - identity: IdentitiesSchema.pick({ name: true, id: true, projectId: true, orgId: true }).extend({ - authMethods: z.array(z.string()) - }), - project: SanitizedProjectSchema.pick({ name: true, id: true }) + identity: IdentitiesSchema.pick({ name: true, id: true, orgId: true, projectId: true }) }) .array(), totalCount: z.number() @@ -304,17 +334,19 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod } }, handler: async (req) => { - const { identityMemberships, totalCount } = await server.services.identityProject.listProjectIdentities({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.params.projectId, - limit: req.query.limit, - offset: req.query.offset, - orderBy: req.query.orderBy, - orderDirection: req.query.orderDirection, - search: req.query.search + const { data: identityMemberships, totalCount } = await server.services.membershipIdentity.listMemberships({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + data: { + offset: req.query.offset, + limit: req.query.limit, + identityName: req.query.identityName, + roles: req.query.roles + } }); return { identityMemberships, totalCount }; @@ -323,29 +355,28 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod server.route({ method: "GET", - url: "/:projectId/identity-memberships/:identityId", + url: "/identities/:identityId", config: { rateLimit: readLimit }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { hide: false, - tags: [ApiDocsTags.ProjectIdentities], - description: "Return project identity membership", + tags: [ApiDocsTags.IdentityProjectMembership], + description: "Get project identity membership by identity ID", security: [ { bearerAuth: [] } ], params: z.object({ - projectId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.projectId), - identityId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.identityId) + projectId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.GET_IDENTITY_MEMBERSHIP_BY_ID.projectId), + identityId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.GET_IDENTITY_MEMBERSHIP_BY_ID.identityId) }), response: { 200: z.object({ identityMembership: z.object({ id: z.string(), - identityId: z.string(), createdAt: z.date(), updatedAt: z.date(), roles: z.array( @@ -364,84 +395,41 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod ), lastLoginAuthMethod: z.string().nullable().optional(), lastLoginTime: z.date().nullable().optional(), - identity: IdentitiesSchema.pick({ name: true, id: true, projectId: true, orgId: true }).extend({ - authMethods: z.array(z.string()) - }), - project: SanitizedProjectSchema.pick({ name: true, id: true }) + identity: IdentitiesSchema.pick({ name: true, id: true, orgId: true, projectId: true }).extend({ + 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() + }) }) }) } }, handler: async (req) => { - const identityMembership = await server.services.identityProject.getProjectIdentityByIdentityId({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.params.projectId, - identityId: req.params.identityId + const identityMembership = await server.services.membershipIdentity.getMembershipByIdentityId({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + selector: { + identityId: req.params.identityId + } }); + return { identityMembership }; } }); server.route({ method: "GET", - url: "/identity-memberships/:identityMembershipId", - config: { - rateLimit: readLimit - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), - schema: { - hide: false, - tags: [ApiDocsTags.ProjectIdentities], - 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 }; - } - }); - - server.route({ - method: "GET", - url: "/:projectId/available-identities", + url: "/available-identities", config: { rateLimit: readLimit }, diff --git a/backend/src/server/routes/v1/index.ts b/backend/src/server/routes/v1/index.ts index 29327d767..4fb7a5d93 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -34,7 +34,7 @@ import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router"; import { registerIdentityOciAuthRouter } from "./identity-oci-auth-router"; import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router"; import { registerIdentityOrgMembershipRouter } from "./identity-org-membership-router"; -import { registerIdentityProjectMembershipRouter } from "./identity-project-membership-router"; +import { registerDeprecatedIdentityProjectMembershipRouter } from "./deprecated-identity-project-membership-router"; import { registerIdentityRouter } from "./identity-router"; import { registerIdentityTlsCertAuthRouter } from "./identity-tls-cert-auth-router"; import { registerIdentityTokenAuthRouter } from "./identity-token-auth-router"; @@ -69,6 +69,7 @@ import { registerUserEngagementRouter } from "./user-engagement-router"; import { registerUserRouter } from "./user-router"; import { registerWebhookRouter } from "./webhook-router"; import { registerWorkflowIntegrationRouter } from "./workflow-integration-router"; +import { registerIdentityProjectMembershipRouter } from "./identity-project-membership-router"; export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerSsoRouter, { prefix: "/sso" }); @@ -138,11 +139,15 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await projectRouter.register(registerProjectEnvRouter); await projectRouter.register(registerSecretTagRouter); await projectRouter.register(registerGroupProjectRouter); - await projectRouter.register(registerIdentityProjectMembershipRouter); + await projectRouter.register(registerDeprecatedIdentityProjectMembershipRouter); }, { prefix: "/projects" } ); + await server.register(registerIdentityProjectMembershipRouter, { + prefix: "/projects/:projectId/memberships" + }); + await server.register( async (pkiRouter) => { await pkiRouter.register(registerCaRouter, { prefix: "/ca" }); diff --git a/backend/src/server/routes/v2/identity-project-membership-router.ts b/backend/src/server/routes/v2/identity-project-membership-router.ts deleted file mode 100644 index 5f85d5d2d..000000000 --- a/backend/src/server/routes/v2/identity-project-membership-router.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { z } from "zod"; - -import { AccessScope, IdentitiesSchema } from "@app/db/schemas"; -import { ApiDocsTags, PROJECT_IDENTITY_MEMBERSHIP } from "@app/lib/api-docs"; -import { readLimit } from "@app/server/config/rateLimiter"; -import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { AuthMode } from "@app/services/auth/auth-type"; - -export const registerIdentityProjectMembershipRouter = async (server: FastifyZodProvider) => { - server.route({ - method: "GET", - url: "/:projectId/identity-memberships", - config: { - rateLimit: readLimit - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), - schema: { - hide: false, - tags: [ApiDocsTags.IdentityProjectMembership], - description: "List project identity memberships", - security: [ - { - bearerAuth: [] - } - ], - params: z.object({ - projectId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_IDENTITY_MEMBERSHIPS.projectId) - }), - querystring: z.object({ - offset: z.coerce - .number() - .min(0) - .default(0) - .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_IDENTITY_MEMBERSHIPS.offset) - .optional(), - limit: z.coerce - .number() - .min(1) - .max(100) - .default(20) - .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_IDENTITY_MEMBERSHIPS.limit) - .optional(), - identityName: z - .string() - .trim() - .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_IDENTITY_MEMBERSHIPS.identityName) - .optional(), - roles: z - .string() - .transform((val) => val.split(",").map((role) => role.trim())) - .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_IDENTITY_MEMBERSHIPS.roles) - .optional() - }), - response: { - 200: z.object({ - identityMemberships: 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, orgId: true, projectId: true }) - }) - .array(), - totalCount: z.number() - }) - } - }, - handler: async (req) => { - const { data: identityMemberships, totalCount } = await server.services.membershipIdentity.listMemberships({ - permission: req.permission, - scopeData: { - scope: AccessScope.Project, - orgId: req.permission.orgId, - projectId: req.params.projectId - }, - data: { - offset: req.query.offset, - limit: req.query.limit, - identityName: req.query.identityName, - roles: req.query.roles - } - }); - - return { identityMemberships, totalCount }; - } - }); - - server.route({ - method: "GET", - url: "/:projectId/identity-memberships/:identityId", - config: { - rateLimit: readLimit - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), - schema: { - hide: false, - tags: [ApiDocsTags.IdentityProjectMembership], - description: "Get project identity membership by identity ID", - security: [ - { - bearerAuth: [] - } - ], - params: z.object({ - projectId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.GET_IDENTITY_MEMBERSHIP_BY_ID.projectId), - identityId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.GET_IDENTITY_MEMBERSHIP_BY_ID.identityId) - }), - response: { - 200: z.object({ - identityMembership: z.object({ - id: 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() - }) - ), - lastLoginAuthMethod: z.string().nullable().optional(), - lastLoginTime: z.date().nullable().optional(), - identity: IdentitiesSchema.pick({ name: true, id: true, orgId: true, projectId: true }).extend({ - 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() - }) - }) - }) - } - }, - handler: async (req) => { - const identityMembership = await server.services.membershipIdentity.getMembershipByIdentityId({ - permission: req.permission, - scopeData: { - scope: AccessScope.Project, - orgId: req.permission.orgId, - projectId: req.params.projectId - }, - selector: { - identityId: req.params.identityId - } - }); - - return { identityMembership }; - } - }); -}; diff --git a/backend/src/server/routes/v2/index.ts b/backend/src/server/routes/v2/index.ts index b13a67457..d3d91a3ba 100644 --- a/backend/src/server/routes/v2/index.ts +++ b/backend/src/server/routes/v2/index.ts @@ -5,7 +5,6 @@ import { registerDeprecatedIdentityProjectRouter } from "./deprecated-identity-p import { registerDeprecatedProjectMembershipRouter } from "./deprecated-project-membership-router"; import { registerDeprecatedProjectRouter } from "./deprecated-project-router"; import { registerIdentityOrgRouter } from "./identity-org-router"; -import { registerIdentityProjectMembershipRouter } from "./identity-project-membership-router"; import { registerMfaRouter } from "./mfa-router"; import { registerOrgRouter } from "./organization-router"; import { registerPasswordRouter } from "./password-router"; @@ -22,13 +21,6 @@ export const registerV2Routes = async (server: FastifyZodProvider) => { await server.register(registerServiceTokenRouter, { prefix: "/service-token" }); await server.register(registerPasswordRouter, { prefix: "/password" }); - await server.register( - async (projectRouter) => { - await projectRouter.register(registerIdentityProjectMembershipRouter); - }, - { prefix: "/projects" } - ); - await server.register(registerCertificateTemplatesV2Router, { prefix: "/certificate-templates" }); await server.register(