mirror of
https://github.com/docmost/docmost.git
synced 2026-05-21 09:14:07 +08:00
feat: role authorizations - WIP
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
AbilityBuilder,
|
||||
createMongoAbility,
|
||||
ExtractSubjectType,
|
||||
InferSubjects,
|
||||
MongoAbility,
|
||||
} from '@casl/ability';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { Action } from '../ability.action';
|
||||
import { Workspace } from '../../workspace/entities/workspace.entity';
|
||||
import { WorkspaceUser } from '../../workspace/entities/workspace-user.entity';
|
||||
import { WorkspaceInvitation } from '../../workspace/entities/workspace-invitation.entity';
|
||||
import { Role } from '../../../helpers/types/permission';
|
||||
import { Group } from '../../group/entities/group.entity';
|
||||
import { GroupUser } from '../../group/entities/group-user.entity';
|
||||
import { Attachment } from '../../attachment/entities/attachment.entity';
|
||||
import { Space } from '../../space/entities/space.entity';
|
||||
import { SpaceUser } from '../../space/entities/space-user.entity';
|
||||
import { Page } from '../../page/entities/page.entity';
|
||||
import { Comment } from '../../comment/entities/comment.entity';
|
||||
|
||||
export type Subjects =
|
||||
| InferSubjects<
|
||||
| typeof Workspace
|
||||
| typeof WorkspaceUser
|
||||
| typeof WorkspaceInvitation
|
||||
| typeof Space
|
||||
| typeof SpaceUser
|
||||
| typeof Group
|
||||
| typeof GroupUser
|
||||
| typeof Attachment
|
||||
| typeof Comment
|
||||
| typeof Page
|
||||
| typeof User
|
||||
>
|
||||
| 'all';
|
||||
export type AppAbility = MongoAbility<[Action, Subjects]>;
|
||||
|
||||
@Injectable()
|
||||
export default class CaslAbilityFactory {
|
||||
createForWorkspace(user: User, workspace: Workspace) {
|
||||
const { can, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
|
||||
|
||||
const userRole = workspace?.workspaceUser.role;
|
||||
console.log(userRole);
|
||||
|
||||
if (userRole === Role.OWNER) {
|
||||
// Workspace Users
|
||||
can<any>([Action.Manage], Workspace);
|
||||
can<any>([Action.Manage], WorkspaceUser);
|
||||
can<any>([Action.Manage], WorkspaceInvitation);
|
||||
|
||||
// Groups
|
||||
can<any>([Action.Manage], Group);
|
||||
can<any>([Action.Manage], GroupUser);
|
||||
|
||||
// Attachments
|
||||
can<any>([Action.Manage], Attachment);
|
||||
}
|
||||
|
||||
if (userRole === Role.MEMBER) {
|
||||
can<any>([Action.Read], WorkspaceUser);
|
||||
|
||||
// Groups
|
||||
can<any>([Action.Read], Group);
|
||||
can<any>([Action.Read], GroupUser);
|
||||
|
||||
// Attachments
|
||||
can<any>([Action.Read, Action.Create], Attachment);
|
||||
}
|
||||
|
||||
return build({
|
||||
detectSubjectType: (item) =>
|
||||
item.constructor as ExtractSubjectType<Subjects>,
|
||||
});
|
||||
}
|
||||
|
||||
createForUser(user: User) {
|
||||
const { can, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
|
||||
|
||||
can<any>([Action.Manage], User, { id: user.id });
|
||||
can<any>([Action.Read], User);
|
||||
|
||||
return build({
|
||||
detectSubjectType: (item) =>
|
||||
item.constructor as ExtractSubjectType<Subjects>,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum Action {
|
||||
Manage = 'manage',
|
||||
Create = 'create',
|
||||
Read = 'read',
|
||||
Update = 'update',
|
||||
Delete = 'delete',
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import CaslAbilityFactory from './abilities/casl-ability.factory';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [CaslAbilityFactory],
|
||||
exports: [CaslAbilityFactory],
|
||||
})
|
||||
export class CaslModule {}
|
||||
@@ -0,0 +1,6 @@
|
||||
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);
|
||||
@@ -0,0 +1,40 @@
|
||||
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.createForWorkspace(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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { AppAbility } from '../abilities/casl-ability.factory';
|
||||
|
||||
interface IPolicyHandler {
|
||||
handle(ability: AppAbility): boolean;
|
||||
}
|
||||
|
||||
type PolicyHandlerCallback = (ability: AppAbility) => boolean;
|
||||
|
||||
export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback;
|
||||
Reference in New Issue
Block a user