feat: role authorizations - WIP

This commit is contained in:
Philipinho
2024-03-08 23:55:42 +00:00
parent 3e174b3838
commit b42fe48e9b
15 changed files with 263 additions and 14 deletions
@@ -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',
}
+9
View File
@@ -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;