diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index d511187d0..6ef775f90 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -91,6 +91,7 @@ import { TIdentityProjectServiceFactory } from "@app/services/identity-project/i import { TIdentityTlsCertAuthServiceFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-types"; import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service"; import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; +import { TScopedIdentityV2ServiceFactory } from "@app/services/identity-v2/identity-service"; import { TIntegrationServiceFactory } from "@app/services/integration/integration-service"; import { TIntegrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service"; import { TMembershipGroupServiceFactory } from "@app/services/membership-group/membership-group-service"; @@ -258,7 +259,8 @@ declare module "fastify" { integrationAuth: TIntegrationAuthServiceFactory; webhook: TWebhookServiceFactory; serviceToken: TServiceTokenServiceFactory; - identity: TIdentityServiceFactory; + identityV1: TIdentityServiceFactory; + identityV2: TScopedIdentityV2ServiceFactory; identityAccessToken: TIdentityAccessTokenServiceFactory; identityProject: TIdentityProjectServiceFactory; identityTokenAuth: TIdentityTokenAuthServiceFactory; diff --git a/backend/src/db/migrations/20251023100246_project-identity.ts b/backend/src/db/migrations/20251023100246_project-identity.ts index 99bd3edb8..4a54c6c43 100644 --- a/backend/src/db/migrations/20251023100246_project-identity.ts +++ b/backend/src/db/migrations/20251023100246_project-identity.ts @@ -1,4 +1,5 @@ import { Knex } from "knex"; + import { TableName } from "../schemas"; export async function up(knex: Knex): Promise { 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 ba409a4fb..f91692a14 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -159,9 +159,22 @@ export enum EventType { DELETE_TRUSTED_IP = "delete-trusted-ip", CREATE_SERVICE_TOKEN = "create-service-token", // v2 DELETE_SERVICE_TOKEN = "delete-service-token", // v2 + + CREATE_SUB_ORGANIZATION = "create-sub-organization", + UPDATE_SUB_ORGANIZATION = "update-sub-organization", + CREATE_IDENTITY = "create-identity", UPDATE_IDENTITY = "update-identity", DELETE_IDENTITY = "delete-identity", + + CREATE_IDENTITY_ORG_MEMBERSHIP = "create-identity-org-membership", + UPDATE_IDENTITY_ORG_MEMBERSHIP = "update-identity-org-membership", + DELETE_IDENTITY_ORG_MEMBERSHIP = "delete-identity-org-membership", + + CREATE_IDENTITY_PROJECT_MEMBERSHIP = "create-identity-project-membership", + UPDATE_IDENTITY_PROJECT_MEMBERSHIP = "update-identity-project-membership", + DELETE_IDENTITY_PROJECT_MEMBERSHIP = "delete-identity-project-membership", + MACHINE_IDENTITY_AUTH_TEMPLATE_CREATE = "machine-identity-auth-template-create", MACHINE_IDENTITY_AUTH_TEMPLATE_UPDATE = "machine-identity-auth-template-update", MACHINE_IDENTITY_AUTH_TEMPLATE_DELETE = "machine-identity-auth-template-delete", @@ -174,9 +187,6 @@ export enum EventType { UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth", GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth", - CREATE_SUB_ORGANIZATION = "create-sub-organization", - UPDATE_SUB_ORGANIZATION = "update-sub-organization", - ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth", UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth", GET_IDENTITY_TOKEN_AUTH = "get-identity-token-auth", @@ -891,6 +901,7 @@ interface CreateIdentityEvent { identityId: string; name: string; hasDeleteProtection: boolean; + metadata?: { key: string; value: string }[]; }; } @@ -900,6 +911,7 @@ interface UpdateIdentityEvent { identityId: string; name?: string; hasDeleteProtection?: boolean; + metadata?: { key: string; value: string }[]; }; } @@ -1501,6 +1513,52 @@ interface ClearIdentityLdapAuthLockoutsEvent { }; } +interface CreateIdentityOrgMembershipEvent { + type: EventType.CREATE_IDENTITY_ORG_MEMBERSHIP; + metadata: { + identityId: string; + roles: unknown; + }; +} + +interface UpdateIdentityOrgMembershipEvent { + type: EventType.UPDATE_IDENTITY_ORG_MEMBERSHIP; + metadata: { + identityId: string; + roles?: unknown; + }; +} + +interface DeleteIdentityOrgMembershipEvent { + type: EventType.DELETE_IDENTITY_ORG_MEMBERSHIP; + metadata: { + identityId: string; + }; +} + +interface CreateIdentityProjectMembershipEvent { + type: EventType.CREATE_IDENTITY_PROJECT_MEMBERSHIP; + metadata: { + identityId: string; + roles: unknown; + }; +} + +interface UpdateIdentityProjectMembershipEvent { + type: EventType.UPDATE_IDENTITY_PROJECT_MEMBERSHIP; + metadata: { + identityId: string; + roles?: unknown; + }; +} + +interface DeleteIdentityProjectMembershipEvent { + type: EventType.DELETE_IDENTITY_PROJECT_MEMBERSHIP; + metadata: { + identityId: string; + }; +} + interface LoginIdentityOidcAuthEvent { type: EventType.LOGIN_IDENTITY_OIDC_AUTH; metadata: { @@ -4197,6 +4255,12 @@ export type Event = | GetIdentityLdapAuthEvent | RevokeIdentityLdapAuthEvent | ClearIdentityLdapAuthLockoutsEvent + | CreateIdentityOrgMembershipEvent + | UpdateIdentityOrgMembershipEvent + | DeleteIdentityOrgMembershipEvent + | CreateIdentityProjectMembershipEvent + | UpdateIdentityProjectMembershipEvent + | DeleteIdentityProjectMembershipEvent | CreateEnvironmentEvent | GetEnvironmentEvent | UpdateEnvironmentEvent diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 82f67778e..b837026e6 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -39,7 +39,7 @@ export enum ApiDocsTags { ProjectUsers = "Project Users", ProjectGroups = "Project Groups", ProjectIdentities = "Project Identities", - ProjectIdentityMembership = "Project Identity Membership", + IdentityProjectMembership = "Project Identity Membership", ProjectRoles = "Project Roles", ProjectTemplates = "Project Templates", Environments = "Environments", @@ -124,13 +124,15 @@ export const IDENTITIES = { name: "The name of the identity to create.", organizationId: "The organization ID to which the identity belongs.", role: "The role of the identity. Possible values are 'no-access', 'member', and 'admin'.", - hasDeleteProtection: "Prevents deletion of the identity when enabled." + hasDeleteProtection: "Prevents deletion of the identity when enabled.", + metadata: "An optional array of key-value pairs to attach to the identity." }, UPDATE: { identityId: "The ID of the machine identity to update.", name: "The new name of the identity.", role: "The new role of the identity.", - hasDeleteProtection: "Prevents deletion of the identity when enabled." + hasDeleteProtection: "Prevents deletion of the identity when enabled.", + metadata: "An optional array of key-value pairs to attach to the identity." }, DELETE: { identityId: "The ID of the machine identity to delete." @@ -140,7 +142,10 @@ export const IDENTITIES = { orgId: "The ID of the org of the identity" }, LIST: { - orgId: "The ID of the organization to list identities." + orgId: "The ID of the organization to list identities.", + search: "The text string that identity names will be filtered by.", + offset: "The offset to start from. If you enter 10, it will start from the 10th identity.", + limit: "The number of identities to return." }, SEARCH: { search: { diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 067d296a3..3bfaa82ef 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -241,6 +241,8 @@ import { identityTokenAuthServiceFactory } from "@app/services/identity-token-au import { identityUaClientSecretDALFactory } from "@app/services/identity-ua/identity-ua-client-secret-dal"; import { identityUaDALFactory } from "@app/services/identity-ua/identity-ua-dal"; import { identityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; +import { identityV2DALFactory } from "@app/services/identity-v2/identity-dal"; +import { identityV2ServiceFactory } from "@app/services/identity-v2/identity-service"; import { integrationDALFactory } from "@app/services/integration/integration-dal"; import { integrationServiceFactory } from "@app/services/integration/integration-service"; import { integrationAuthDALFactory } from "@app/services/integration-auth/integration-auth-dal"; @@ -445,6 +447,7 @@ export const registerRoutes = async ( const serviceTokenDAL = serviceTokenDALFactory(db); const identityDAL = identityDALFactory(db); + const identityV2DAL = identityV2DALFactory(db); const identityMetadataDAL = identityMetadataDALFactory(db); const identityAccessTokenDAL = identityAccessTokenDALFactory(db); const identityOrgMembershipDAL = identityOrgDALFactory(db); @@ -1656,6 +1659,16 @@ export const registerRoutes = async ( membershipIdentityDAL, membershipRoleDAL }); + + const identityV2Service = identityV2ServiceFactory({ + membershipIdentityDAL, + membershipRoleDAL, + identityMetadataDAL, + licenseService, + permissionService, + identityDAL: identityV2DAL + }); + const identityProjectService = identityProjectServiceFactory({ identityProjectDAL, membershipIdentityDAL, @@ -2459,7 +2472,8 @@ export const registerRoutes = async ( integrationAuth: integrationAuthService, webhook: webhookService, serviceToken: serviceTokenService, - identity: identityService, + identityV1: identityService, + identityV2: identityV2Service, identityAuthTemplate: identityAuthTemplateService, identityAccessToken: identityAccessTokenService, identityTokenAuth: identityTokenAuthService, diff --git a/backend/src/server/routes/v1/identity-org-membership-router.ts b/backend/src/server/routes/v1/identity-org-membership-router.ts index c50e0d423..de57280a0 100644 --- a/backend/src/server/routes/v1/identity-org-membership-router.ts +++ b/backend/src/server/routes/v1/identity-org-membership-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { AccessScope, IdentitiesSchema, MembershipRolesSchema, TemporaryPermissionMode } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ApiDocsTags, ORG_IDENTITY_MEMBERSHIP } from "@app/lib/api-docs"; import { ms } from "@app/lib/ms"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; @@ -15,7 +16,7 @@ const sanitizedOrgIdentityMembershipSchema = z.object({ updatedAt: z.date() }); -export const registerOrgIdentityMembershipRouter = async (server: FastifyZodProvider) => { +export const registerIdentityOrgMembershipRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/identity-memberships/:identityId", @@ -87,6 +88,18 @@ export const registerOrgIdentityMembershipRouter = async (server: FastifyZodProv } }); + await server.services.auditLog.createAuditLog({ + orgId: req.permission.orgId, + ...req.auditLogInfo, + event: { + type: EventType.CREATE_IDENTITY_ORG_MEMBERSHIP, + metadata: { + identityId: req.params.identityId, + roles: req.body.roles + } + } + }); + return { identityMembership: { ...membership, identityId: req.params.identityId, orgId: req.permission.orgId } }; @@ -166,6 +179,18 @@ export const registerOrgIdentityMembershipRouter = async (server: FastifyZodProv } }); + await server.services.auditLog.createAuditLog({ + orgId: req.permission.orgId, + ...req.auditLogInfo, + event: { + type: EventType.UPDATE_IDENTITY_ORG_MEMBERSHIP, + metadata: { + identityId: req.params.identityId, + roles: req.body.roles + } + } + }); + return { roles: membership.roles.map((el) => ({ ...el, membershipId: membership.id })) }; @@ -209,6 +234,17 @@ export const registerOrgIdentityMembershipRouter = async (server: FastifyZodProv } }); + await server.services.auditLog.createAuditLog({ + orgId: req.permission.orgId, + ...req.auditLogInfo, + event: { + type: EventType.DELETE_IDENTITY_ORG_MEMBERSHIP, + metadata: { + identityId: req.params.identityId + } + } + }); + return { identityMembership: { ...membership, identityId: req.params.identityId, orgId: req.permission.orgId } }; diff --git a/backend/src/server/routes/v1/identity-project-membership-router.ts b/backend/src/server/routes/v1/identity-project-membership-router.ts deleted file mode 100644 index de6e44a95..000000000 --- a/backend/src/server/routes/v1/identity-project-membership-router.ts +++ /dev/null @@ -1,438 +0,0 @@ -import { z } from "zod"; - -import { AccessScope, IdentitiesSchema, MembershipRolesSchema, TemporaryPermissionMode } from "@app/db/schemas"; -import { ApiDocsTags, PROJECT_IDENTITY_MEMBERSHIP } from "@app/lib/api-docs"; -import { ms } from "@app/lib/ms"; -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"; - -const sanitizedProjectIdentityMembershipSchema = z.object({ - id: z.string().uuid(), - projectId: z.string(), - identityId: z.string().uuid(), - createdAt: z.date(), - updatedAt: z.date() -}); - -export const registerProjectIdentityMembershipRouter = 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.ProjectIdentityMembership], - description: "Create project identity membership", - security: [ - { - bearerAuth: [] - } - ], - params: z.object({ - projectId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.CREATE_IDENTITY_MEMBERSHIP.projectId), - identityId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.CREATE_IDENTITY_MEMBERSHIP.identityId) - }), - body: z.object({ - roles: z - .array( - z.union([ - z.object({ - role: z.string().describe(PROJECT_IDENTITY_MEMBERSHIP.CREATE_IDENTITY_MEMBERSHIP.roles.role), - isTemporary: z - .literal(false) - .default(false) - .describe(PROJECT_IDENTITY_MEMBERSHIP.CREATE_IDENTITY_MEMBERSHIP.roles.isTemporary) - }), - z.object({ - role: z.string().describe(PROJECT_IDENTITY_MEMBERSHIP.CREATE_IDENTITY_MEMBERSHIP.roles.role), - isTemporary: z - .literal(true) - .describe(PROJECT_IDENTITY_MEMBERSHIP.CREATE_IDENTITY_MEMBERSHIP.roles.isTemporary), - temporaryMode: z - .nativeEnum(TemporaryPermissionMode) - .describe(PROJECT_IDENTITY_MEMBERSHIP.CREATE_IDENTITY_MEMBERSHIP.roles.temporaryMode), - temporaryRange: z - .string() - .refine((val) => ms(val) > 0, "Temporary range must be a positive number") - .describe(PROJECT_IDENTITY_MEMBERSHIP.CREATE_IDENTITY_MEMBERSHIP.roles.temporaryRange), - temporaryAccessStartTime: z - .string() - .datetime() - .describe(PROJECT_IDENTITY_MEMBERSHIP.CREATE_IDENTITY_MEMBERSHIP.roles.temporaryAccessStartTime) - }) - ]) - ) - .describe(PROJECT_IDENTITY_MEMBERSHIP.CREATE_IDENTITY_MEMBERSHIP.roles.description) - .min(1) - }), - response: { - 200: z.object({ - identityMembership: sanitizedProjectIdentityMembershipSchema - }) - } - }, - handler: async (req) => { - 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: req.body.roles - } - }); - - 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.ProjectIdentityMembership], - description: "Update project identity membership", - security: [ - { - bearerAuth: [] - } - ], - params: z.object({ - projectId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.UPDATE_IDENTITY_MEMBERSHIP.projectId), - identityId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.UPDATE_IDENTITY_MEMBERSHIP.identityId) - }), - body: z.object({ - roles: z - .array( - z.union([ - z.object({ - role: z.string().describe(PROJECT_IDENTITY_MEMBERSHIP.UPDATE_IDENTITY_MEMBERSHIP.roles.role), - isTemporary: z - .literal(false) - .default(false) - .describe(PROJECT_IDENTITY_MEMBERSHIP.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary) - }), - z.object({ - role: z.string().describe(PROJECT_IDENTITY_MEMBERSHIP.UPDATE_IDENTITY_MEMBERSHIP.roles.role), - isTemporary: z - .literal(true) - .describe(PROJECT_IDENTITY_MEMBERSHIP.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary), - temporaryMode: z - .nativeEnum(TemporaryPermissionMode) - .describe(PROJECT_IDENTITY_MEMBERSHIP.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode), - temporaryRange: z - .string() - .refine((val) => ms(val) > 0, "Temporary range must be a positive number") - .describe(PROJECT_IDENTITY_MEMBERSHIP.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryRange), - temporaryAccessStartTime: z - .string() - .datetime() - .describe(PROJECT_IDENTITY_MEMBERSHIP.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryAccessStartTime) - }) - ]) - ) - .min(1) - .describe(PROJECT_IDENTITY_MEMBERSHIP.UPDATE_IDENTITY_MEMBERSHIP.roles.description) - }), - response: { - 200: z.object({ - roles: MembershipRolesSchema.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, membershipId: 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.ProjectIdentityMembership], - description: "Delete project identity membership", - security: [ - { - bearerAuth: [] - } - ], - params: z.object({ - projectId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.DELETE_IDENTITY_MEMBERSHIP.projectId), - identityId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.DELETE_IDENTITY_MEMBERSHIP.identityId) - }), - response: { - 200: z.object({ - identityMembership: sanitizedProjectIdentityMembershipSchema - }) - } - }, - 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.ProjectIdentityMembership], - 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.ProjectIdentityMembership], - 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() - }) - ), - identity: IdentitiesSchema.pick({ name: true, id: true, orgId: true, projectId: true }).extend({ - authMethods: z.array(z.string()) - }) - }) - }) - } - }, - 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 }; - } - }); - - server.route({ - method: "GET", - url: "/:projectId/available-identities", - config: { - rateLimit: readLimit - }, - onRequest: verifyAuth([AuthMode.JWT]), - schema: { - hide: false, - tags: [ApiDocsTags.ProjectIdentityMembership], - description: "List available identities for project membership", - security: [ - { - bearerAuth: [] - } - ], - params: z.object({ - projectId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_AVAILABLE_IDENTITIES.projectId) - }), - querystring: z.object({ - offset: z.coerce - .number() - .min(0) - .default(0) - .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_AVAILABLE_IDENTITIES.offset) - .optional(), - limit: z.coerce - .number() - .min(1) - .max(100) - .default(20) - .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_AVAILABLE_IDENTITIES.limit) - .optional() - }), - response: { - 200: z.object({ - identities: IdentitiesSchema.pick({ id: true, name: true }).array() - }) - } - }, - handler: async (req) => { - const { identities } = await server.services.membershipIdentity.listAvailableIdentities({ - permission: req.permission, - scopeData: { - scope: AccessScope.Project, - orgId: req.permission.orgId, - projectId: req.params.projectId - }, - data: { - offset: req.query.offset, - limit: req.query.limit - } - }); - - return { identities }; - } - }); -}; diff --git a/backend/src/server/routes/v1/identity-project-router.ts b/backend/src/server/routes/v1/identity-project-router.ts index fd39c7efe..06fe6a14a 100644 --- a/backend/src/server/routes/v1/identity-project-router.ts +++ b/backend/src/server/routes/v1/identity-project-router.ts @@ -8,7 +8,7 @@ import { ProjectUserMembershipRolesSchema, TemporaryPermissionMode } from "@app/db/schemas"; -import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs"; +import { ApiDocsTags, ORGANIZATIONS, 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"; @@ -19,7 +19,7 @@ import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity- import { SanitizedProjectSchema } from "../sanitizedSchemas"; -export const registerIdentityProjectRouter = async (server: FastifyZodProvider) => { +export const registerIdentityProjectMembershipRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/:projectId/identity-memberships/:identityId", @@ -436,4 +436,62 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) return { identityMembership }; } }); + + server.route({ + method: "GET", + url: "/:projectId/available-identities", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + hide: false, + tags: [ApiDocsTags.IdentityProjectMembership], + description: "List available identities for project membership", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_AVAILABLE_IDENTITIES.projectId) + }), + querystring: z.object({ + offset: z.coerce + .number() + .min(0) + .default(0) + .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_AVAILABLE_IDENTITIES.offset) + .optional(), + limit: z.coerce + .number() + .min(1) + .max(100) + .default(20) + .describe(PROJECT_IDENTITY_MEMBERSHIP.LIST_AVAILABLE_IDENTITIES.limit) + .optional() + }), + response: { + 200: z.object({ + identities: IdentitiesSchema.pick({ id: true, name: true }).array() + }) + } + }, + handler: async (req) => { + const { identities } = await server.services.membershipIdentity.listAvailableIdentities({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + data: { + offset: req.query.offset, + limit: req.query.limit + } + }); + + return { identities }; + } + }); }; diff --git a/backend/src/server/routes/v1/identity-router.ts b/backend/src/server/routes/v1/identity-router.ts index f8e6c78ee..ba8506be4 100644 --- a/backend/src/server/routes/v1/identity-router.ts +++ b/backend/src/server/routes/v1/identity-router.ts @@ -60,7 +60,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const identity = await server.services.identity.createIdentity({ + const identity = await server.services.identityV1.createIdentity({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -136,7 +136,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const identity = await server.services.identity.updateIdentity({ + const identity = await server.services.identityV1.updateIdentity({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -189,7 +189,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const identity = await server.services.identity.deleteIdentity({ + const identity = await server.services.identityV1.deleteIdentity({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -258,7 +258,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const identity = await server.services.identity.getIdentityById({ + const identity = await server.services.identityV1.getIdentityById({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -308,7 +308,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const { identityMemberships, totalCount } = await server.services.identity.listOrgIdentities({ + const { identityMemberships, totalCount } = await server.services.identityV1.listOrgIdentities({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -402,7 +402,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const { identityMemberships, totalCount } = await server.services.identity.searchOrgIdentities({ + const { identityMemberships, totalCount } = await server.services.identityV1.searchOrgIdentities({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -468,7 +468,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const identityMemberships = await server.services.identity.listProjectIdentitiesByIdentityId({ + const identityMemberships = await server.services.identityV1.listProjectIdentitiesByIdentityId({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, diff --git a/backend/src/server/routes/v1/index.ts b/backend/src/server/routes/v1/index.ts index 4300f5698..4088c1490 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -33,8 +33,8 @@ import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-rou import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router"; import { registerIdentityOciAuthRouter } from "./identity-oci-auth-router"; import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router"; -import { registerOrgIdentityMembershipRouter } from "./identity-org-membership-router"; -import { registerIdentityProjectRouter } from "./identity-project-router"; +import { registerIdentityOrgMembershipRouter } from "./identity-org-membership-router"; +import { registerIdentityProjectMembershipRouter } from "./identity-project-router"; import { registerIdentityRouter } from "./identity-router"; import { registerIdentityTlsCertAuthRouter } from "./identity-tls-cert-auth-router"; import { registerIdentityTokenAuthRouter } from "./identity-token-auth-router"; @@ -45,6 +45,7 @@ import { registerInviteOrgRouter } from "./invite-org-router"; import { registerMicrosoftTeamsRouter } from "./microsoft-teams-router"; import { registerNotificationRouter } from "./notification-router"; import { registerOrgAdminRouter } from "./org-admin-router"; +import { registerOrgIdentityRouter } from "./org-identity-router"; import { registerOrgRouter } from "./organization-router"; import { registerPasswordRouter } from "./password-router"; import { registerPkiAlertRouter } from "./pki-alert-router"; @@ -52,6 +53,7 @@ import { registerPkiCollectionRouter } from "./pki-collection-router"; import { registerPkiSubscriberRouter } from "./pki-subscriber-router"; import { PKI_SYNC_REGISTER_ROUTER_MAP, registerPkiSyncRouter } from "./pki-sync-routers"; import { registerProjectEnvRouter } from "./project-env-router"; +import { registerProjectIdentityRouter } from "./project-identity-router"; import { registerProjectKeyRouter } from "./project-key-router"; import { registerProjectMembershipRouter } from "./project-membership-router"; import { registerProjectRouter } from "./project-router"; @@ -90,8 +92,14 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { { prefix: "/auth" } ); await server.register(registerPasswordRouter, { prefix: "/password" }); - await server.register(registerOrgRouter, { prefix: "/organization" }); - await server.register(registerOrgIdentityMembershipRouter, { prefix: "/organization" }); + await server.register( + async (orgRouter) => { + await orgRouter.register(registerOrgRouter); + await orgRouter.register(registerOrgIdentityRouter); + await orgRouter.register(registerIdentityOrgMembershipRouter); + }, + { prefix: "/organization" } + ); await server.register(registerAdminRouter, { prefix: "/admin" }); await server.register(registerOrgAdminRouter, { prefix: "/organization-admin" }); await server.register(registerUserRouter, { prefix: "/user" }); @@ -126,10 +134,11 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { async (projectRouter) => { await projectRouter.register(registerProjectRouter); await projectRouter.register(registerProjectMembershipRouter); + await projectRouter.register(registerProjectIdentityRouter); await projectRouter.register(registerProjectEnvRouter); await projectRouter.register(registerSecretTagRouter); await projectRouter.register(registerGroupProjectRouter); - await projectRouter.register(registerIdentityProjectRouter); + await projectRouter.register(registerIdentityProjectMembershipRouter); }, { prefix: "/projects" } ); diff --git a/backend/src/server/routes/v1/org-identity-router.ts b/backend/src/server/routes/v1/org-identity-router.ts new file mode 100644 index 000000000..7376959d8 --- /dev/null +++ b/backend/src/server/routes/v1/org-identity-router.ts @@ -0,0 +1,286 @@ +import { z } from "zod"; + +import { AccessScope, IdentitiesSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, IDENTITIES } from "@app/lib/api-docs"; +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"; + +const metadataSchema = z.object({ + key: z.string().trim().min(1, "Metadata key cannot be empty"), + value: z.string().trim().min(1, "Metadata value cannot be empty") +}); + +const sanitizedIdentitySchema = IdentitiesSchema.pick({ + id: true, + name: true, + orgId: true, + projectId: true, + createdAt: true, + updatedAt: true, + hasDeleteProtection: true +}).extend({ + authMethods: z.array(z.string()).optional(), + metadata: z.array(metadataSchema).optional() +}); + +export const registerOrgIdentityRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/identities", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + tags: [ApiDocsTags.Identities], + description: "Create an identity", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + name: z.string().trim().min(1).describe(IDENTITIES.CREATE.name), + hasDeleteProtection: z.boolean().default(false).describe(IDENTITIES.CREATE.hasDeleteProtection), + metadata: z.array(metadataSchema).optional().describe(IDENTITIES.CREATE.metadata) + }), + response: { + 200: z.object({ + identity: sanitizedIdentitySchema + }) + } + }, + handler: async (req) => { + const { identity } = await server.services.identityV2.createIdentity({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.permission.orgId + }, + data: { + name: req.body.name, + hasDeleteProtection: req.body.hasDeleteProtection, + metadata: req.body.metadata + } + }); + + await server.services.auditLog.createAuditLog({ + orgId: req.permission.orgId, + ...req.auditLogInfo, + event: { + type: EventType.CREATE_IDENTITY, + metadata: { + identityId: identity.id, + name: req.body.name, + hasDeleteProtection: req.body.hasDeleteProtection, + metadata: req.body.metadata + } + } + }); + + return { identity }; + } + }); + + server.route({ + method: "PATCH", + url: "/identities/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + tags: [ApiDocsTags.Identities], + description: "Update an identity", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + identityId: z.string().trim().describe(IDENTITIES.UPDATE.identityId) + }), + body: z.object({ + name: z.string().trim().min(1).optional().describe(IDENTITIES.UPDATE.name), + hasDeleteProtection: z.boolean().optional().describe(IDENTITIES.UPDATE.hasDeleteProtection), + metadata: z.array(metadataSchema).optional().describe(IDENTITIES.UPDATE.metadata) + }), + response: { + 200: z.object({ + identity: sanitizedIdentitySchema + }) + } + }, + handler: async (req) => { + const { identity } = await server.services.identityV2.updateIdentity({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.permission.orgId + }, + selector: { + identityId: req.params.identityId + }, + data: { + name: req.body.name, + hasDeleteProtection: req.body.hasDeleteProtection, + metadata: req.body.metadata + } + }); + + await server.services.auditLog.createAuditLog({ + orgId: req.permission.orgId, + ...req.auditLogInfo, + event: { + type: EventType.UPDATE_IDENTITY, + metadata: { + identityId: req.params.identityId, + name: req.body.name, + hasDeleteProtection: req.body.hasDeleteProtection, + metadata: req.body.metadata + } + } + }); + + return { identity }; + } + }); + + server.route({ + method: "DELETE", + url: "/identities/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + tags: [ApiDocsTags.Identities], + description: "Delete an identity", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + identityId: z.string().trim().describe(IDENTITIES.DELETE.identityId) + }), + response: { + 200: z.object({ + identity: sanitizedIdentitySchema + }) + } + }, + handler: async (req) => { + const { identity } = await server.services.identityV2.deleteIdentity({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.permission.orgId + }, + selector: { + identityId: req.params.identityId + } + }); + + await server.services.auditLog.createAuditLog({ + orgId: req.permission.orgId, + ...req.auditLogInfo, + event: { + type: EventType.DELETE_IDENTITY, + metadata: { + identityId: req.params.identityId + } + } + }); + + return { identity }; + } + }); + + server.route({ + method: "GET", + url: "/identities/:identityId", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + tags: [ApiDocsTags.Identities], + description: "Get an identity by ID", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + identityId: z.string().trim().describe(IDENTITIES.GET_BY_ID.identityId) + }), + response: { + 200: z.object({ + identity: sanitizedIdentitySchema + }) + } + }, + handler: async (req) => { + const { identity } = await server.services.identityV2.getIdentityById({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.permission.orgId + }, + selector: { + identityId: req.params.identityId + } + }); + + return { identity }; + } + }); + + server.route({ + method: "GET", + url: "/identities", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + tags: [ApiDocsTags.Identities], + description: "List identities", + security: [ + { + bearerAuth: [] + } + ], + querystring: z.object({ + offset: z.coerce.number().min(0).default(0).describe(IDENTITIES.LIST.offset).optional(), + limit: z.coerce.number().min(1).max(1000).default(20).describe(IDENTITIES.LIST.limit).optional(), + search: z.string().trim().describe(IDENTITIES.LIST.search).optional() + }), + response: { + 200: z.object({ + identities: z.array(sanitizedIdentitySchema), + totalCount: z.number() + }) + } + }, + handler: async (req) => { + const { docs: identities, count: totalCount } = await server.services.identityV2.listIdentities({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.permission.orgId + }, + data: { + offset: req.query.offset, + limit: req.query.limit, + search: req.query.search + } + }); + + return { identities, totalCount }; + } + }); +}; diff --git a/backend/src/server/routes/v1/project-identity-router.ts b/backend/src/server/routes/v1/project-identity-router.ts new file mode 100644 index 000000000..2a8a6c7d8 --- /dev/null +++ b/backend/src/server/routes/v1/project-identity-router.ts @@ -0,0 +1,306 @@ +import { z } from "zod"; + +import { AccessScope, IdentitiesSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, IDENTITIES } from "@app/lib/api-docs"; +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"; + +const metadataSchema = z.object({ + key: z.string().trim().min(1, "Metadata key cannot be empty"), + value: z.string().trim().min(1, "Metadata value cannot be empty") +}); + +const sanitizedIdentitySchema = IdentitiesSchema.pick({ + id: true, + name: true, + orgId: true, + projectId: true, + createdAt: true, + updatedAt: true, + hasDeleteProtection: true +}).extend({ + metadata: z + .object({ + key: z.string(), + value: z.string(), + id: z.string() + }) + .array() + .optional() +}); + +export const registerProjectIdentityRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/:projectId/identities", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + tags: [ApiDocsTags.Identities], + description: "Create an identity in a project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe("The ID of the project to create the identity in") + }), + body: z.object({ + name: z.string().trim().min(1).describe(IDENTITIES.CREATE.name), + hasDeleteProtection: z.boolean().default(false).describe(IDENTITIES.CREATE.hasDeleteProtection), + metadata: z.array(metadataSchema).optional().describe(IDENTITIES.CREATE.metadata) + }), + response: { + 200: z.object({ + identity: sanitizedIdentitySchema + }) + } + }, + handler: async (req) => { + const { identity } = await server.services.identityV2.createIdentity({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + data: { + name: req.body.name, + hasDeleteProtection: req.body.hasDeleteProtection, + metadata: req.body.metadata + } + }); + + await server.services.auditLog.createAuditLog({ + projectId: req.params.projectId, + ...req.auditLogInfo, + event: { + type: EventType.CREATE_IDENTITY, + metadata: { + identityId: identity.id, + name: req.body.name, + hasDeleteProtection: req.body.hasDeleteProtection, + metadata: req.body.metadata + } + } + }); + + return { identity }; + } + }); + + server.route({ + method: "PATCH", + url: "/:projectId/identities/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + tags: [ApiDocsTags.Identities], + description: "Update an identity in a project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe("The ID of the project"), + identityId: z.string().trim().describe(IDENTITIES.UPDATE.identityId) + }), + body: z.object({ + name: z.string().trim().min(1).optional().describe(IDENTITIES.UPDATE.name), + hasDeleteProtection: z.boolean().optional().describe(IDENTITIES.UPDATE.hasDeleteProtection), + metadata: z.array(metadataSchema).optional().describe(IDENTITIES.UPDATE.metadata) + }), + response: { + 200: z.object({ + identity: sanitizedIdentitySchema + }) + } + }, + handler: async (req) => { + const { identity } = await server.services.identityV2.updateIdentity({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + projectId: req.params.projectId, + orgId: req.permission.orgId + }, + selector: { + identityId: req.params.identityId + }, + data: { + name: req.body.name, + hasDeleteProtection: req.body.hasDeleteProtection, + metadata: req.body.metadata + } + }); + + await server.services.auditLog.createAuditLog({ + projectId: req.params.projectId, + ...req.auditLogInfo, + event: { + type: EventType.UPDATE_IDENTITY, + metadata: { + identityId: req.params.identityId, + name: req.body.name, + hasDeleteProtection: req.body.hasDeleteProtection, + metadata: req.body.metadata + } + } + }); + + return { identity }; + } + }); + + server.route({ + method: "DELETE", + url: "/:projectId/identities/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + tags: [ApiDocsTags.Identities], + description: "Delete an identity from a project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe("The ID of the project"), + identityId: z.string().trim().describe(IDENTITIES.DELETE.identityId) + }), + response: { + 200: z.object({ + identity: sanitizedIdentitySchema + }) + } + }, + handler: async (req) => { + const { identity } = await server.services.identityV2.deleteIdentity({ + permission: req.permission, + scopeData: { + orgId: req.permission.orgId, + scope: AccessScope.Project, + projectId: req.params.projectId + }, + selector: { + identityId: req.params.identityId + } + }); + + await server.services.auditLog.createAuditLog({ + projectId: req.params.projectId, + ...req.auditLogInfo, + event: { + type: EventType.DELETE_IDENTITY, + metadata: { + identityId: req.params.identityId + } + } + }); + + return { identity }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/identities/:identityId", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + tags: [ApiDocsTags.Identities], + description: "Get an identity by ID in a project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe("The ID of the project"), + identityId: z.string().trim().describe(IDENTITIES.GET_BY_ID.identityId) + }), + response: { + 200: z.object({ + identity: sanitizedIdentitySchema + }) + } + }, + handler: async (req) => { + const { identity } = await server.services.identityV2.getIdentityById({ + permission: req.permission, + scopeData: { + orgId: req.permission.orgId, + scope: AccessScope.Project, + projectId: req.params.projectId + }, + selector: { + identityId: req.params.identityId + } + }); + + return { identity }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/identities", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + tags: [ApiDocsTags.Identities], + description: "List identities in a project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe("The ID of the project") + }), + querystring: z.object({ + offset: z.coerce.number().min(0).default(0).describe(IDENTITIES.LIST.offset).optional(), + limit: z.coerce.number().min(1).max(1000).default(20).describe(IDENTITIES.LIST.limit).optional(), + search: z.string().trim().describe(IDENTITIES.LIST.search).optional() + }), + response: { + 200: z.object({ + identities: z.array(sanitizedIdentitySchema), + totalCount: z.number() + }) + } + }, + handler: async (req) => { + const { docs: identities, count: totalCount } = await server.services.identityV2.listIdentities({ + permission: req.permission, + scopeData: { + orgId: req.permission.orgId, + scope: AccessScope.Project, + projectId: req.params.projectId + }, + data: { + offset: req.query.offset, + limit: req.query.limit, + search: req.query.search + } + }); + + return { identities, totalCount }; + } + }); +}; diff --git a/backend/src/server/routes/v2/identity-org-router.ts b/backend/src/server/routes/v2/identity-org-router.ts index 630e09dda..f1295209f 100644 --- a/backend/src/server/routes/v2/identity-org-router.ts +++ b/backend/src/server/routes/v2/identity-org-router.ts @@ -70,7 +70,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const { identityMemberships, totalCount } = await server.services.identity.listOrgIdentities({ + const { identityMemberships, totalCount } = await server.services.identityV1.listOrgIdentities({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, diff --git a/backend/src/server/routes/v2/identity-project-membership-router.ts b/backend/src/server/routes/v2/identity-project-membership-router.ts new file mode 100644 index 000000000..2ea027736 --- /dev/null +++ b/backend/src/server/routes/v2/identity-project-membership-router.ts @@ -0,0 +1,166 @@ +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() + }) + ), + identity: IdentitiesSchema.pick({ name: true, id: true, orgId: true, projectId: true }).extend({ + authMethods: z.array(z.string()) + }) + }) + }) + } + }, + 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 d3d91a3ba..b13a67457 100644 --- a/backend/src/server/routes/v2/index.ts +++ b/backend/src/server/routes/v2/index.ts @@ -5,6 +5,7 @@ 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"; @@ -21,6 +22,13 @@ 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( diff --git a/backend/src/services/scoped-identity/identity-dal.ts b/backend/src/services/identity-v2/identity-dal.ts similarity index 98% rename from backend/src/services/scoped-identity/identity-dal.ts rename to backend/src/services/identity-v2/identity-dal.ts index 0375a1b08..2a741202b 100644 --- a/backend/src/services/scoped-identity/identity-dal.ts +++ b/backend/src/services/identity-v2/identity-dal.ts @@ -4,9 +4,9 @@ import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex" import { buildAuthMethods } from "../identity/identity-fns"; -export type TIdentityDALFactory = ReturnType; +export type TIdentityV2DALFactory = ReturnType; -export const identityDALFactory = (db: TDbClient) => { +export const identityV2DALFactory = (db: TDbClient) => { const orm = ormify(db, TableName.Identity); const getIdentityById = async (scopeData: AccessScopeData, identityId: string) => { diff --git a/backend/src/services/scoped-identity/identity-service.ts b/backend/src/services/identity-v2/identity-service.ts similarity index 90% rename from backend/src/services/scoped-identity/identity-service.ts rename to backend/src/services/identity-v2/identity-service.ts index 489d3a744..0de1b8a5c 100644 --- a/backend/src/services/scoped-identity/identity-service.ts +++ b/backend/src/services/identity-v2/identity-service.ts @@ -6,19 +6,19 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TIdentityMetadataDALFactory } from "../identity/identity-metadata-dal"; import { TMembershipRoleDALFactory } from "../membership/membership-role-dal"; import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal"; -import { TIdentityDALFactory } from "./identity-dal"; +import { TIdentityV2DALFactory } from "./identity-dal"; import { - TCreateIdentityDTO, - TDeleteIdentityDTO, - TGetIdentityByIdDTO, - TListIdentityDTO, - TUpdateIdentityDTO + TCreateIdentityV2DTO, + TDeleteIdentityV2DTO, + TGetIdentityByIdV2DTO, + TListIdentityV2DTO, + TUpdateIdentityV2DTO } from "./identity-types"; import { newOrgIdentityFactory } from "./org/org-identity-factory"; import { newProjectIdentityFactory } from "./project/project-identity-factory"; -type TScopedIdentityServiceFactoryDep = { - identityDAL: TIdentityDALFactory; +type TScopedIdentityV2ServiceFactoryDep = { + identityDAL: TIdentityV2DALFactory; permissionService: TPermissionServiceFactory; licenseService: Pick; membershipIdentityDAL: TMembershipIdentityDALFactory; @@ -26,16 +26,16 @@ type TScopedIdentityServiceFactoryDep = { identityMetadataDAL: TIdentityMetadataDALFactory; }; -export type TScopedIdentityServiceFactory = ReturnType; +export type TScopedIdentityV2ServiceFactory = ReturnType; -export const identityServiceFactory = ({ +export const identityV2ServiceFactory = ({ identityDAL, permissionService, licenseService, membershipIdentityDAL, membershipRoleDAL, identityMetadataDAL -}: TScopedIdentityServiceFactoryDep) => { +}: TScopedIdentityV2ServiceFactoryDep) => { const orgFactory = newOrgIdentityFactory({ permissionService }); @@ -50,7 +50,7 @@ export const identityServiceFactory = ({ [AccessScope.Namespace]: orgFactory }; - const createIdentity = async (dto: TCreateIdentityDTO) => { + const createIdentity = async (dto: TCreateIdentityV2DTO) => { const { scopeData, data } = dto; const factory = scopeFactory[scopeData.scope]; @@ -134,7 +134,7 @@ export const identityServiceFactory = ({ return { identity }; }; - const updateIdentity = async (dto: TUpdateIdentityDTO) => { + const updateIdentity = async (dto: TUpdateIdentityV2DTO) => { const { scopeData, data } = dto; const factory = scopeFactory[scopeData.scope]; @@ -187,7 +187,7 @@ export const identityServiceFactory = ({ return { identity }; }; - const deleteIdentity = async (dto: TDeleteIdentityDTO) => { + const deleteIdentity = async (dto: TDeleteIdentityV2DTO) => { const { scopeData } = dto; const factory = scopeFactory[scopeData.scope]; @@ -208,7 +208,7 @@ export const identityServiceFactory = ({ return { identity: deletedIdentity }; }; - const getIdentityById = async (dto: TGetIdentityByIdDTO) => { + const getIdentityById = async (dto: TGetIdentityByIdV2DTO) => { const { scopeData } = dto; const factory = scopeFactory[scopeData.scope]; @@ -220,7 +220,7 @@ export const identityServiceFactory = ({ return { identity }; }; - const listIdentities = async (dto: TListIdentityDTO) => { + const listIdentities = async (dto: TListIdentityV2DTO) => { const { scopeData } = dto; const factory = scopeFactory[scopeData.scope]; @@ -232,7 +232,7 @@ export const identityServiceFactory = ({ limit: dto.data.limit }); - return { identities }; + return identities; }; return { diff --git a/backend/src/services/scoped-identity/identity-types.ts b/backend/src/services/identity-v2/identity-types.ts similarity index 66% rename from backend/src/services/scoped-identity/identity-types.ts rename to backend/src/services/identity-v2/identity-types.ts index 4d3e3e0ff..1fcfe2d8f 100644 --- a/backend/src/services/scoped-identity/identity-types.ts +++ b/backend/src/services/identity-v2/identity-types.ts @@ -1,12 +1,12 @@ import { AccessScopeData } from "@app/db/schemas"; import { OrderByDirection, OrgServiceActor } from "@app/lib/types"; -export interface TIdentityFactory { - onCreateIdentityGuard: (arg: TCreateIdentityDTO) => Promise; - onUpdateIdentityGuard: (arg: TUpdateIdentityDTO) => Promise; - onDeleteIdentityGuard: (arg: TDeleteIdentityDTO) => Promise; - onListIdentityGuard: (arg: TListIdentityDTO) => Promise; - onGetIdentityByIdGuard: (arg: TGetIdentityByIdDTO) => Promise; +export interface TIdentityV2Factory { + onCreateIdentityGuard: (arg: TCreateIdentityV2DTO) => Promise; + onUpdateIdentityGuard: (arg: TUpdateIdentityV2DTO) => Promise; + onDeleteIdentityGuard: (arg: TDeleteIdentityV2DTO) => Promise; + onListIdentityGuard: (arg: TListIdentityV2DTO) => Promise; + onGetIdentityByIdGuard: (arg: TGetIdentityByIdV2DTO) => Promise; getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string }; } @@ -15,7 +15,7 @@ export enum IdentityOrderBy { Role = "role" } -export type TCreateIdentityDTO = { +export type TCreateIdentityV2DTO = { permission: OrgServiceActor; scopeData: AccessScopeData; data: { @@ -25,7 +25,7 @@ export type TCreateIdentityDTO = { }; }; -export type TUpdateIdentityDTO = { +export type TUpdateIdentityV2DTO = { permission: OrgServiceActor; scopeData: AccessScopeData; selector: { @@ -38,7 +38,7 @@ export type TUpdateIdentityDTO = { }>; }; -export type TDeleteIdentityDTO = { +export type TDeleteIdentityV2DTO = { permission: OrgServiceActor; scopeData: AccessScopeData; selector: { @@ -46,7 +46,7 @@ export type TDeleteIdentityDTO = { }; }; -export type TGetIdentityByIdDTO = { +export type TGetIdentityByIdV2DTO = { permission: OrgServiceActor; scopeData: AccessScopeData; selector: { @@ -54,7 +54,7 @@ export type TGetIdentityByIdDTO = { }; }; -export type TListIdentityDTO = { +export type TListIdentityV2DTO = { permission: OrgServiceActor; scopeData: AccessScopeData; data: Partial<{ diff --git a/backend/src/services/scoped-identity/org/org-identity-factory.ts b/backend/src/services/identity-v2/org/org-identity-factory.ts similarity index 82% rename from backend/src/services/scoped-identity/org/org-identity-factory.ts rename to backend/src/services/identity-v2/org/org-identity-factory.ts index cec7a4e66..770c0ae7d 100644 --- a/backend/src/services/scoped-identity/org/org-identity-factory.ts +++ b/backend/src/services/identity-v2/org/org-identity-factory.ts @@ -5,21 +5,21 @@ import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/ser import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { InternalServerError } from "@app/lib/errors"; -import { TIdentityFactory } from "../identity-types"; +import { TIdentityV2Factory } from "../identity-types"; type TOrgIdentityFactoryDep = { permissionService: Pick; }; -export const newOrgIdentityFactory = ({ permissionService }: TOrgIdentityFactoryDep): TIdentityFactory => { - const getScopeField: TIdentityFactory["getScopeField"] = (scopeData) => { +export const newOrgIdentityFactory = ({ permissionService }: TOrgIdentityFactoryDep): TIdentityV2Factory => { + const getScopeField: TIdentityV2Factory["getScopeField"] = (scopeData) => { if (scopeData.scope === AccessScope.Organization) { return { key: "orgId" as const, value: scopeData.orgId }; } throw new InternalServerError({ message: "Invalid scope provided for the org factory" }); }; - const onCreateIdentityGuard: TIdentityFactory["onCreateIdentityGuard"] = async (dto) => { + const onCreateIdentityGuard: TIdentityV2Factory["onCreateIdentityGuard"] = async (dto) => { const { permission } = await permissionService.getOrgPermission({ actor: dto.permission.type, actorId: dto.permission.id, @@ -31,7 +31,7 @@ export const newOrgIdentityFactory = ({ permissionService }: TOrgIdentityFactory ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); }; - const onUpdateIdentityGuard: TIdentityFactory["onUpdateIdentityGuard"] = async (dto) => { + const onUpdateIdentityGuard: TIdentityV2Factory["onUpdateIdentityGuard"] = async (dto) => { const { permission } = await permissionService.getOrgPermission({ actor: dto.permission.type, actorId: dto.permission.id, @@ -43,7 +43,7 @@ export const newOrgIdentityFactory = ({ permissionService }: TOrgIdentityFactory ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); }; - const onDeleteIdentityGuard: TIdentityFactory["onDeleteIdentityGuard"] = async (dto) => { + const onDeleteIdentityGuard: TIdentityV2Factory["onDeleteIdentityGuard"] = async (dto) => { const { permission } = await permissionService.getOrgPermission({ actor: dto.permission.type, actorId: dto.permission.id, @@ -55,7 +55,7 @@ export const newOrgIdentityFactory = ({ permissionService }: TOrgIdentityFactory ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity); }; - const onListIdentityGuard: TIdentityFactory["onListIdentityGuard"] = async (dto) => { + const onListIdentityGuard: TIdentityV2Factory["onListIdentityGuard"] = async (dto) => { const { permission } = await permissionService.getOrgPermission({ actor: dto.permission.type, actorId: dto.permission.id, @@ -67,7 +67,7 @@ export const newOrgIdentityFactory = ({ permissionService }: TOrgIdentityFactory ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); }; - const onGetIdentityByIdGuard: TIdentityFactory["onGetIdentityByIdGuard"] = async (dto) => { + const onGetIdentityByIdGuard: TIdentityV2Factory["onGetIdentityByIdGuard"] = async (dto) => { const { permission } = await permissionService.getOrgPermission({ actor: dto.permission.type, actorId: dto.permission.id, diff --git a/backend/src/services/scoped-identity/project/project-identity-factory.ts b/backend/src/services/identity-v2/project/project-identity-factory.ts similarity index 84% rename from backend/src/services/scoped-identity/project/project-identity-factory.ts rename to backend/src/services/identity-v2/project/project-identity-factory.ts index cf6976a5b..ab3ee3b94 100644 --- a/backend/src/services/scoped-identity/project/project-identity-factory.ts +++ b/backend/src/services/identity-v2/project/project-identity-factory.ts @@ -5,21 +5,21 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { InternalServerError } from "@app/lib/errors"; -import { TIdentityFactory } from "../identity-types"; +import { TIdentityV2Factory } from "../identity-types"; type TProjectIdentityFactoryDep = { permissionService: Pick; }; -export const newProjectIdentityFactory = ({ permissionService }: TProjectIdentityFactoryDep): TIdentityFactory => { - const getScopeField: TIdentityFactory["getScopeField"] = (scopeData) => { +export const newProjectIdentityFactory = ({ permissionService }: TProjectIdentityFactoryDep): TIdentityV2Factory => { + const getScopeField: TIdentityV2Factory["getScopeField"] = (scopeData) => { if (scopeData.scope === AccessScope.Project) { return { key: "projectId" as const, value: scopeData.projectId }; } throw new InternalServerError({ message: "Invalid scope provided for the project factory" }); }; - const onCreateIdentityGuard: TIdentityFactory["onCreateIdentityGuard"] = async (dto) => { + const onCreateIdentityGuard: TIdentityV2Factory["onCreateIdentityGuard"] = async (dto) => { const scope = getScopeField(dto.scopeData); const { permission } = await permissionService.getProjectPermission({ actor: dto.permission.type, @@ -35,7 +35,7 @@ export const newProjectIdentityFactory = ({ permissionService }: TProjectIdentit ); }; - const onUpdateIdentityGuard: TIdentityFactory["onUpdateIdentityGuard"] = async (dto) => { + const onUpdateIdentityGuard: TIdentityV2Factory["onUpdateIdentityGuard"] = async (dto) => { const scope = getScopeField(dto.scopeData); const { permission } = await permissionService.getProjectPermission({ actor: dto.permission.type, @@ -51,7 +51,7 @@ export const newProjectIdentityFactory = ({ permissionService }: TProjectIdentit ); }; - const onDeleteIdentityGuard: TIdentityFactory["onDeleteIdentityGuard"] = async (dto) => { + const onDeleteIdentityGuard: TIdentityV2Factory["onDeleteIdentityGuard"] = async (dto) => { const scope = getScopeField(dto.scopeData); const { permission } = await permissionService.getProjectPermission({ actor: dto.permission.type, @@ -67,7 +67,7 @@ export const newProjectIdentityFactory = ({ permissionService }: TProjectIdentit ); }; - const onListIdentityGuard: TIdentityFactory["onListIdentityGuard"] = async (dto) => { + const onListIdentityGuard: TIdentityV2Factory["onListIdentityGuard"] = async (dto) => { const scope = getScopeField(dto.scopeData); const { permission } = await permissionService.getProjectPermission({ actor: dto.permission.type, @@ -83,7 +83,7 @@ export const newProjectIdentityFactory = ({ permissionService }: TProjectIdentit ); }; - const onGetIdentityByIdGuard: TIdentityFactory["onGetIdentityByIdGuard"] = async (dto) => { + const onGetIdentityByIdGuard: TIdentityV2Factory["onGetIdentityByIdGuard"] = async (dto) => { const scope = getScopeField(dto.scopeData); const { permission } = await permissionService.getProjectPermission({ actor: dto.permission.type, diff --git a/backend/src/services/membership-identity/project/project-membership-identity-factory.ts b/backend/src/services/membership-identity/project/project-membership-identity-factory.ts index afbdecf0a..3639dff78 100644 --- a/backend/src/services/membership-identity/project/project-membership-identity-factory.ts +++ b/backend/src/services/membership-identity/project/project-membership-identity-factory.ts @@ -12,11 +12,11 @@ import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, InternalServerError, PermissionBoundaryError } from "@app/lib/errors"; +import { TIdentityDALFactory } from "@app/services/identity/identity-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TMembershipIdentityDALFactory } from "../membership-identity-dal"; import { TMembershipIdentityScopeFactory } from "../membership-identity-types"; -import { TIdentityDALFactory } from "@app/services/identity/identity-dal"; type TProjectMembershipIdentityScopeFactoryDep = { permissionService: Pick;