mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 12:26:13 +00:00
feat: removed console and resolved drift in tf due to folder removing in v1
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import { packRules } from "@casl/ability/extra";
|
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -82,7 +81,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore-error this is valid ts
|
// @ts-ignore-error this is valid ts
|
||||||
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(permission)))
|
permissions: backfillPermissionV1SchemaToV2Schema(permission)
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@@ -164,7 +163,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore-error this is valid ts
|
// @ts-ignore-error this is valid ts
|
||||||
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(permission)))
|
permissions: backfillPermissionV1SchemaToV2Schema(permission)
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@@ -249,10 +248,12 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
projectSlug: req.body.projectSlug,
|
projectSlug: req.body.projectSlug,
|
||||||
data: {
|
data: {
|
||||||
...updatedInfo,
|
...updatedInfo,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
permissions: permission
|
permissions: permission
|
||||||
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore-error this is valid ts
|
// @ts-ignore-error this is valid ts
|
||||||
JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(permission)))
|
backfillPermissionV1SchemaToV2Schema(permission)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -68,9 +68,10 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions)))
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true)))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { role };
|
return { role };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -129,7 +130,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
data: {
|
data: {
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: req.body.permissions
|
permissions: req.body.permissions
|
||||||
? JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions)))
|
? JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true)))
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -241,6 +242,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
roleSlug: req.params.slug
|
roleSlug: req.params.slug
|
||||||
});
|
});
|
||||||
|
|
||||||
return { role };
|
return { role };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { packRules } from "@casl/ability/extra";
|
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -70,7 +69,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
projectMembershipId: req.body.projectMembershipId,
|
projectMembershipId: req.body.projectMembershipId,
|
||||||
...req.body.type,
|
...req.body.type,
|
||||||
slug: req.body.slug || slugify(alphaNumericNanoId(8)),
|
slug: req.body.slug || slugify(alphaNumericNanoId(8)),
|
||||||
permissions: JSON.stringify(packRules(req.body.permissions))
|
permissions: req.body.permissions
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@@ -137,7 +136,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
permissions: req.body.permissions
|
permissions: req.body.permissions
|
||||||
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore-error this is valid ts
|
// @ts-ignore-error this is valid ts
|
||||||
JSON.stringify(packRules(req.body.permissions))
|
req.body.permissions
|
||||||
: undefined,
|
: undefined,
|
||||||
privilegeId: req.params.privilegeId
|
privilegeId: req.params.privilegeId
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { packRules } from "@casl/ability/extra";
|
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -78,7 +77,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
identityId: req.body.identityId,
|
identityId: req.body.identityId,
|
||||||
...req.body.type,
|
...req.body.type,
|
||||||
slug: req.body.slug || slugify(alphaNumericNanoId(8)),
|
slug: req.body.slug || slugify(alphaNumericNanoId(8)),
|
||||||
permissions: JSON.stringify(packRules(req.body.permissions))
|
permissions: req.body.permissions
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@@ -149,11 +148,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
data: {
|
data: {
|
||||||
...req.body,
|
...req.body,
|
||||||
...req.body.type,
|
...req.body.type,
|
||||||
permissions: req.body.permissions
|
permissions: req.body.permissions || undefined
|
||||||
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore-error this is valid ts
|
|
||||||
JSON.stringify(packRules(req.body.permissions))
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
|
|||||||
+19
-8
@@ -1,4 +1,5 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { packRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
@@ -61,14 +62,18 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityId,
|
identityId,
|
||||||
identityProjectMembership.projectId,
|
identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
|
||||||
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
@@ -78,11 +83,12 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
});
|
});
|
||||||
if (existingSlug) throw new BadRequestError({ message: "Additional privilege with provided slug already exists" });
|
if (existingSlug) throw new BadRequestError({ message: "Additional privilege with provided slug already exists" });
|
||||||
|
|
||||||
|
const packedPermission = JSON.stringify(packRules(customPermission));
|
||||||
if (!dto.isTemporary) {
|
if (!dto.isTemporary) {
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||||
projectMembershipId: identityProjectMembership.id,
|
projectMembershipId: identityProjectMembership.id,
|
||||||
slug,
|
slug,
|
||||||
permissions: customPermission
|
permissions: packedPermission
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -95,7 +101,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||||
projectMembershipId: identityProjectMembership.id,
|
projectMembershipId: identityProjectMembership.id,
|
||||||
slug,
|
slug,
|
||||||
permissions: customPermission,
|
permissions: packedPermission,
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
||||||
temporaryRange: dto.temporaryRange,
|
temporaryRange: dto.temporaryRange,
|
||||||
@@ -133,14 +139,18 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
identityProjectMembership.identityId,
|
||||||
identityProjectMembership.projectId,
|
identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
|
||||||
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
@@ -154,12 +164,13 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
||||||
|
const packedPermission = data.permissions ? JSON.stringify(packRules(data.permissions)) : undefined;
|
||||||
if (isTemporary) {
|
if (isTemporary) {
|
||||||
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
||||||
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
slug: data.slug,
|
slug: data.slug,
|
||||||
permissions: data.permissions,
|
permissions: packedPermission,
|
||||||
isTemporary: data.isTemporary,
|
isTemporary: data.isTemporary,
|
||||||
temporaryRange: data.temporaryRange,
|
temporaryRange: data.temporaryRange,
|
||||||
temporaryMode: data.temporaryMode,
|
temporaryMode: data.temporaryMode,
|
||||||
@@ -174,7 +185,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
|
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
slug: data.slug,
|
slug: data.slug,
|
||||||
permissions: data.permissions,
|
permissions: packedPermission,
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
temporaryAccessStartTime: null,
|
temporaryAccessStartTime: null,
|
||||||
temporaryAccessEndTime: null,
|
temporaryAccessEndTime: null,
|
||||||
|
|||||||
+4
-2
@@ -1,11 +1,13 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
||||||
|
|
||||||
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
||||||
Relative = "relative"
|
Relative = "relative"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TCreateIdentityPrivilegeDTO = {
|
export type TCreateIdentityPrivilegeDTO = {
|
||||||
permissions: unknown;
|
permissions: TProjectPermissionV2Schema[];
|
||||||
identityId: string;
|
identityId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@@ -24,7 +26,7 @@ export type TCreateIdentityPrivilegeDTO = {
|
|||||||
|
|
||||||
export type TUpdateIdentityPrivilegeByIdDTO = { id: string } & Omit<TProjectPermission, "projectId"> & {
|
export type TUpdateIdentityPrivilegeByIdDTO = { id: string } & Omit<TProjectPermission, "projectId"> & {
|
||||||
data: Partial<{
|
data: Partial<{
|
||||||
permissions: unknown;
|
permissions: TProjectPermissionV2Schema[];
|
||||||
slug: string;
|
slug: string;
|
||||||
isTemporary: boolean;
|
isTemporary: boolean;
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
|||||||
+26
-10
@@ -1,5 +1,5 @@
|
|||||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
import { PackRule, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
@@ -70,14 +70,18 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityId,
|
identityId,
|
||||||
identityProjectMembership.projectId,
|
identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
|
||||||
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
@@ -87,11 +91,12 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
|
|
||||||
|
const packedPermission = JSON.stringify(packRules(customPermission));
|
||||||
if (!dto.isTemporary) {
|
if (!dto.isTemporary) {
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||||
projectMembershipId: identityProjectMembership.id,
|
projectMembershipId: identityProjectMembership.id,
|
||||||
slug,
|
slug,
|
||||||
permissions: customPermission
|
permissions: packedPermission
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...additionalPrivilege,
|
...additionalPrivilege,
|
||||||
@@ -103,7 +108,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: packedPermission,
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
||||||
temporaryRange: dto.temporaryRange,
|
temporaryRange: dto.temporaryRange,
|
||||||
@@ -142,14 +147,19 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
|
||||||
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
identityProjectMembership.identityId,
|
||||||
identityProjectMembership.projectId,
|
identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
|
||||||
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
@@ -172,23 +182,29 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
||||||
|
|
||||||
|
const packedPermission = data.permissions ? JSON.stringify(packRules(data.permissions)) : undefined;
|
||||||
if (isTemporary) {
|
if (isTemporary) {
|
||||||
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
||||||
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
...data,
|
slug: data.slug,
|
||||||
|
permissions: packedPermission,
|
||||||
|
isTemporary: data.isTemporary,
|
||||||
|
temporaryRange: data.temporaryRange,
|
||||||
|
temporaryMode: data.temporaryMode,
|
||||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...additionalPrivilege,
|
...additionalPrivilege,
|
||||||
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
...data,
|
slug: data.slug,
|
||||||
|
permissions: packedPermission,
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
temporaryAccessStartTime: null,
|
temporaryAccessStartTime: null,
|
||||||
temporaryAccessEndTime: null,
|
temporaryAccessEndTime: null,
|
||||||
|
|||||||
+4
-2
@@ -1,11 +1,13 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
||||||
|
|
||||||
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
||||||
Relative = "relative"
|
Relative = "relative"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TCreateIdentityPrivilegeDTO = {
|
export type TCreateIdentityPrivilegeDTO = {
|
||||||
permissions: unknown;
|
permissions: TProjectPermissionV2Schema[];
|
||||||
identityId: string;
|
identityId: string;
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@@ -27,7 +29,7 @@ export type TUpdateIdentityPrivilegeDTO = { slug: string; identityId: string; pr
|
|||||||
"projectId"
|
"projectId"
|
||||||
> & {
|
> & {
|
||||||
data: Partial<{
|
data: Partial<{
|
||||||
permissions: unknown;
|
permissions: TProjectPermissionV2Schema[];
|
||||||
slug: string;
|
slug: string;
|
||||||
isTemporary: boolean;
|
isTemporary: boolean;
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
secretVersioning: true,
|
secretVersioning: true,
|
||||||
pitRecovery: false,
|
pitRecovery: false,
|
||||||
ipAllowlisting: false,
|
ipAllowlisting: false,
|
||||||
rbac: false,
|
rbac: true,
|
||||||
customRateLimits: false,
|
customRateLimits: false,
|
||||||
customAlerts: false,
|
customAlerts: false,
|
||||||
auditLogs: false,
|
auditLogs: false,
|
||||||
|
|||||||
@@ -420,6 +420,8 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
|
|||||||
...GeneralPermissionSchema
|
...GeneralPermissionSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export type TProjectPermissionV2Schema = z.infer<typeof ProjectPermissionV2Schema>;
|
||||||
|
|
||||||
const buildAdminPermissionRules = () => {
|
const buildAdminPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
@@ -757,8 +759,11 @@ export const isAtLeastAsPrivilegedWorkspace = (
|
|||||||
};
|
};
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
export const backfillPermissionV1SchemaToV2Schema = (data: z.infer<typeof ProjectPermissionV1Schema>[]) => {
|
export const backfillPermissionV1SchemaToV2Schema = (
|
||||||
const formattedData = UnpackedPermissionSchema.array().parse(data);
|
data: z.infer<typeof ProjectPermissionV1Schema>[],
|
||||||
|
dontRemoveReadFolderPermission?: boolean
|
||||||
|
) => {
|
||||||
|
let formattedData = UnpackedPermissionSchema.array().parse(data);
|
||||||
const secretSubjects = formattedData.filter((el) => el.subject === ProjectPermissionSub.Secrets);
|
const secretSubjects = formattedData.filter((el) => el.subject === ProjectPermissionSub.Secrets);
|
||||||
|
|
||||||
// this means the folder permission as readonly is set
|
// this means the folder permission as readonly is set
|
||||||
@@ -802,13 +807,15 @@ export const backfillPermissionV1SchemaToV2Schema = (data: z.infer<typeof Projec
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return formattedData
|
if (!dontRemoveReadFolderPermission) {
|
||||||
.filter((i) => i.subject !== ProjectPermissionSub.SecretFolders)
|
formattedData = formattedData.filter((i) => i.subject !== ProjectPermissionSub.SecretFolders);
|
||||||
.concat(
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore-error this is valid ts
|
return formattedData.concat(
|
||||||
secretImportPolicies,
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
dynamicSecretPolicies,
|
// @ts-ignore-error this is valid ts
|
||||||
hasReadOnlyFolder.length ? [] : secretFolderPolicies
|
secretImportPolicies,
|
||||||
);
|
dynamicSecretPolicies,
|
||||||
|
hasReadOnlyFolder.length ? [] : secretFolderPolicies
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+20
-9
@@ -1,5 +1,5 @@
|
|||||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
import { PackRule, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
@@ -62,14 +62,18 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
const { permission: entityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
projectMembership.userId,
|
projectMembership.userId,
|
||||||
projectMembership.projectId,
|
projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, entityPermission);
|
|
||||||
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
|
targetUserPermission.update(targetUserPermission.rules.concat(customPermission));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetUserPermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
@@ -81,12 +85,13 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (existingSlug)
|
if (existingSlug)
|
||||||
throw new BadRequestError({ message: `Additional privilege with provided slug ${slug} already exists` });
|
throw new BadRequestError({ message: `Additional privilege with provided slug ${slug} already exists` });
|
||||||
|
|
||||||
|
const packedPermission = JSON.stringify(packRules(customPermission));
|
||||||
if (!dto.isTemporary) {
|
if (!dto.isTemporary) {
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||||
userId: projectMembership.userId,
|
userId: projectMembership.userId,
|
||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
slug,
|
slug,
|
||||||
permissions: customPermission
|
permissions: packedPermission
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...additionalPrivilege,
|
...additionalPrivilege,
|
||||||
@@ -99,7 +104,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
userId: projectMembership.userId,
|
userId: projectMembership.userId,
|
||||||
slug,
|
slug,
|
||||||
permissions: customPermission,
|
permissions: packedPermission,
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
||||||
temporaryRange: dto.temporaryRange,
|
temporaryRange: dto.temporaryRange,
|
||||||
@@ -142,14 +147,18 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
const { permission: entityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
projectMembership.userId,
|
projectMembership.userId,
|
||||||
projectMembership.projectId,
|
projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, entityPermission);
|
|
||||||
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
|
targetUserPermission.update(targetUserPermission.rules.concat(dto.permissions || []));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetUserPermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
@@ -164,12 +173,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : userPrivilege.isTemporary;
|
const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : userPrivilege.isTemporary;
|
||||||
|
|
||||||
|
const packedPermission = dto.permissions && JSON.stringify(packRules(dto.permissions));
|
||||||
if (isTemporary) {
|
if (isTemporary) {
|
||||||
const temporaryAccessStartTime = dto?.temporaryAccessStartTime || userPrivilege?.temporaryAccessStartTime;
|
const temporaryAccessStartTime = dto?.temporaryAccessStartTime || userPrivilege?.temporaryAccessStartTime;
|
||||||
const temporaryRange = dto?.temporaryRange || userPrivilege?.temporaryRange;
|
const temporaryRange = dto?.temporaryRange || userPrivilege?.temporaryRange;
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||||
slug: dto.slug,
|
slug: dto.slug,
|
||||||
permissions: dto.permissions,
|
permissions: packedPermission,
|
||||||
isTemporary: dto.isTemporary,
|
isTemporary: dto.isTemporary,
|
||||||
temporaryRange: dto.temporaryRange,
|
temporaryRange: dto.temporaryRange,
|
||||||
temporaryMode: dto.temporaryMode,
|
temporaryMode: dto.temporaryMode,
|
||||||
@@ -185,7 +196,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
|
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||||
slug: dto.slug,
|
slug: dto.slug,
|
||||||
permissions: dto.permissions,
|
permissions: packedPermission,
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
temporaryAccessStartTime: null,
|
temporaryAccessStartTime: null,
|
||||||
temporaryAccessEndTime: null,
|
temporaryAccessEndTime: null,
|
||||||
|
|||||||
+5
-3
@@ -1,18 +1,20 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
||||||
|
|
||||||
export enum ProjectUserAdditionalPrivilegeTemporaryMode {
|
export enum ProjectUserAdditionalPrivilegeTemporaryMode {
|
||||||
Relative = "relative"
|
Relative = "relative"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TCreateUserPrivilegeDTO = (
|
export type TCreateUserPrivilegeDTO = (
|
||||||
| {
|
| {
|
||||||
permissions: unknown;
|
permissions: TProjectPermissionV2Schema[];
|
||||||
projectMembershipId: string;
|
projectMembershipId: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
isTemporary: false;
|
isTemporary: false;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
permissions: unknown;
|
permissions: TProjectPermissionV2Schema[];
|
||||||
projectMembershipId: string;
|
projectMembershipId: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
isTemporary: true;
|
isTemporary: true;
|
||||||
@@ -25,7 +27,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: TProjectPermissionV2Schema[];
|
||||||
slug: string;
|
slug: string;
|
||||||
isTemporary: boolean;
|
isTemporary: boolean;
|
||||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
@@ -36,7 +37,7 @@ import {
|
|||||||
import { TProjectUserMembershipRoleDALFactory } from "./project-user-membership-role-dal";
|
import { TProjectUserMembershipRoleDALFactory } from "./project-user-membership-role-dal";
|
||||||
|
|
||||||
type TProjectMembershipServiceFactoryDep = {
|
type TProjectMembershipServiceFactoryDep = {
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRole">;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
projectBotDAL: TProjectBotDALFactory;
|
projectBotDAL: TProjectBotDALFactory;
|
||||||
projectMembershipDAL: TProjectMembershipDALFactory;
|
projectMembershipDAL: TProjectMembershipDALFactory;
|
||||||
@@ -262,6 +263,21 @@ export const projectMembershipServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "Forbidden member update" });
|
throw new ForbiddenRequestError({ message: "Forbidden member update" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for await (const { role: requestedRoleChange } of roles) {
|
||||||
|
const { permission: rolePermission } = await permissionService.getProjectPermissionByRole(
|
||||||
|
requestedRoleChange,
|
||||||
|
projectId
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
|
|
||||||
|
if (!hasRequiredPriviledges) {
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
message: `Failed to change to a more privileged role ${requestedRoleChange}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// validate custom roles input
|
// validate custom roles input
|
||||||
const customInputRoles = roles.filter(
|
const customInputRoles = roles.filter(
|
||||||
({ role }) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
|
({ role }) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
|
||||||
|
|||||||
-1
@@ -94,7 +94,6 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({
|
|||||||
privilegeId || ""
|
privilegeId || ""
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(privilegeDetails);
|
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
const isMemberEditDisabled = permission.cannot(
|
const isMemberEditDisabled = permission.cannot(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
|
|||||||
+1
@@ -179,6 +179,7 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
permissions.forEach((permission) => {
|
permissions.forEach((permission) => {
|
||||||
const { subject: caslSub, action, conditions, inverted } = permission;
|
const { subject: caslSub, action, conditions, inverted } = permission;
|
||||||
const subject = (typeof caslSub === "string" ? caslSub : caslSub[0]) as ProjectPermissionSub;
|
const subject = (typeof caslSub === "string" ? caslSub : caslSub[0]) as ProjectPermissionSub;
|
||||||
|
if (!action.length) return;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user