diff --git a/backend/src/ee/routes/v1/user-additional-privilege-router.ts b/backend/src/ee/routes/v1/user-additional-privilege-router.ts index 1ba7e0e33..2c7bdb5cf 100644 --- a/backend/src/ee/routes/v1/user-additional-privilege-router.ts +++ b/backend/src/ee/routes/v1/user-additional-privilege-router.ts @@ -3,19 +3,18 @@ import slugify from "@sindresorhus/slugify"; import ms from "ms"; import { z } from "zod"; -import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas"; -import { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission"; +import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types"; import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { ProjectSpecificPrivilegePermissionSchema } from "@app/server/routes/sanitizedSchemas"; +import { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/santizedSchemas/user-additional-privilege"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/permanent", + url: "/", method: "POST", config: { rateLimit: writeLimit @@ -34,80 +33,30 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }) .optional() .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug), - permissions: ProjectSpecificPrivilegePermissionSchema.describe( - PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions - ) - }), - response: { - 200: z.object({ - privilege: ProjectUserAdditionalPrivilegeSchema - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const privilege = await server.services.projectUserAdditionalPrivilege.create({ - actorId: req.permission.id, - actor: req.permission.type, - actorOrgId: req.permission.orgId, - actorAuthMethod: req.permission.authMethod, - ...req.body, - slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)), - isTemporary: false, - permissions: JSON.stringify( - packRules( - backfillPermissionV1SchemaToV2Schema( - req.body.permissions.actions.map((action) => ({ - action, - subject: req.body.permissions.subject, - conditions: req.body.permissions.conditions - })) - ) - ) - ) - }); - return { privilege }; - } - }); - - server.route({ - method: "POST", - url: "/temporary", - config: { - rateLimit: writeLimit - }, - schema: { - body: z.object({ - projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId), - slug: z - .string() - .min(1) - .max(60) - .trim() - .refine((v) => v.toLowerCase() === v, "Slug must be lowercase") - .refine((v) => slugify(v) === v, { - message: "Slug must be a valid slug" + permissions: ProjectPermissionV2Schema.array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions), + type: z.discriminatedUnion("isTemporary", [ + z.object({ + isTemporary: z.literal(false) + }), + z.object({ + isTemporary: z.literal(true), + temporaryMode: z + .nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode) + .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode), + temporaryRange: z + .string() + .refine((val) => ms(val) > 0, "Temporary range must be a positive number") + .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange), + temporaryAccessStartTime: z + .string() + .datetime() + .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime) }) - .optional() - .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug), - permissions: ProjectSpecificPrivilegePermissionSchema.describe( - PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions - ), - temporaryMode: z - .nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode) - .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode), - temporaryRange: z - .string() - .refine((val) => ms(val) > 0, "Temporary range must be a positive number") - .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange), - temporaryAccessStartTime: z - .string() - .datetime() - .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime) + ]) }), response: { 200: z.object({ - privilege: ProjectUserAdditionalPrivilegeSchema + privilege: SanitizedUserProjectAdditionalPrivilegeSchema }) } }, @@ -118,20 +67,10 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr actor: req.permission.type, actorOrgId: req.permission.orgId, actorAuthMethod: req.permission.authMethod, - ...req.body, - slug: req.body.slug ? slugify(req.body.slug) : `privilege-${slugify(alphaNumericNanoId(12))}`, - isTemporary: true, - permissions: JSON.stringify( - packRules( - backfillPermissionV1SchemaToV2Schema( - req.body.permissions.actions.map((action) => ({ - action, - subject: req.body.permissions.subject, - conditions: req.body.permissions.conditions - })) - ) - ) - ) + projectMembershipId: req.body.projectMembershipId, + ...req.body.type, + slug: req.body.slug || slugify(alphaNumericNanoId(8).toLowerCase()), + permissions: JSON.stringify(packRules(req.body.permissions)) }); return { privilege }; } @@ -158,26 +97,31 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr message: "Slug must be a valid slug" }) .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug), - permissions: ProjectSpecificPrivilegePermissionSchema.describe( - PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions - ).optional(), - isTemporary: z.boolean().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary), - temporaryMode: z - .nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode) - .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode), - temporaryRange: z - .string() - .refine((val) => ms(val) > 0, "Temporary range must be a positive number") - .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryRange), - temporaryAccessStartTime: z - .string() - .datetime() - .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryAccessStartTime) + permissions: ProjectPermissionV2Schema.array() + .optional() + .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions), + type: z.discriminatedUnion("isTemporary", [ + z.object({ isTemporary: z.literal(false).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary) }), + z.object({ + isTemporary: z.literal(true).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary), + temporaryMode: z + .nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode) + .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode), + temporaryRange: z + .string() + .refine((val) => typeof val === "undefined" || ms(val) > 0, "Temporary range must be a positive number") + .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryRange), + temporaryAccessStartTime: z + .string() + .datetime() + .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryAccessStartTime) + }) + ]) }) .partial(), response: { 200: z.object({ - privilege: ProjectUserAdditionalPrivilegeSchema + privilege: SanitizedUserProjectAdditionalPrivilegeSchema }) } }, @@ -189,18 +133,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr actorOrgId: req.permission.orgId, actorAuthMethod: req.permission.authMethod, ...req.body, + ...req.body.type, permissions: req.body.permissions - ? JSON.stringify( - packRules( - backfillPermissionV1SchemaToV2Schema( - req.body.permissions.actions.map((action) => ({ - action, - subject: req.body.permissions!.subject, - conditions: req.body.permissions!.conditions - })) - ) - ) - ) + ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore-error this is valid ts + JSON.stringify(packRules(req.body.permissions)) : undefined, privilegeId: req.params.privilegeId }); @@ -220,7 +157,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }), response: { 200: z.object({ - privilege: ProjectUserAdditionalPrivilegeSchema + privilege: SanitizedUserProjectAdditionalPrivilegeSchema }) } }, @@ -249,7 +186,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }), response: { 200: z.object({ - privileges: ProjectUserAdditionalPrivilegeSchema.array() + privileges: SanitizedUserProjectAdditionalPrivilegeSchema.omit({ permissions: true }).array() }) } }, @@ -278,7 +215,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }), response: { 200: z.object({ - privilege: ProjectUserAdditionalPrivilegeSchema + privilege: SanitizedUserProjectAdditionalPrivilegeSchema }) } }, diff --git a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts index 8b54c428c..64742364d 100644 --- a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts +++ b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts @@ -259,10 +259,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ userId: projectMembership.userId, projectId: projectMembership.projectId }); - return userPrivileges.map((el) => ({ - ...el, - permissions: unpackPermissions(el.permissions) - })); + return userPrivileges; }; return { diff --git a/backend/src/server/routes/santizedSchemas/user-additional-privilege.ts b/backend/src/server/routes/santizedSchemas/user-additional-privilege.ts new file mode 100644 index 000000000..502ab0f7c --- /dev/null +++ b/backend/src/server/routes/santizedSchemas/user-additional-privilege.ts @@ -0,0 +1,7 @@ +import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas"; + +import { UnpackedPermissionSchema } from "./permission"; + +export const SanitizedUserProjectAdditionalPrivilegeSchema = ProjectUserAdditionalPrivilegeSchema.extend({ + permissions: UnpackedPermissionSchema.array() +});