feat(server): added identity privilege route changes with project slug

This commit is contained in:
Akhil Mohan
2024-03-28 19:22:54 +05:30
parent 51716336c2
commit c8cbcaf10c
8 changed files with 264 additions and 214 deletions

View File

@@ -1,12 +1,13 @@
import { packRules } from "@casl/ability/extra";
import { MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeSchema } from "@app/db/schemas";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
import { ProjectPermissionSet } from "@app/ee/services/permission/project-permission";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { zpStr } from "@app/lib/zod";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -18,37 +19,37 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
body: z.union([
z.object({
identityId: z.string().min(1),
projectId: z.string().min(1),
// disallow empty string
slug: zpStr(
z
.string()
.max(60)
.trim()
.optional()
.default(`privilege-${slugify(alphaNumericNanoId(12))}`)
.refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug"
})
),
projectSlug: z.string().min(1),
slug: z
.string()
.min(1)
.max(60)
.trim()
.optional()
.default(`privilege-${slugify(alphaNumericNanoId(12))}`)
.refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug"
}),
permissions: z.any().array(),
isPackedPermission: z.boolean().optional().default(true),
isPackedPermission: z.boolean().optional().default(false),
isTemporary: z.literal(false).default(false)
}),
z.object({
identityId: z.string(),
projectId: z.string(),
projectSlug: z.string().min(1),
slug: z
.string()
.min(1)
.max(60)
.trim()
.optional()
.default(`privilege-${slugify(alphaNumericNanoId(12))}`)
.refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug"
}),
permissions: z.any().array(),
isTemporary: z.literal(true),
isPackedPermission: z.boolean().optional().default(true),
isPackedPermission: z.boolean().optional().default(false),
temporaryMode: z.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
@@ -77,30 +78,33 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
});
server.route({
url: "/:privilegeId",
url: "/",
method: "PATCH",
schema: {
params: z.object({
privilegeId: z.string()
body: z.object({
// disallow empty string
slug: z.string().min(1),
identityId: z.string().min(1),
projectSlug: z.string().min(1),
data: z
.object({
slug: z
.string()
.min(1)
.max(60)
.trim()
.refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug"
}),
isPackedPermission: z.boolean().optional().default(false),
permissions: z.any().array(),
isTemporary: z.boolean(),
temporaryMode: z.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
.partial()
}),
body: z
.object({
// disallow empty string
slug: z
.string()
.max(60)
.trim()
.refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug"
}),
permissions: z.any().array(),
isPackedPermission: z.boolean().optional().default(true),
isTemporary: z.boolean(),
temporaryMode: z.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
.partial(),
response: {
200: z.object({
privilege: IdentityProjectAdditionalPrivilegeSchema
@@ -109,27 +113,32 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilege.updateById({
const { isPackedPermission, ...data } = req.body.data;
const privilege = await server.services.identityProjectAdditionalPrivilege.updateBySlug({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body,
permissions: req.body.permissions
? JSON.stringify(req.body.isPackedPermission ? req.body.permissions : packRules(req.body.permissions))
: undefined,
privilegeId: req.params.privilegeId
data: {
...data,
permissions: data?.permissions
? JSON.stringify(isPackedPermission ? data?.permissions : packRules(data.permissions))
: undefined
}
});
return { privilege };
}
});
server.route({
url: "/:privilegeId",
url: "/",
method: "DELETE",
schema: {
params: z.object({
privilegeId: z.string()
body: z.object({
slug: z.string().min(1),
identityId: z.string().min(1),
projectSlug: z.string().min(1)
}),
response: {
200: z.object({
@@ -139,23 +148,27 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilege.deleteById({
const privilege = await server.services.identityProjectAdditionalPrivilege.deleteBySlug({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
privilegeId: req.params.privilegeId
...req.body
});
return { privilege };
}
});
server.route({
url: "/:privilegeId",
url: "/:slug",
method: "GET",
schema: {
params: z.object({
privilegeId: z.string()
slug: z.string()
}),
querystring: z.object({
identityId: z.string().min(1),
projectSlug: z.string().min(1)
}),
response: {
200: z.object({
@@ -165,24 +178,29 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilege.getPrivilegeDetailsById({
const privilege = await server.services.identityProjectAdditionalPrivilege.getPrivilegeDetailsBySlug({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
privilegeId: req.params.privilegeId
slug: req.params.slug,
...req.query
});
return { privilege };
}
});
server.route({
url: "/permissions",
method: "POST",
url: "/",
method: "GET",
schema: {
body: z.object({
identityId: z.string(),
projectId: z.string()
querystring: z.object({
identityId: z.string().min(1),
projectSlug: z.string().min(1),
unpacked: z
.enum(["false", "true"])
.transform((el) => el === "true")
.default("true")
}),
response: {
200: z.object({
@@ -197,9 +215,16 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.body.projectId,
identityId: req.body.identityId
...req.query
});
if (req.query.unpacked) {
return {
privileges: privileges.map(({ permissions, ...el }) => ({
...el,
permissions: unpackRules(permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
}))
};
}
return { privileges };
}
});

View File

@@ -5,6 +5,7 @@ import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
@@ -21,6 +22,7 @@ import {
type TIdentityProjectAdditionalPrivilegeServiceFactoryDep = {
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeDALFactory;
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
@@ -31,19 +33,24 @@ export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
export const identityProjectAdditionalPrivilegeServiceFactory = ({
identityProjectAdditionalPrivilegeDAL,
identityProjectDAL,
permissionService
permissionService,
projectDAL
}: TIdentityProjectAdditionalPrivilegeServiceFactoryDep) => {
const create = async ({
slug,
actor,
actorId,
projectId,
identityId,
projectSlug,
permissions: customPermission,
actorOrgId,
actorAuthMethod,
...dto
}: TCreateIdentityPrivilegeDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
@@ -65,7 +72,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
);
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
@@ -96,19 +103,23 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
return additionalPrivilege;
};
const updateById = async ({
privilegeId,
const updateBySlug = async ({
projectSlug,
slug,
identityId,
data,
actorOrgId,
actor,
actorId,
actorAuthMethod,
...dto
actorAuthMethod
}: TUpdateIdentityPrivilegeDTO) => {
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(privilegeId);
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findById(identityPrivilege.projectMembershipId);
if (!identityProjectMembership) throw new BadRequestError({ message: `Failed to find identity` });
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -127,23 +138,28 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
);
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
if (dto?.slug) {
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
projectMembershipId: identityProjectMembership.id
});
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
if (data?.slug) {
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
slug: dto.slug,
slug: data.slug,
projectMembershipId: identityProjectMembership.id
});
if (existingSlug && existingSlug.id !== identityPrivilege.id)
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
}
const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : identityPrivilege.isTemporary;
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
if (isTemporary) {
const temporaryAccessStartTime = dto?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
const temporaryRange = dto?.temporaryRange || identityPrivilege?.temporaryRange;
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
...dto,
...data,
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
});
@@ -151,7 +167,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
}
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
...dto,
...data,
isTemporary: false,
temporaryAccessStartTime: null,
temporaryAccessEndTime: null,
@@ -161,18 +177,22 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
return additionalPrivilege;
};
const deleteById = async ({
const deleteBySlug = async ({
actorId,
slug,
identityId,
projectSlug,
actor,
actorOrgId,
privilegeId,
actorAuthMethod
}: TDeleteIdentityPrivilegeDTO) => {
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(privilegeId);
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findById(identityPrivilege.projectMembershipId);
if (!identityProjectMembership) throw new BadRequestError({ message: `Failed to find identity` });
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -191,25 +211,34 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
);
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
projectMembershipId: identityProjectMembership.id
});
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
return deletedPrivilege;
};
const getPrivilegeDetailsById = async ({
privilegeId,
const getPrivilegeDetailsBySlug = async ({
projectSlug,
identityId,
slug,
actorOrgId,
actor,
actorId,
actorAuthMethod
}: TGetIdentityPrivilegeDetailsDTO) => {
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(privilegeId);
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
const identityProjectMembership = await identityProjectDAL.findById(identityPrivilege.projectMembershipId);
if (!identityProjectMembership) throw new BadRequestError({ message: `Failed to find identity` });
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
@@ -218,31 +247,31 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityProjectMembership.identityId,
identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
projectMembershipId: identityProjectMembership.id
});
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
return identityPrivilege;
};
const listIdentityProjectPrivileges = async ({
projectId,
identityId,
actorOrgId,
actor,
actorId,
actorAuthMethod
actorAuthMethod,
projectSlug
}: TListIdentityPrivilegesDTO) => {
const identityProjectMembership = await identityProjectDAL.findOne({ projectId, identityId });
if (!identityProjectMembership) throw new BadRequestError({ message: `Failed to find identity` });
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
@@ -251,29 +280,18 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityProjectMembership.identityId,
identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find({
projectMembershipId: identityProjectMembership.id
});
if (!identityPrivileges) throw new BadRequestError({ message: "Identity additional privilege not found" });
return identityPrivileges;
};
return {
create,
updateById,
deleteById,
getPrivilegeDetailsById,
updateBySlug,
deleteBySlug,
getPrivilegeDetailsBySlug,
listIdentityProjectPrivileges
};
};

View File

@@ -4,19 +4,16 @@ export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
Relative = "relative"
}
export type TCreateIdentityPrivilegeDTO = (
export type TCreateIdentityPrivilegeDTO = {
permissions: unknown;
identityId: string;
projectSlug: string;
slug: string;
} & (
| {
permissions: unknown;
identityId: string;
projectId: string;
slug: string;
isTemporary: false;
}
| {
permissions: unknown;
identityId: string;
projectId: string;
slug: string;
isTemporary: true;
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string;
@@ -25,21 +22,33 @@ export type TCreateIdentityPrivilegeDTO = (
) &
Omit<TProjectPermission, "projectId">;
export type TUpdateIdentityPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> &
Partial<{
permissions: unknown;
slug: string;
isTemporary: boolean;
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string;
temporaryAccessStartTime: string;
}>;
export type TUpdateIdentityPrivilegeDTO = { slug: string; identityId: string; projectSlug: string } & Omit<
TProjectPermission,
"projectId"
> & {
data: Partial<{
permissions: unknown;
slug: string;
isTemporary: boolean;
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string;
temporaryAccessStartTime: string;
}>;
};
export type TDeleteIdentityPrivilegeDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
export type TDeleteIdentityPrivilegeDTO = Omit<TProjectPermission, "projectId"> & {
slug: string;
identityId: string;
projectSlug: string;
};
export type TGetIdentityPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
export type TGetIdentityPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & {
slug: string;
identityId: string;
projectSlug: string;
};
export type TListIdentityPrivilegesDTO = Omit<TProjectPermission, "projectId"> & {
identityId: string;
projectId: string;
projectSlug: string;
};

View File

@@ -144,6 +144,8 @@ export const permissionDALFactory = (db: TDbClient) => {
}
]
});
if (!permission?.[0]) return undefined;
// when introducting cron mode change it here
const activeRoles = permission?.[0]?.roles?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
@@ -155,9 +157,7 @@ export const permissionDALFactory = (db: TDbClient) => {
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
);
return permission?.[0]
? { ...permission[0], roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges }
: undefined;
return { ...permission[0], roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges };
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectPermission" });
}
@@ -176,6 +176,11 @@ export const permissionDALFactory = (db: TDbClient) => {
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.leftJoin(
TableName.IdentityProjectAdditionalPrivilege,
`${TableName.IdentityProjectAdditionalPrivilege}.projectMembershipId`,
`${TableName.IdentityProjectMembership}.id`
)
.join(
// Join the Project table to later select orgId
TableName.Project,
@@ -191,9 +196,28 @@ export const permissionDALFactory = (db: TDbClient) => {
db.ref("role").withSchema(TableName.IdentityProjectMembership).as("oldRoleField"),
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
)
.select("permissions");
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
db.ref("permissions").withSchema(TableName.ProjectRoles),
db.ref("id").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApId"),
db.ref("permissions").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApPermissions"),
db
.ref("temporaryMode")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryMode"),
db.ref("isTemporary").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApIsTemporary"),
db
.ref("temporaryRange")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryAccessEndTime")
);
const permission = sqlNestRelationships({
data: docs,
@@ -218,16 +242,44 @@ export const permissionDALFactory = (db: TDbClient) => {
permissions: z.unknown(),
customRoleSlug: z.string().optional().nullable()
}).parse(data)
},
{
key: "identityApId",
label: "additionalPrivileges" as const,
mapper: ({
identityApId,
identityApPermissions,
identityApIsTemporary,
identityApTemporaryMode,
identityApTemporaryRange,
identityApTemporaryAccessEndTime,
identityApTemporaryAccessStartTime
}) => ({
id: identityApId,
permissions: identityApPermissions,
temporaryRange: identityApTemporaryRange,
temporaryMode: identityApTemporaryMode,
temporaryAccessEndTime: identityApTemporaryAccessEndTime,
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
isTemporary: identityApIsTemporary
})
}
]
});
if (!permission?.[0]) return undefined;
// when introducting cron mode change it here
const activeRoles = permission?.[0]?.roles.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
);
return permission?.[0] ? { ...permission[0], roles: activeRoles } : undefined;
const activeAdditionalPrivileges = permission?.[0]?.additionalPrivileges?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
);
return { ...permission[0], roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges };
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectIdentityPermission" });
}

View File

@@ -238,8 +238,16 @@ export const permissionServiceFactory = ({
throw new UnauthorizedError({ name: "You are not a member of this organization" });
}
const rolePermissions =
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges =
identityProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
role: ProjectMembershipRole.Custom,
permissions
})) || [];
return {
permission: buildProjectPermission(identityProjectPermission.roles),
permission: buildProjectPermission(rolePermissions.concat(additionalPrivileges)),
membership: identityProjectPermission,
hasRole: (role: string) =>
identityProjectPermission.roles.findIndex(

View File

@@ -560,6 +560,7 @@ export const registerRoutes = async (
projectRoleDAL
});
const identityProjectAdditionalPrivilegeService = identityProjectAdditionalPrivilegeServiceFactory({
projectDAL,
identityProjectAdditionalPrivilegeDAL,
permissionService,
identityProjectDAL

View File

@@ -156,20 +156,6 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
identityId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
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(
z.object({
id: z.string(),

View File

@@ -47,29 +47,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
db.ref("isTemporary").withSchema(TableName.IdentityProjectMembershipRole),
db.ref("temporaryRange").withSchema(TableName.IdentityProjectMembershipRole),
db.ref("temporaryAccessStartTime").withSchema(TableName.IdentityProjectMembershipRole),
db.ref("temporaryAccessEndTime").withSchema(TableName.IdentityProjectMembershipRole),
db.ref("id").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApId"),
db.ref("name").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApName"),
db.ref("description").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApDescription"),
db.ref("slug").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApSlug"),
db
.ref("temporaryMode")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryMode"),
db.ref("isTemporary").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApIsTemporary"),
db.ref("createdAt").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApCreatedAt"),
db
.ref("temporaryRange")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryAccessEndTime")
db.ref("temporaryAccessEndTime").withSchema(TableName.IdentityProjectMembershipRole)
);
const members = sqlNestRelationships({
@@ -113,33 +91,6 @@ export const identityProjectDALFactory = (db: TDbClient) => {
temporaryAccessStartTime,
isTemporary
})
},
{
label: "additionalPrivileges" as const,
key: "identityApId",
mapper: ({
identityApId,
identityApDescription,
identityApName,
identityApSlug,
identityApIsTemporary,
identityApTemporaryMode,
identityApTemporaryRange,
identityApCreatedAt,
identityApTemporaryAccessEndTime,
identityApTemporaryAccessStartTime
}) => ({
id: identityApId,
name: identityApName,
description: identityApDescription,
slug: identityApSlug,
temporaryRange: identityApTemporaryRange,
temporaryMode: identityApTemporaryMode,
temporaryAccessEndTime: identityApTemporaryAccessEndTime,
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
isTemporary: identityApIsTemporary,
createdAt: identityApCreatedAt
})
}
]
});