feat(server): removed name and description and fixed api for user privileges

This commit is contained in:
Akhil Mohan
2024-03-28 19:55:12 +05:30
parent f551a4158d
commit 6b51c7269a
12 changed files with 148 additions and 157 deletions
@@ -9,9 +9,7 @@ import { TImmutableDBKeys } from "./models";
export const IdentityProjectAdditionalPrivilegeSchema = z.object({ export const IdentityProjectAdditionalPrivilegeSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
name: z.string(),
slug: z.string(), slug: z.string(),
description: z.string().nullable().optional(),
projectMembershipId: z.string().uuid(), projectMembershipId: z.string().uuid(),
isTemporary: z.boolean().default(false), isTemporary: z.boolean().default(false),
temporaryMode: z.string().nullable().optional(), temporaryMode: z.string().nullable().optional(),
@@ -9,9 +9,7 @@ import { TImmutableDBKeys } from "./models";
export const ProjectUserAdditionalPrivilegeSchema = z.object({ export const ProjectUserAdditionalPrivilegeSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
name: z.string(),
slug: z.string(), slug: z.string(),
description: z.string().nullable().optional(),
projectMembershipId: z.string().uuid(), projectMembershipId: z.string().uuid(),
isTemporary: z.boolean().default(false), isTemporary: z.boolean().default(false),
temporaryMode: z.string().nullable().optional(), temporaryMode: z.string().nullable().optional(),
@@ -31,8 +31,6 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
message: "Slug must be a valid slug" message: "Slug must be a valid slug"
}) })
), ),
name: z.string().trim().min(1),
description: z.string().trim().optional(),
permissions: z.any().array(), permissions: z.any().array(),
isPackedPermission: z.boolean().optional().default(true), isPackedPermission: z.boolean().optional().default(true),
isTemporary: z.literal(false).default(false) isTemporary: z.literal(false).default(false)
@@ -48,8 +46,6 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
.refine((v) => slugify(v) === v, { .refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug" message: "Slug must be a valid slug"
}), }),
name: z.string().trim(),
description: z.string().trim().optional(),
permissions: z.any().array(), permissions: z.any().array(),
isTemporary: z.literal(true), isTemporary: z.literal(true),
isPackedPermission: z.boolean().optional().default(true), isPackedPermission: z.boolean().optional().default(true),
@@ -70,6 +66,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body, ...req.body,
permissions: JSON.stringify( permissions: JSON.stringify(
req.body.isPackedPermission ? req.body.permissions : packRules(req.body.permissions) req.body.isPackedPermission ? req.body.permissions : packRules(req.body.permissions)
@@ -96,8 +93,6 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
.refine((v) => slugify(v) === v, { .refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug" message: "Slug must be a valid slug"
}), }),
name: z.string().trim(),
description: z.string().trim().optional(),
permissions: z.any().array(), permissions: z.any().array(),
isPackedPermission: z.boolean().optional().default(true), isPackedPermission: z.boolean().optional().default(true),
isTemporary: z.boolean(), isTemporary: z.boolean(),
@@ -118,6 +113,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body, ...req.body,
permissions: req.body.permissions permissions: req.body.permissions
? JSON.stringify(req.body.isPackedPermission ? req.body.permissions : packRules(req.body.permissions)) ? JSON.stringify(req.body.isPackedPermission ? req.body.permissions : packRules(req.body.permissions))
@@ -146,6 +142,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
const privilege = await server.services.identityProjectAdditionalPrivilege.deleteById({ const privilege = await server.services.identityProjectAdditionalPrivilege.deleteById({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
privilegeId: req.params.privilegeId privilegeId: req.params.privilegeId
}); });
@@ -170,6 +167,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilege.getPrivilegeDetailsById({ const privilege = await server.services.identityProjectAdditionalPrivilege.getPrivilegeDetailsById({
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
privilegeId: req.params.privilegeId privilegeId: req.params.privilegeId
@@ -197,6 +195,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
const privileges = await server.services.identityProjectAdditionalPrivilege.listIdentityProjectPrivileges({ const privileges = await server.services.identityProjectAdditionalPrivilege.listIdentityProjectPrivileges({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
projectId: req.body.projectId, projectId: req.body.projectId,
identityId: req.body.identityId identityId: req.body.identityId
@@ -5,7 +5,6 @@ import { z } from "zod";
import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas"; import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas";
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types"; import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { zpStr } from "@app/lib/zod";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -17,36 +16,29 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
body: z.union([ body: z.union([
z.object({ z.object({
projectMembershipId: z.string(), projectMembershipId: z.string(),
// to disallow empty string slug: z
slug: zpStr( .string()
z .min(1)
.string() .max(60)
.max(60) .trim()
.trim() .default(`privilege-${slugify(alphaNumericNanoId(12))}`)
.optional() .refine((v) => slugify(v) === v, {
.default(`privilege-${slugify(alphaNumericNanoId(12))}`) message: "Slug must be a valid slug"
.refine((v) => slugify(v) === v, { }),
message: "Slug must be a valid slug"
})
),
name: z.string().trim(),
description: z.string().trim().optional(),
permissions: z.any().array(), permissions: z.any().array(),
isTemporary: z.literal(false).default(false) isTemporary: z.literal(false).default(false)
}), }),
z.object({ z.object({
projectMembershipId: z.string(), projectMembershipId: z.string(),
slug: zpStr( slug: z
z .string()
.string() .min(1)
.max(60) .max(60)
.trim() .trim()
.optional() .default(`privilege-${slugify(alphaNumericNanoId(12))}`)
.default(`privilege-${slugify(alphaNumericNanoId(12))}`) .refine((v) => slugify(v) === v, {
.refine((v) => slugify(v) === v, { message: "Slug must be a valid slug"
message: "Slug must be a valid slug" }),
})
),
name: z.string().trim(), name: z.string().trim(),
description: z.string().trim().optional(), description: z.string().trim().optional(),
permissions: z.any().array(), permissions: z.any().array(),
@@ -68,6 +60,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body, ...req.body,
permissions: JSON.stringify(req.body.permissions) permissions: JSON.stringify(req.body.permissions)
}); });
@@ -88,11 +81,10 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
.string() .string()
.max(60) .max(60)
.trim() .trim()
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
.refine((v) => slugify(v) === v, { .refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug" message: "Slug must be a valid slug"
}), }),
name: z.string().trim(),
description: z.string().trim().optional(),
permissions: z.any().array(), permissions: z.any().array(),
isTemporary: z.boolean(), isTemporary: z.boolean(),
temporaryMode: z.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode), temporaryMode: z.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode),
@@ -112,6 +104,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body, ...req.body,
permissions: req.body.permissions ? JSON.stringify(req.body.permissions) : undefined, permissions: req.body.permissions ? JSON.stringify(req.body.permissions) : undefined,
privilegeId: req.params.privilegeId privilegeId: req.params.privilegeId
@@ -139,12 +132,39 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
privilegeId: req.params.privilegeId privilegeId: req.params.privilegeId
}); });
return { privilege }; return { privilege };
} }
}); });
server.route({
url: "/",
method: "GET",
schema: {
querystring: z.object({
projectMembershipId: z.string()
}),
response: {
200: z.object({
privileges: ProjectUserAdditionalPrivilegeSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const privileges = await server.services.projectUserAdditionalPrivilege.listPrivileges({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
projectMembershipId: req.query.projectMembershipId
});
return { privileges };
}
});
server.route({ server.route({
url: "/:privilegeId", url: "/:privilegeId",
method: "GET", method: "GET",
@@ -164,6 +184,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
privilegeId: req.params.privilegeId privilegeId: req.params.privilegeId
}); });
return { privilege }; return { privilege };
@@ -34,7 +34,6 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
permissionService permissionService
}: TIdentityProjectAdditionalPrivilegeServiceFactoryDep) => { }: TIdentityProjectAdditionalPrivilegeServiceFactoryDep) => {
const create = async ({ const create = async ({
name,
slug, slug,
actor, actor,
actorId, actorId,
@@ -42,7 +41,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
identityId, identityId,
permissions: customPermission, permissions: customPermission,
actorOrgId, actorOrgId,
description, actorAuthMethod,
...dto ...dto
}: TCreateIdentityPrivilegeDTO) => { }: TCreateIdentityPrivilegeDTO) => {
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId }); const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
@@ -53,6 +52,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actor, actor,
actorId, actorId,
identityProjectMembership.projectId, identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
@@ -60,6 +60,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
ActorType.IDENTITY, ActorType.IDENTITY,
identityId, identityId,
identityProjectMembership.projectId, identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
@@ -76,9 +77,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({ const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
projectMembershipId: identityProjectMembership.id, projectMembershipId: identityProjectMembership.id,
slug, slug,
permissions: customPermission, permissions: customPermission
name,
description
}); });
return additionalPrivilege; return additionalPrivilege;
} }
@@ -88,8 +87,6 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
projectMembershipId: identityProjectMembership.id, projectMembershipId: identityProjectMembership.id,
slug, slug,
permissions: customPermission, permissions: customPermission,
name,
description,
isTemporary: true, isTemporary: true,
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative, temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
temporaryRange: dto.temporaryRange, temporaryRange: dto.temporaryRange,
@@ -99,7 +96,14 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
return additionalPrivilege; return additionalPrivilege;
}; };
const updateById = async ({ privilegeId, actorOrgId, actor, actorId, ...dto }: TUpdateIdentityPrivilegeDTO) => { const updateById = async ({
privilegeId,
actorOrgId,
actor,
actorId,
actorAuthMethod,
...dto
}: TUpdateIdentityPrivilegeDTO) => {
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(privilegeId); const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(privilegeId);
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" }); if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
@@ -110,6 +114,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actor, actor,
actorId, actorId,
identityProjectMembership.projectId, identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
@@ -117,6 +122,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
ActorType.IDENTITY, ActorType.IDENTITY,
identityProjectMembership.identityId, identityProjectMembership.identityId,
identityProjectMembership.projectId, identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
@@ -155,7 +161,13 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
return additionalPrivilege; return additionalPrivilege;
}; };
const deleteById = async ({ actorId, actor, actorOrgId, privilegeId }: TDeleteIdentityPrivilegeDTO) => { const deleteById = async ({
actorId,
actor,
actorOrgId,
privilegeId,
actorAuthMethod
}: TDeleteIdentityPrivilegeDTO) => {
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(privilegeId); const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(privilegeId);
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" }); if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
@@ -166,6 +178,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actor, actor,
actorId, actorId,
identityProjectMembership.projectId, identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
@@ -173,6 +186,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
ActorType.IDENTITY, ActorType.IDENTITY,
identityProjectMembership.identityId, identityProjectMembership.identityId,
identityProjectMembership.projectId, identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
@@ -187,7 +201,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
privilegeId, privilegeId,
actorOrgId, actorOrgId,
actor, actor,
actorId actorId,
actorAuthMethod
}: TGetIdentityPrivilegeDetailsDTO) => { }: TGetIdentityPrivilegeDetailsDTO) => {
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(privilegeId); const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(privilegeId);
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" }); if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
@@ -199,6 +214,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actor, actor,
actorId, actorId,
identityProjectMembership.projectId, identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
@@ -206,6 +222,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
ActorType.IDENTITY, ActorType.IDENTITY,
identityProjectMembership.identityId, identityProjectMembership.identityId,
identityProjectMembership.projectId, identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
@@ -220,7 +237,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
identityId, identityId,
actorOrgId, actorOrgId,
actor, actor,
actorId actorId,
actorAuthMethod
}: TListIdentityPrivilegesDTO) => { }: TListIdentityPrivilegesDTO) => {
const identityProjectMembership = await identityProjectDAL.findOne({ projectId, identityId }); const identityProjectMembership = await identityProjectDAL.findOne({ projectId, identityId });
if (!identityProjectMembership) throw new BadRequestError({ message: `Failed to find identity` }); if (!identityProjectMembership) throw new BadRequestError({ message: `Failed to find identity` });
@@ -229,6 +247,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actor, actor,
actorId, actorId,
identityProjectMembership.projectId, identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
@@ -236,6 +255,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
ActorType.IDENTITY, ActorType.IDENTITY,
identityProjectMembership.identityId, identityProjectMembership.identityId,
identityProjectMembership.projectId, identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
@@ -9,18 +9,14 @@ export type TCreateIdentityPrivilegeDTO = (
permissions: unknown; permissions: unknown;
identityId: string; identityId: string;
projectId: string; projectId: string;
name: string;
slug: string; slug: string;
description?: string;
isTemporary: false; isTemporary: false;
} }
| { | {
permissions: unknown; permissions: unknown;
identityId: string; identityId: string;
projectId: string; projectId: string;
name: string;
slug: string; slug: string;
description?: string;
isTemporary: true; isTemporary: true;
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative; temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string; temporaryRange: string;
@@ -32,9 +28,7 @@ export type TCreateIdentityPrivilegeDTO = (
export type TUpdateIdentityPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> & export type TUpdateIdentityPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> &
Partial<{ Partial<{
permissions: unknown; permissions: unknown;
name: string;
slug: string; slug: string;
description?: string;
isTemporary: boolean; isTemporary: boolean;
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative; temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string; temporaryRange: string;
@@ -180,10 +180,12 @@ export const permissionServiceFactory = ({
authMethod: ActorAuthMethod, authMethod: ActorAuthMethod,
userOrgId?: string userOrgId?: string
): Promise<TProjectPermissionRT<ActorType.USER>> => { ): Promise<TProjectPermissionRT<ActorType.USER>> => {
const membership = await permissionDAL.getProjectPermission(userId, projectId); const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
if (!membership) throw new UnauthorizedError({ name: "User not in project" }); if (!userProjectPermission) throw new UnauthorizedError({ name: "User not in project" });
if (membership.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)) { if (
userProjectPermission.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)
) {
throw new BadRequestError({ name: "Custom permission not found" }); throw new BadRequestError({ name: "Custom permission not found" });
} }
@@ -192,11 +194,11 @@ export const permissionServiceFactory = ({
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes. // Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization. // Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
if (userOrgId !== "API_KEY" && membership.orgId !== userOrgId) { if (userOrgId !== "API_KEY" && userProjectPermission.orgId !== userOrgId) {
throw new UnauthorizedError({ name: "You are not logged into this organization" }); throw new UnauthorizedError({ name: "You are not logged into this organization" });
} }
validateOrgSAML(authMethod, membership.orgAuthEnforced); validateOrgSAML(authMethod, userProjectPermission.orgAuthEnforced);
// join two permissions and pass to build the final permission set // join two permissions and pass to build the final permission set
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || []; const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
@@ -210,7 +212,9 @@ export const permissionServiceFactory = ({
permission: buildProjectPermission(rolePermissions.concat(additionalPrivileges)), permission: buildProjectPermission(rolePermissions.concat(additionalPrivileges)),
membership: userProjectPermission, membership: userProjectPermission,
hasRole: (role: string) => hasRole: (role: string) =>
membership.roles.findIndex(({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug) !== -1 userProjectPermission.roles.findIndex(
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
) !== -1
}; };
}; };
@@ -12,6 +12,7 @@ import {
TCreateUserPrivilegeDTO, TCreateUserPrivilegeDTO,
TDeleteUserPrivilegeDTO, TDeleteUserPrivilegeDTO,
TGetUserPrivilegeDetailsDTO, TGetUserPrivilegeDetailsDTO,
TListUserPrivilegesDTO,
TUpdateUserPrivilegeDTO TUpdateUserPrivilegeDTO
} from "./project-user-additional-privilege-types"; } from "./project-user-additional-privilege-types";
@@ -31,13 +32,12 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
permissionService permissionService
}: TProjectUserAdditionalPrivilegeServiceFactoryDep) => { }: TProjectUserAdditionalPrivilegeServiceFactoryDep) => {
const create = async ({ const create = async ({
name,
slug, slug,
actor, actor,
actorId, actorId,
permissions: customPermission, permissions: customPermission,
actorOrgId, actorOrgId,
description, actorAuthMethod,
projectMembershipId, projectMembershipId,
...dto ...dto
}: TCreateUserPrivilegeDTO) => { }: TCreateUserPrivilegeDTO) => {
@@ -48,6 +48,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actor, actor,
actorId, actorId,
projectMembership.projectId, projectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
@@ -59,9 +60,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({ const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
projectMembershipId, projectMembershipId,
slug, slug,
permissions: customPermission, permissions: customPermission
name,
description
}); });
return additionalPrivilege; return additionalPrivilege;
} }
@@ -71,8 +70,6 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
projectMembershipId, projectMembershipId,
slug, slug,
permissions: customPermission, permissions: customPermission,
name,
description,
isTemporary: true, isTemporary: true,
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative, temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
temporaryRange: dto.temporaryRange, temporaryRange: dto.temporaryRange,
@@ -82,7 +79,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
return additionalPrivilege; return additionalPrivilege;
}; };
const updateById = async ({ privilegeId, actorOrgId, actor, actorId, ...dto }: TUpdateUserPrivilegeDTO) => { const updateById = async ({
privilegeId,
actorOrgId,
actor,
actorId,
actorAuthMethod,
...dto
}: TUpdateUserPrivilegeDTO) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId); const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" }); if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
@@ -93,6 +97,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actor, actor,
actorId, actorId,
projectMembership.projectId, projectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
@@ -129,7 +134,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
return additionalPrivilege; return additionalPrivilege;
}; };
const deleteById = async ({ actorId, actor, actorOrgId, privilegeId }: TDeleteUserPrivilegeDTO) => { const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId); const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" }); if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
@@ -140,6 +145,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actor, actor,
actorId, actorId,
projectMembership.projectId, projectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
@@ -148,7 +154,13 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
return deletedPrivilege; return deletedPrivilege;
}; };
const getPrivilegeDetailsById = async ({ privilegeId, actorOrgId, actor, actorId }: TGetUserPrivilegeDetailsDTO) => { const getPrivilegeDetailsById = async ({
privilegeId,
actorOrgId,
actor,
actorId,
actorAuthMethod
}: TGetUserPrivilegeDetailsDTO) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId); const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" }); if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
@@ -159,17 +171,42 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actor, actor,
actorId, actorId,
projectMembership.projectId, projectMembership.projectId,
actorAuthMethod,
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
return userPrivilege; return userPrivilege;
}; };
const listPrivileges = async ({
projectMembershipId,
actorOrgId,
actor,
actorId,
actorAuthMethod
}: TListUserPrivilegesDTO) => {
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find({ projectMembershipId });
return userPrivileges;
};
return { return {
create, create,
updateById, updateById,
deleteById, deleteById,
getPrivilegeDetailsById getPrivilegeDetailsById,
listPrivileges
}; };
}; };
@@ -8,17 +8,13 @@ export type TCreateUserPrivilegeDTO = (
| { | {
permissions: unknown; permissions: unknown;
projectMembershipId: string; projectMembershipId: string;
name: string;
slug: string; slug: string;
description?: string;
isTemporary: false; isTemporary: false;
} }
| { | {
permissions: unknown; permissions: unknown;
projectMembershipId: string; projectMembershipId: string;
name: string;
slug: string; slug: string;
description?: string;
isTemporary: true; isTemporary: true;
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative; temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string; temporaryRange: string;
@@ -30,9 +26,7 @@ export type TCreateUserPrivilegeDTO = (
export type TUpdateUserPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> & export type TUpdateUserPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> &
Partial<{ Partial<{
permissions: unknown; permissions: unknown;
name: string;
slug: string; slug: string;
description?: string;
isTemporary: boolean; isTemporary: boolean;
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative; temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string; temporaryRange: string;
@@ -42,3 +36,5 @@ export type TUpdateUserPrivilegeDTO = { privilegeId: string } & Omit<TProjectPer
export type TDeleteUserPrivilegeDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string }; export type TDeleteUserPrivilegeDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
export type TGetUserPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string }; export type TGetUserPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
export type TListUserPrivilegesDTO = Omit<TProjectPermission, "projectId"> & { projectMembershipId: string };
@@ -40,20 +40,6 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
lastName: true, lastName: true,
id: true id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
additionalPrivileges: z.array(
z.object({
id: z.string(),
name: z.string(),
slug: z.string(),
description: 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(),
createdAt: z.date()
})
),
roles: z.array( roles: z.array(
z.object({ z.object({
id: z.string(), id: z.string(),
@@ -172,7 +158,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
]) ])
) )
.min(1) .min(1)
.refine((data) => data.some(({ isTemporary }) => !isTemporary), "At least long lived role is required") .refine((data) => data.some(({ isTemporary }) => !isTemporary), "At least one long lived role is required")
.describe(PROJECTS.UPDATE_USER_MEMBERSHIP.roles) .describe(PROJECTS.UPDATE_USER_MEMBERSHIP.roles)
}), }),
response: { response: {
@@ -73,20 +73,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
lastName: true, lastName: true,
id: true id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
additionalPrivileges: z.array(
z.object({
id: z.string(),
name: z.string(),
slug: z.string(),
description: 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(),
createdAt: z.date()
})
),
roles: z.array( roles: z.array(
z.object({ z.object({
id: z.string(), id: z.string(),
@@ -29,11 +29,6 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
`${TableName.ProjectUserMembershipRole}.customRoleId`, `${TableName.ProjectUserMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id` `${TableName.ProjectRoles}.id`
) )
.leftJoin(
TableName.ProjectUserAdditionalPrivilege,
`${TableName.ProjectMembership}.id`,
`${TableName.ProjectUserAdditionalPrivilege}.projectMembershipId`
)
.select( .select(
db.ref("id").withSchema(TableName.ProjectMembership), db.ref("id").withSchema(TableName.ProjectMembership),
db.ref("isGhost").withSchema(TableName.Users), db.ref("isGhost").withSchema(TableName.Users),
@@ -52,23 +47,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
db.ref("isTemporary").withSchema(TableName.ProjectUserMembershipRole), db.ref("isTemporary").withSchema(TableName.ProjectUserMembershipRole),
db.ref("temporaryRange").withSchema(TableName.ProjectUserMembershipRole), db.ref("temporaryRange").withSchema(TableName.ProjectUserMembershipRole),
db.ref("temporaryAccessStartTime").withSchema(TableName.ProjectUserMembershipRole), db.ref("temporaryAccessStartTime").withSchema(TableName.ProjectUserMembershipRole),
db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole), db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole)
db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApId"),
db.ref("name").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApName"),
db.ref("description").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApDescription"),
db.ref("slug").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApSlug"),
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryMode"),
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApIsTemporary"),
db.ref("createdAt").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApCreatedAt"),
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userApTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userApTemporaryAccessEndTime")
) )
.where({ isGhost: false }); .where({ isGhost: false });
@@ -108,33 +87,6 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
temporaryAccessStartTime, temporaryAccessStartTime,
isTemporary isTemporary
}) })
},
{
label: "additionalPrivileges" as const,
key: "userApId",
mapper: ({
userApId,
userApDescription,
userApName,
userApSlug,
userApIsTemporary,
userApTemporaryMode,
userApTemporaryRange,
userApTemporaryAccessEndTime,
userApTemporaryAccessStartTime,
userApCreatedAt
}) => ({
id: userApId,
name: userApName,
description: userApDescription,
slug: userApSlug,
temporaryRange: userApTemporaryRange,
temporaryMode: userApTemporaryMode,
temporaryAccessEndTime: userApTemporaryAccessEndTime,
temporaryAccessStartTime: userApTemporaryAccessStartTime,
isTemporary: userApIsTemporary,
createdAt: userApCreatedAt
})
} }
] ]
}); });