diff --git a/backend/src/users/providers/update-member-status.provider.ts b/backend/src/users/providers/update-member-status.provider.ts index 371685de..e66a78b1 100644 --- a/backend/src/users/providers/update-member-status.provider.ts +++ b/backend/src/users/providers/update-member-status.provider.ts @@ -1,8 +1,9 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from '../entities/user.entity'; import { MembershipStatus } from '../enums/membership-status.enum'; +import { UserRole } from '../enums/userRoles.enum'; @Injectable() export class UpdateMemberStatusProvider { @@ -20,7 +21,19 @@ export class UpdateMemberStatusProvider { }); if (!user) { throw new NotFoundException(`Member with id "${memberId}" not found`); - } + + + if ( + user.role === UserRole.SUPER_ADMIN && + status !== MembershipStatus.ACTIVE + ) { + const superAdmins = await this.usersRepository.count({ + where: { role: UserRole.SUPER_ADMIN }, + }); + if (superAdmins <= 1) { + throw new ForbiddenException('Cannot demote the last super-admin'); + } + } } user.membershipStatus = status; diff --git a/backend/src/users/providers/users.service.ts b/backend/src/users/providers/users.service.ts index 94958e42..526f1b92 100644 --- a/backend/src/users/providers/users.service.ts +++ b/backend/src/users/providers/users.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { CreateUserDto } from '../dto/createUser.dto'; import { UpdateUserDto } from '../dto/updateUser.dto'; import { User } from '../entities/user.entity'; @@ -11,7 +11,7 @@ import { UpdateUserProvider } from './updateUser.provider'; import { DeleteUserProvider } from './deleteUser.provider'; import { AuthResponse } from '../../auth/interface/authResponse.interface'; import { Response } from 'express'; -import { NotFoundException } from '@nestjs/common'; +import { NotFoundException, ForbiddenException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { UploadProfilePictureProvider } from './uploadProfilePicture.provider'; @@ -32,7 +32,8 @@ import { AuditAction, AuditService } from '../../audit/audit.service'; export class UsersService { async updateTwoFactor(userId: string, data: { twoFactorEnabled: boolean }) { const user = await this.findUserById(userId); - if (!user) { + if ( + // BE-23 - Prevent self-demotion of the last super-admin account.!user) { throw new NotFoundException('User not found'); } user.twoFactorEnabled = data.twoFactorEnabled; @@ -127,6 +128,18 @@ export class UsersService { updateData.role !== undefined && previous.role !== updated.role ) { + if ( + previous.role === UserRole.SUPER_ADMIN && + updated.role !== UserRole.SUPER_ADMIN + ) { + const superAdmins = await this.usersRepository.count({ + where: { role: UserRole.SUPER_ADMIN }, + }); + if (superAdmins <= 1) { + throw new ForbiddenException('Cannot demote the last super-admin'); + } + } + await this.auditService.adminAction( AuditAction.ROLE_CHANGED, {}, // actor comes from the ALS context