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

View File

@ -47,7 +47,7 @@ export class AuthenticationExtension implements Extension {
const page = await this.pageRepo.findById(pageId);
if (!page) {
this.logger.warn(`Page not found: ${pageId}}`);
this.logger.warn(`Page not found: ${pageId}`);
throw new NotFoundException('Page not found');
}
@ -59,13 +59,13 @@ export class AuthenticationExtension implements Extension {
const userSpaceRole = findHighestUserSpaceRole(userSpaceRoles);
if (!userSpaceRole) {
this.logger.warn(`User authorized to access page: ${pageId}}`);
this.logger.warn(`User not authorized to access page: ${pageId}`);
throw new UnauthorizedException();
}
if (userSpaceRole === SpaceRole.READER) {
data.connection.readOnly = true;
this.logger.warn(`User granted readonly access to page: ${pageId}}`);
this.logger.debug(`User granted readonly access to page: ${pageId}`);
}
this.logger.debug(`Authenticated user ${user.id} on page ${pageId}`);

View File

@ -57,7 +57,7 @@ export class PersistenceExtension implements Extension {
return ydoc;
}
this.logger.debug(`creating fresh ydoc': ${pageId}`);
this.logger.debug(`creating fresh ydoc: ${pageId}`);
return new Y.Doc();
}

View File

@ -33,13 +33,16 @@ import {
MAX_AVATAR_SIZE,
MAX_FILE_SIZE,
} from './attachment.constants';
import CaslAbilityFactory from '../casl/abilities/casl-ability.factory';
import {
SpaceCaslAction,
SpaceCaslSubject,
} from '../casl/interfaces/space-ability.type';
import { Action } from '../casl/ability.action';
import SpaceAbilityFactory from '../casl/abilities/space-ability.factory';
import {
WorkspaceCaslAction,
WorkspaceCaslSubject,
} from '../casl/interfaces/workspace-ability.type';
import WorkspaceAbilityFactory from '../casl/abilities/workspace-ability.factory';
@Controller('attachments')
export class AttachmentController {
@ -48,7 +51,7 @@ export class AttachmentController {
constructor(
private readonly attachmentService: AttachmentService,
private readonly storageService: StorageService,
private readonly caslAbility: CaslAbilityFactory,
private readonly workspaceAbility: WorkspaceAbilityFactory,
private readonly spaceAbility: SpaceAbilityFactory,
) {}
@ -155,8 +158,13 @@ export class AttachmentController {
}
if (attachmentType === AttachmentType.WorkspaceLogo) {
const ability = this.caslAbility.createForUser(user, workspace);
if (ability.cannot(Action.Manage, 'Workspace')) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(
WorkspaceCaslAction.Manage,
WorkspaceCaslSubject.Settings,
)
) {
throw new ForbiddenException();
}
}

View File

@ -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>,
});
}
}

View File

@ -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);

View File

@ -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();
}

View File

@ -1,7 +0,0 @@
export enum Action {
Manage = 'manage',
Create = 'create',
Read = 'read',
Update = 'update',
Delete = 'delete',
}

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 {}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;

View File

@ -11,7 +11,7 @@ export enum SpaceCaslSubject {
Page = 'page',
}
export type SpaceAbility =
export type ISpaceAbility =
| [SpaceCaslAction, SpaceCaslSubject.Settings]
| [SpaceCaslAction, SpaceCaslSubject.Member]
| [SpaceCaslAction, SpaceCaslSubject.Page];

View File

@ -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];

View File

