frontend permissions

* rework backend workspace permissions
This commit is contained in:
Philipinho
2024-06-03 02:54:12 +01:00
parent b88e0b605f
commit 886d9591fa
54 changed files with 715 additions and 385 deletions
@@ -1,61 +0,0 @@
import { Injectable } from '@nestjs/common';
import {
AbilityBuilder,
createMongoAbility,
ExtractSubjectType,
MongoAbility,
} from '@casl/ability';
import { Action } from '../ability.action';
import { UserRole } from '../../../helpers/types/permission';
import { User, Workspace } from '@docmost/db/types/entity.types';
export type Subjects =
| 'Workspace'
| 'Space'
| 'SpaceMember'
| 'Group'
| 'GroupUser'
| 'Attachment'
| 'Comment'
| 'Page'
| 'User'
| 'WorkspaceUser'
| 'all';
export type AppAbility = MongoAbility<[Action, Subjects]>;
@Injectable()
export default class CaslAbilityFactory {
createForUser(user: User, workspace: Workspace) {
const { can, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
const userRole = user.role;
if (userRole === UserRole.OWNER || userRole === UserRole.ADMIN) {
// Workspace Users
can([Action.Manage], 'Workspace');
can([Action.Manage], 'WorkspaceUser');
// Groups
can([Action.Manage], 'Group');
can([Action.Manage], 'GroupUser');
// Attachments
can([Action.Manage], 'Attachment');
}
if (userRole === UserRole.MEMBER) {
can([Action.Read], 'WorkspaceUser');
// Groups
can([Action.Read], 'Group');
can([Action.Read], 'GroupUser');
// Attachments
can([Action.Read, Action.Create], 'Attachment');
}
return build({
detectSubjectType: (item) => item as ExtractSubjectType<Subjects>,
});
}
}
@@ -9,7 +9,7 @@ import { User } from '@docmost/db/types/entity.types';
import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
import {
SpaceCaslAction,
SpaceAbility,
ISpaceAbility,
SpaceCaslSubject,
} from '../interfaces/space-ability.type';
import { findHighestUserSpaceRole } from '@docmost/db/repos/space/utils';
@@ -39,7 +39,7 @@ export default class SpaceAbilityFactory {
}
function buildSpaceAdminAbility() {
const { can, build } = new AbilityBuilder<MongoAbility<SpaceAbility>>(
const { can, build } = new AbilityBuilder<MongoAbility<ISpaceAbility>>(
createMongoAbility,
);
can(SpaceCaslAction.Manage, SpaceCaslSubject.Settings);
@@ -49,7 +49,7 @@ function buildSpaceAdminAbility() {
}
function buildSpaceWriterAbility() {
const { can, build } = new AbilityBuilder<MongoAbility<SpaceAbility>>(
const { can, build } = new AbilityBuilder<MongoAbility<ISpaceAbility>>(
createMongoAbility,
);
can(SpaceCaslAction.Read, SpaceCaslSubject.Settings);
@@ -59,7 +59,7 @@ function buildSpaceWriterAbility() {
}
function buildSpaceReaderAbility() {
const { can, build } = new AbilityBuilder<MongoAbility<SpaceAbility>>(
const { can, build } = new AbilityBuilder<MongoAbility<ISpaceAbility>>(
createMongoAbility,
);
can(SpaceCaslAction.Read, SpaceCaslSubject.Settings);
@@ -0,0 +1,73 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import {
AbilityBuilder,
createMongoAbility,
MongoAbility,
} from '@casl/ability';
import { UserRole } from '../../../helpers/types/permission';
import { User, Workspace } from '@docmost/db/types/entity.types';
import {
IWorkspaceAbility,
WorkspaceCaslAction,
WorkspaceCaslSubject,
} from '../interfaces/workspace-ability.type';
@Injectable()
export default class WorkspaceAbilityFactory {
createForUser(user: User, workspace: Workspace) {
const userRole = user.role;
switch (userRole) {
case UserRole.OWNER:
return buildWorkspaceOwnerAbility();
case UserRole.ADMIN:
return buildWorkspaceAdminAbility();
case UserRole.MEMBER:
return buildWorkspaceMemberAbility();
default:
throw new NotFoundException('Workspace permissions not found');
}
}
}
function buildWorkspaceOwnerAbility() {
const { can, build } = new AbilityBuilder<MongoAbility<IWorkspaceAbility>>(
createMongoAbility,
);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Settings);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Member);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Space);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Group);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Member);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Attachment);
return build();
}
function buildWorkspaceAdminAbility() {
const { can, build } = new AbilityBuilder<MongoAbility<IWorkspaceAbility>>(
createMongoAbility,
);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Settings);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Member);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Space);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Group);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Member);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Attachment);
return build();
}
function buildWorkspaceMemberAbility() {
const { can, build } = new AbilityBuilder<MongoAbility<IWorkspaceAbility>>(
createMongoAbility,
);
can(WorkspaceCaslAction.Read, WorkspaceCaslSubject.Settings);
can(WorkspaceCaslAction.Read, WorkspaceCaslSubject.Member);
can(WorkspaceCaslAction.Read, WorkspaceCaslSubject.Space);
can(WorkspaceCaslAction.Read, WorkspaceCaslSubject.Group);
can(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Attachment);
return build();
}
@@ -1,7 +0,0 @@
export enum Action {
Manage = 'manage',
Create = 'create',
Read = 'read',
Update = 'update',
Delete = 'delete',
}
+3 -3
View File
@@ -1,10 +1,10 @@
import { Global, Module } from '@nestjs/common';
import CaslAbilityFactory from './abilities/casl-ability.factory';
import SpaceAbilityFactory from './abilities/space-ability.factory';
import WorkspaceAbilityFactory from './abilities/workspace-ability.factory';
@Global()
@Module({
providers: [CaslAbilityFactory, SpaceAbilityFactory],
exports: [CaslAbilityFactory, SpaceAbilityFactory],
providers: [WorkspaceAbilityFactory, SpaceAbilityFactory],
exports: [WorkspaceAbilityFactory, SpaceAbilityFactory],
})
export class CaslModule {}
@@ -1,6 +0,0 @@
import { PolicyHandler } from '../interfaces/policy-handler.interface';
import { SetMetadata } from '@nestjs/common';
export const CHECK_POLICIES_KEY = 'check_policy';
export const CheckPolicies = (...handlers: PolicyHandler[]) =>
SetMetadata(CHECK_POLICIES_KEY, handlers);
@@ -1,40 +0,0 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import CaslAbilityFactory, {
AppAbility,
} from '../abilities/casl-ability.factory';
import { PolicyHandler } from '../interfaces/policy-handler.interface';
import { CHECK_POLICIES_KEY } from '../decorators/policies.decorator';
@Injectable()
export class PoliciesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private caslAbilityFactory: CaslAbilityFactory,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const policyHandlers =
this.reflector.get<PolicyHandler[]>(
CHECK_POLICIES_KEY,
context.getHandler(),
) || [];
const request = context.switchToHttp().getRequest();
const user = request.user.user;
const workspace = request.user.workspace;
const ability = this.caslAbilityFactory.createForUser(user, workspace);
return policyHandlers.every((handler) =>
this.execPolicyHandler(handler, ability),
);
}
private execPolicyHandler(handler: PolicyHandler, ability: AppAbility) {
if (typeof handler === 'function') {
return handler(ability);
}
return handler.handle(ability);
}
}
@@ -1,9 +0,0 @@
import { AppAbility } from '../abilities/casl-ability.factory';
interface IPolicyHandler {
handle(ability: AppAbility): boolean;
}
type PolicyHandlerCallback = (ability: AppAbility) => boolean;
export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback;
@@ -11,7 +11,7 @@ export enum SpaceCaslSubject {
Page = 'page',
}
export type SpaceAbility =
export type ISpaceAbility =
| [SpaceCaslAction, SpaceCaslSubject.Settings]
| [SpaceCaslAction, SpaceCaslSubject.Member]
| [SpaceCaslAction, SpaceCaslSubject.Page];
@@ -0,0 +1,21 @@
export enum WorkspaceCaslAction {
Manage = 'manage',
Create = 'create',
Read = 'read',
Edit = 'edit',
Delete = 'delete',
}
export enum WorkspaceCaslSubject {
Settings = 'settings',
Member = 'member',
Space = 'space',
Group = 'group',
Attachment = 'attachment',
}
export type IWorkspaceAbility =
| [WorkspaceCaslAction, WorkspaceCaslSubject.Settings]
| [WorkspaceCaslAction, WorkspaceCaslSubject.Member]
| [WorkspaceCaslAction, WorkspaceCaslSubject.Space]
| [WorkspaceCaslAction, WorkspaceCaslSubject.Group]
| [WorkspaceCaslAction, WorkspaceCaslSubject.Attachment];