This commit is contained in:
Philipinho
2026-05-27 10:43:43 +01:00
parent 830b5b4d45
commit 41aaeddac4
5 changed files with 27 additions and 7 deletions
@@ -4,6 +4,11 @@ export enum UserRole {
MEMBER = 'member', MEMBER = 'member',
} }
export enum InviteUserRole {
ADMIN = 'admin', // can have owner permissions but cannot delete workspace
MEMBER = 'member',
}
export enum SpaceRole { export enum SpaceRole {
ADMIN = 'admin', // can manage space settings, members, and delete space ADMIN = 'admin', // can manage space settings, members, and delete space
WRITER = 'writer', // can read and write pages in space WRITER = 'writer', // can read and write pages in space
@@ -11,7 +11,7 @@ import {
MaxLength, MaxLength,
MinLength, MinLength,
} from 'class-validator'; } from 'class-validator';
import { UserRole } from '../../../common/helpers/types/permission'; import { InviteUserRole } from '../../../common/helpers/types/permission';
import { NoUrls } from '../../../common/validators/no-urls.validator'; import { NoUrls } from '../../../common/validators/no-urls.validator';
export class InviteUserDto { export class InviteUserDto {
@@ -32,7 +32,7 @@ export class InviteUserDto {
@IsUUID('all', { each: true }) @IsUUID('all', { each: true })
groupIds: string[]; groupIds: string[];
@IsEnum(UserRole) @IsEnum(InviteUserRole)
role: string; role: string;
} }
@@ -1,5 +1,6 @@
import { import {
BadRequestException, BadRequestException,
ForbiddenException,
Inject, Inject,
Injectable, Injectable,
Logger, Logger,
@@ -40,6 +41,7 @@ import {
AUDIT_SERVICE, AUDIT_SERVICE,
IAuditService, IAuditService,
} from '../../../integrations/audit/audit.service'; } from '../../../integrations/audit/audit.service';
import { isAdminActingOnOwner } from '../workspace.util';
@Injectable() @Injectable()
export class WorkspaceInvitationService { export class WorkspaceInvitationService {
@@ -119,6 +121,10 @@ export class WorkspaceInvitationService {
): Promise<void> { ): Promise<void> {
const { emails, role, groupIds } = inviteUserDto; const { emails, role, groupIds } = inviteUserDto;
if (isAdminActingOnOwner(authUser.role, role)) {
throw new ForbiddenException();
}
let invites: WorkspaceInvitation[] = []; let invites: WorkspaceInvitation[] = [];
try { try {
@@ -30,6 +30,7 @@ import { DomainService } from '../../../integrations/environment/domain.service'
import { jsonArrayFrom } from 'kysely/helpers/postgres'; import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { addDays } from 'date-fns'; import { addDays } from 'date-fns';
import { DISALLOWED_HOSTNAMES, WorkspaceStatus } from '../workspace.constants'; import { DISALLOWED_HOSTNAMES, WorkspaceStatus } from '../workspace.constants';
import { isAdminActingOnOwner } from '../workspace.util';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { InjectQueue } from '@nestjs/bullmq'; import { InjectQueue } from '@nestjs/bullmq';
import { QueueJob, QueueName } from '../../../integrations/queue/constants'; import { QueueJob, QueueName } from '../../../integrations/queue/constants';
@@ -590,8 +591,8 @@ export class WorkspaceService {
// prevent ADMIN from managing OWNER role // prevent ADMIN from managing OWNER role
if ( if (
(authUser.role === UserRole.ADMIN && newRole === UserRole.OWNER) || isAdminActingOnOwner(authUser.role, newRole) ||
(authUser.role === UserRole.ADMIN && user.role === UserRole.OWNER) isAdminActingOnOwner(authUser.role, user.role)
) { ) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
@@ -695,7 +696,7 @@ export class WorkspaceService {
throw new BadRequestException('You cannot deactivate yourself'); throw new BadRequestException('You cannot deactivate yourself');
} }
if (authUser.role === UserRole.ADMIN && user.role === UserRole.OWNER) { if (isAdminActingOnOwner(authUser.role, user.role)) {
throw new BadRequestException( throw new BadRequestException(
'You cannot deactivate a user with owner role', 'You cannot deactivate a user with owner role',
); );
@@ -753,7 +754,7 @@ export class WorkspaceService {
throw new BadRequestException('User is not deactivated'); throw new BadRequestException('User is not deactivated');
} }
if (authUser.role === UserRole.ADMIN && user.role === UserRole.OWNER) { if (isAdminActingOnOwner(authUser.role, user.role)) {
throw new BadRequestException( throw new BadRequestException(
'You cannot activate a user with owner role', 'You cannot activate a user with owner role',
); );
@@ -805,7 +806,7 @@ export class WorkspaceService {
throw new BadRequestException('You cannot delete yourself'); throw new BadRequestException('You cannot delete yourself');
} }
if (authUser.role === UserRole.ADMIN && user.role === UserRole.OWNER) { if (isAdminActingOnOwner(authUser.role, user.role)) {
throw new BadRequestException('You cannot delete a user with owner role'); throw new BadRequestException('You cannot delete a user with owner role');
} }
@@ -0,0 +1,8 @@
import { UserRole } from '../../common/helpers/types/permission';
export function isAdminActingOnOwner(
authUserRole: string,
targetRole: string,
): boolean {
return authUserRole === UserRole.ADMIN && targetRole === UserRole.OWNER;
}