@ -5,6 +5,7 @@ import {
UseGuards,
HttpCode,
HttpStatus,
ForbiddenException,
} from '@nestjs/common';
import { GroupService } from './services/group.service';
import { CreateGroupDto } from './dto/create-group.dto';
@ -16,12 +17,13 @@ import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
import { AddGroupUserDto } from './dto/add-group-user.dto';
import { RemoveGroupUserDto } from './dto/remove-group-user.dto';
import { UpdateGroupDto } from './dto/update-group.dto';
import { Action } from '../casl/ability.action';
import { PoliciesGuard } from '../casl/guards/policies.guard';
import { CheckPolicies } from '../casl/decorators/policies.decorator';
import { AppAbility } from '../casl/abilities/casl-ability.factory';
import { JwtAuthGuard } from '../../guards/jwt-auth.guard';
import { User, Workspace } from '@docmost/db/types/entity.types';
import WorkspaceAbilityFactory from '../casl/abilities/workspace-ability.factory';
import {
WorkspaceCaslAction,
WorkspaceCaslSubject,
} from '../casl/interfaces/workspace-ability.type';
@UseGuards(JwtAuthGuard)
@Controller('groups')
@ -29,10 +31,9 @@ export class GroupController {
constructor(
private readonly groupService: GroupService,
private readonly groupUserService: GroupUserService,
private readonly workspaceAbility: WorkspaceAbilityFactory,
) {}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => ability.can(Action.Read, 'Group'))
@HttpCode(HttpStatus.OK)
@Post('/')
getWorkspaceGroups(
@ -40,11 +41,14 @@ export class GroupController {
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (ability.cannot(WorkspaceCaslAction.Read, WorkspaceCaslSubject.Group)) {
throw new ForbiddenException();
}
return this.groupService.getWorkspaceGroups(workspace.id, pagination);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => ability.can(Action.Read, 'Group'))
@HttpCode(HttpStatus.OK)
@Post('/info')
getGroup(
@ -52,11 +56,13 @@ export class GroupController {
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (ability.cannot(WorkspaceCaslAction.Read, WorkspaceCaslSubject.Group)) {
throw new ForbiddenException();
}
return this.groupService.getGroupInfo(groupIdDto.groupId, workspace.id);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => ability.can(Action.Manage, 'Group'))
@HttpCode(HttpStatus.OK)
@Post('create')
createGroup(
@ -64,11 +70,15 @@ export class GroupController {
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Group)
) {
throw new ForbiddenException();
}
return this.groupService.createGroup(user, workspace.id, createGroupDto);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => ability.can(Action.Manage, 'Group'))
@HttpCode(HttpStatus.OK)
@Post('update')
updateGroup(
@ -76,18 +86,29 @@ export class GroupController {
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Group)
) {
throw new ForbiddenException();
}
return this.groupService.updateGroup(workspace.id, updateGroupDto);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => ability.can(Action.Read, 'GroupUser'))
@HttpCode(HttpStatus.OK)
@Post('members')
getGroupMembers(
@Body() groupIdDto: GroupIdDto,
@Body() pagination: PaginationOptions,
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (ability.cannot(WorkspaceCaslAction.Read, WorkspaceCaslSubject.Group)) {
throw new ForbiddenException();
}
return this.groupUserService.getGroupUsers(
groupIdDto.groupId,
workspace.id,
@ -95,10 +116,6 @@ export class GroupController {
);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) =>
ability.can(Action.Manage, 'GroupUser'),
)
@HttpCode(HttpStatus.OK)
@Post('members/add')
addGroupMember(
@ -106,6 +123,13 @@ export class GroupController {
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Group)
) {
throw new ForbiddenException();
}
return this.groupUserService.addUsersToGroupBatch(
addGroupUserDto.userIds,
addGroupUserDto.groupId,
@ -113,17 +137,20 @@ export class GroupController {
);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) =>
ability.can(Action.Manage, 'GroupUser'),
)
@HttpCode(HttpStatus.OK)
@Post('members/remove')
removeGroupMember(
@Body() removeGroupUserDto: RemoveGroupUserDto,
//@AuthUser() user: User,
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Group)
) {
throw new ForbiddenException();
}
return this.groupUserService.removeUserFromGroup(
removeGroupUserDto.userId,
removeGroupUserDto.groupId,
@ -131,8 +158,6 @@ export class GroupController {
);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => ability.can(Action.Manage, 'Group'))
@HttpCode(HttpStatus.OK)
@Post('delete')
deleteGroup(
@ -140,6 +165,12 @@ export class GroupController {
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Group)
) {
throw new ForbiddenException();
}
return this.groupService.deleteGroup(groupIdDto.groupId, workspace.id);
}
}

