diff --git a/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts b/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts index f17b6e560..f5f4207e5 100644 --- a/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts +++ b/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts @@ -14,66 +14,25 @@ import { AuthMode } from "@app/services/auth/auth-type"; export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/", + url: "/permanent", method: "POST", schema: { - body: z.union([ - z.object({ - identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId), - projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug), - slug: z - .string() - .min(1) - .max(60) - .trim() - .default(`privilege-${slugify(alphaNumericNanoId(12))}`) - .refine((val) => val.toLowerCase() === val, "Must be lowercase") - .refine((v) => slugify(v) === v, { - message: "Slug must be a valid slug" - }) - .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug), - permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions), - isPackedPermission: z - .boolean() - .optional() - .default(false) - .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.isPackPermission), - isTemporary: z.literal(false).default(false).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.isTemporary) - }), - z.object({ - identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId), - projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug), - slug: z - .string() - .min(1) - .max(60) - .trim() - .default(`privilege-${slugify(alphaNumericNanoId(12))}`) - .refine((val) => val.toLowerCase() === val, "Must be lowercase") - .refine((v) => slugify(v) === v, { - message: "Slug must be a valid slug" - }) - .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug), - permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions), - isPackedPermission: z - .boolean() - .optional() - .default(false) - .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.isPackPermission), - isTemporary: z.literal(true).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.isTemporary), - temporaryMode: z - .nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode) - .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode), - temporaryRange: z - .string() - .refine((val) => ms(val) > 0, "Temporary range must be a positive number") - .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange), - temporaryAccessStartTime: z - .string() - .datetime() - .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime) - }) - ]), + body: z.object({ + identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId), + projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug), + slug: z + .string() + .min(1) + .max(60) + .trim() + .default(slugify(alphaNumericNanoId(12))) + .refine((val) => val.toLowerCase() === val, "Must be lowercase") + .refine((v) => slugify(v) === v, { + message: "Slug must be a valid slug" + }) + .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug), + permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions) + }), response: { 200: z.object({ privilege: IdentityProjectAdditionalPrivilegeSchema @@ -88,9 +47,60 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F actorOrgId: req.permission.orgId, actorAuthMethod: req.permission.authMethod, ...req.body, - permissions: JSON.stringify( - req.body.isPackedPermission ? req.body.permissions : packRules(req.body.permissions) - ) + isTemporary: false, + permissions: JSON.stringify(packRules(req.body.permissions)) + }); + return { privilege }; + } + }); + + server.route({ + url: "/temporary", + method: "POST", + schema: { + body: z.object({ + identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId), + projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug), + slug: z + .string() + .min(1) + .max(60) + .trim() + .default(slugify(alphaNumericNanoId(12))) + .refine((val) => val.toLowerCase() === val, "Must be lowercase") + .refine((v) => slugify(v) === v, { + message: "Slug must be a valid slug" + }) + .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug), + permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions), + temporaryMode: z + .nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode) + .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode), + temporaryRange: z + .string() + .refine((val) => ms(val) > 0, "Temporary range must be a positive number") + .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange), + temporaryAccessStartTime: z + .string() + .datetime() + .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime) + }), + response: { + 200: z.object({ + privilege: IdentityProjectAdditionalPrivilegeSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const privilege = await server.services.identityProjectAdditionalPrivilege.create({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + ...req.body, + isTemporary: true, + permissions: JSON.stringify(packRules(req.body.permissions)) }); return { privilege }; } @@ -117,11 +127,6 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F message: "Slug must be a valid slug" }) .describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.newSlug), - isPackedPermission: z - .boolean() - .optional() - .default(false) - .describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isPackPermission), permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.permissions), isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary), temporaryMode: z @@ -146,7 +151,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const { isPackedPermission, ...updatedInfo } = req.body.data; + const updatedInfo = req.body.data; const privilege = await server.services.identityProjectAdditionalPrivilege.updateBySlug({ actorId: req.permission.id, actor: req.permission.type, @@ -157,9 +162,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F projectSlug: req.body.projectSlug, data: { ...updatedInfo, - permissions: updatedInfo?.permissions - ? JSON.stringify(isPackedPermission ? updatedInfo?.permissions : packRules(updatedInfo.permissions)) - : undefined + permissions: updatedInfo?.permissions ? JSON.stringify(packRules(updatedInfo.permissions)) : undefined } }); return { privilege }; 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 c003971e4..9b6bfb6fb 100644 --- a/backend/src/ee/routes/v1/user-additional-privilege-router.ts +++ b/backend/src/ee/routes/v1/user-additional-privilege-router.ts @@ -11,54 +11,24 @@ import { AuthMode } from "@app/services/auth/auth-type"; export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/", + url: "/permanent", method: "POST", schema: { - body: z.union([ - z.object({ - projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId), - slug: z - .string() - .min(1) - .max(60) - .trim() - .default(`privilege-${slugify(alphaNumericNanoId(12))}`) - .refine((v) => v.toLowerCase() === v, "Slug must be lowercase") - .refine((v) => slugify(v) === v, { - message: "Slug must be a valid slug" - }) - .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug), - permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions), - isTemporary: z.literal(false).default(false).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.isTemporary) - }), - z.object({ - projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId), - slug: z - .string() - .min(1) - .max(60) - .trim() - .default(`privilege-${slugify(alphaNumericNanoId(12))}`) - .refine((v) => v.toLowerCase() === v, "Slug must be lowercase") - .refine((v) => slugify(v) === v, { - message: "Slug must be a valid slug" - }) - .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug), - permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions), - isTemporary: z.literal(true).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.isTemporary), - 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) - }) - ]), + body: z.object({ + projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId), + slug: z + .string() + .min(1) + .max(60) + .trim() + .default(slugify(alphaNumericNanoId(12))) + .refine((v) => v.toLowerCase() === v, "Slug must be lowercase") + .refine((v) => slugify(v) === v, { + message: "Slug must be a valid slug" + }) + .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug), + permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions) + }), response: { 200: z.object({ privilege: ProjectUserAdditionalPrivilegeSchema @@ -73,6 +43,58 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr actorOrgId: req.permission.orgId, actorAuthMethod: req.permission.authMethod, ...req.body, + isTemporary: false, + permissions: JSON.stringify(req.body.permissions) + }); + return { privilege }; + } + }); + + server.route({ + url: "/temporary", + method: "POST", + schema: { + body: z.object({ + projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId), + slug: z + .string() + .min(1) + .max(60) + .trim() + .default(`privilege-${slugify(alphaNumericNanoId(12))}`) + .refine((v) => v.toLowerCase() === v, "Slug must be lowercase") + .refine((v) => slugify(v) === v, { + message: "Slug must be a valid slug" + }) + .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug), + permissions: z.any().array().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 + }) + } + }, + 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, + isTemporary: true, permissions: JSON.stringify(req.body.permissions) }); return { privilege }; diff --git a/frontend/src/hooks/api/identityProjectAdditionalPrivilege/mutation.tsx b/frontend/src/hooks/api/identityProjectAdditionalPrivilege/mutation.tsx index fb79fa464..0ea4318c4 100644 --- a/frontend/src/hooks/api/identityProjectAdditionalPrivilege/mutation.tsx +++ b/frontend/src/hooks/api/identityProjectAdditionalPrivilege/mutation.tsx @@ -1,4 +1,3 @@ -import { packRules } from "@casl/ability/extra"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; @@ -16,11 +15,10 @@ export const useCreateIdentityProjectAdditionalPrivilege = () => { return useMutation({ mutationFn: async (dto) => { - const { data } = await apiRequest.post("/api/v1/additional-privilege/identity", { - ...dto, - isPackedPermission: true, - permissions: packRules(dto.permissions) - }); + const { data } = await apiRequest.post( + "/api/v1/additional-privilege/identity/permanent", + dto + ); return data.privilege; }, onSuccess: (_, { projectSlug, identityId }) => { @@ -40,11 +38,7 @@ export const useUpdateIdentityProjectAdditionalPrivilege = () => { privilegeSlug, projectSlug, identityId, - data: { - isPackedPermission: true, - ...data, - permissions: data.permissions ? packRules(data.permissions) : undefined - } + data }); return res.privilege; }, diff --git a/frontend/src/hooks/api/projectUserAdditionalPrivilege/mutation.tsx b/frontend/src/hooks/api/projectUserAdditionalPrivilege/mutation.tsx index 6a07efdf4..fb21a425e 100644 --- a/frontend/src/hooks/api/projectUserAdditionalPrivilege/mutation.tsx +++ b/frontend/src/hooks/api/projectUserAdditionalPrivilege/mutation.tsx @@ -16,7 +16,7 @@ export const useCreateProjectUserAdditionalPrivilege = () => { return useMutation<{ privilege: TProjectUserPrivilege }, {}, TCreateProjectUserPrivilegeDTO>({ mutationFn: async (dto) => { - const { data } = await apiRequest.post("/api/v1/additional-privilege/users", { + const { data } = await apiRequest.post("/api/v1/additional-privilege/users/permanent", { ...dto, permissions: packRules(dto.permissions) });