View File

@ -106,7 +106,7 @@ export class PageController {
}
const ability = await this.spaceAbility.createForUser(user, page.spaceId);
if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) {
if (ability.cannot(SpaceCaslAction.Manage, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
}
await this.pageService.forceDelete(pageIdDto.pageId);

View File

@ -1,7 +1,7 @@
import { IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
export class CreateSpaceDto {
@MinLength(4)
@MinLength(2)
@MaxLength(64)
@IsString()
name: string;
@ -10,7 +10,7 @@ export class CreateSpaceDto {
@IsString()
description?: string;
@MinLength(4)
@MinLength(2)
@MaxLength(64)
@IsString()
slug: string;

View File

@ -48,10 +48,6 @@ export class SpaceService {
updateSpaceDto: UpdateSpaceDto,
workspaceId: string,
): Promise<Space> {
if (!updateSpaceDto.name && !updateSpaceDto.description) {
throw new BadRequestException('Please provide fields to update');
}
return await this.spaceRepo.updateSpace(
{
name: updateSpaceDto.name,

View File

@ -26,6 +26,8 @@ import {
SpaceCaslSubject,
} from '../casl/interfaces/space-ability.type';
import { UpdateSpaceDto } from './dto/update-space.dto';
import { findHighestUserSpaceRole } from '@docmost/db/repos/space/utils';
import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
@UseGuards(JwtAuthGuard)
@Controller('spaces')
@ -33,6 +35,7 @@ export class SpaceController {
constructor(
private readonly spaceService: SpaceService,
private readonly spaceMemberService: SpaceMemberService,
private readonly spaceMemberRepo: SpaceMemberRepo,
private readonly spaceAbility: SpaceAbilityFactory,
) {}
@ -67,7 +70,20 @@ export class SpaceController {
throw new ForbiddenException();
}
return space;
const userSpaceRoles = await this.spaceMemberRepo.getUserSpaceRoles(
user.id,
space.id,
);
const userSpaceRole = findHighestUserSpaceRole(userSpaceRoles);
const membership = {
userId: user.id,
role: userSpaceRole,
permissions: ability.rules,
};
return { ...space, membership };
}
@HttpCode(HttpStatus.OK)

View File

@ -1,6 +1,7 @@
import {
Body,
Controller,
ForbiddenException,
HttpCode,
HttpStatus,
Post,
@ -21,12 +22,13 @@ import {
InviteUserDto,
RevokeInviteDto,
} from '../dto/invitation.dto';
import { Action } from '../../casl/ability.action';
import { CheckPolicies } from '../../casl/decorators/policies.decorator';
import { AppAbility } from '../../casl/abilities/casl-ability.factory';
import { PoliciesGuard } from '../../casl/guards/policies.guard';
import { JwtAuthGuard } from '../../../guards/jwt-auth.guard';
import { User, Workspace } from '@docmost/db/types/entity.types';
import WorkspaceAbilityFactory from '../../casl/abilities/workspace-ability.factory';
import {
WorkspaceCaslAction,
WorkspaceCaslSubject,
} from '../../casl/interfaces/workspace-ability.type';
@UseGuards(JwtAuthGuard)
@Controller('workspace')
@ -34,12 +36,13 @@ export class WorkspaceController {
constructor(
private readonly workspaceService: WorkspaceService,
private readonly workspaceInvitationService: WorkspaceInvitationService,
private readonly workspaceAbility: WorkspaceAbilityFactory,
) {}
@Public()
@HttpCode(HttpStatus.OK)
@Post('/public')
async getWorkspacePublicInfo(@Req() req) {
async getWorkspacePublicInfo(@Req() req: any) {
return this.workspaceService.getWorkspacePublicData(req.raw.workspaceId);
}
@ -49,72 +52,89 @@ export class WorkspaceController {
return this.workspaceService.getWorkspaceInfo(workspace.id);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) =>
ability.can(Action.Manage, 'Workspace'),
)
@HttpCode(HttpStatus.OK)
@Post('update')
async updateWorkspace(
@Body() updateWorkspaceDto: UpdateWorkspaceDto,
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Settings)
) {
throw new ForbiddenException();
}
return this.workspaceService.update(workspace.id, updateWorkspaceDto);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) =>
ability.can(Action.Read, 'WorkspaceUser'),
)
@HttpCode(HttpStatus.OK)
@Post('members')
async getWorkspaceMembers(
@Body()
pagination: PaginationOptions,
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (ability.cannot(WorkspaceCaslAction.Read, WorkspaceCaslSubject.Member)) {
throw new ForbiddenException();
}
return this.workspaceService.getWorkspaceUsers(workspace.id, pagination);
}
@UseGuards(PoliciesGuard)
// @CheckPolicies((ability: AppAbility) =>
// ability.can(Action.Manage, 'WorkspaceUser'),
// )
@HttpCode(HttpStatus.OK)
@Post('members/deactivate')
async deactivateWorkspaceMember() {
async deactivateWorkspaceMember(
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Member)
) {
throw new ForbiddenException();
}
return this.workspaceService.deactivateUser();
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) =>
ability.can(Action.Manage, 'WorkspaceUser'),
)
@HttpCode(HttpStatus.OK)
@Post('members/change-role')
async updateWorkspaceMemberRole(
@Body() workspaceUserRoleDto: UpdateWorkspaceUserRoleDto,
@AuthUser() authUser: User,
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Member)
) {
throw new ForbiddenException();
}
return this.workspaceService.updateWorkspaceUserRole(
authUser,
user,
workspaceUserRoleDto,
workspace.id,
);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) =>
ability.can(Action.Read, 'WorkspaceUser'),
)
@HttpCode(HttpStatus.OK)
@Post('invites')
async getInvitations(
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
@Body()
pagination: PaginationOptions,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (ability.cannot(WorkspaceCaslAction.Read, WorkspaceCaslSubject.Member)) {
throw new ForbiddenException();
}
return this.workspaceInvitationService.getInvitations(
workspace.id,
pagination,
@ -131,50 +151,61 @@ export class WorkspaceController {
);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) =>
ability.can(Action.Manage, 'WorkspaceUser'),
)
@HttpCode(HttpStatus.OK)
@Post('invites/create')
async inviteUser(
@Body() inviteUserDto: InviteUserDto,
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
@AuthUser() authUser: User,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Member)
) {
throw new ForbiddenException();
}
return this.workspaceInvitationService.createInvitation(
inviteUserDto,
workspace.id,
authUser,
user,
);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) =>
ability.can(Action.Manage, 'WorkspaceUser'),
)
@HttpCode(HttpStatus.OK)
@Post('invites/resend')
async resendInvite(
@Body() revokeInviteDto: RevokeInviteDto,
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Member)
) {
throw new ForbiddenException();
}
return this.workspaceInvitationService.resendInvitation(
revokeInviteDto.invitationId,
workspace.id,
);
}
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) =>
ability.can(Action.Manage, 'WorkspaceUser'),
)
@HttpCode(HttpStatus.OK)
@Post('invites/revoke')
async revokeInvite(
@Body() revokeInviteDto: RevokeInviteDto,
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = this.workspaceAbility.createForUser(user, workspace);
if (
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Member)
) {
throw new ForbiddenException();
}
return this.workspaceInvitationService.revokeInvitation(
revokeInviteDto.invitationId,
workspace.id,