From 31439c3b06b04996368639769b3b039563a6fc12 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Fri, 20 Mar 2026 17:05:40 +0530 Subject: [PATCH] feat: Add SUPER_ADMIN role with dedicated admin user management and updated access controls for existing admin features. --- prisma/schema.prisma | 1 + prisma/seed.ts | 2 +- src/auth/guards/roles.guard.ts | 6 ++- src/messages/messages.gateway.ts | 2 +- src/users/users.controller.ts | 30 ++++++++++++++ src/users/users.service.ts | 67 +++++++++++++++++++++++++++++++- 6 files changed, 104 insertions(+), 4 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1303dcb..d6b13af 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -17,6 +17,7 @@ enum UserRole { USER AGENT ADMIN + SUPER_ADMIN } enum UserStatus { diff --git a/prisma/seed.ts b/prisma/seed.ts index 5b92339..ff59e7b 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1336,7 +1336,7 @@ async function main() { data: { email: adminEmail, password: hashedPassword, - role: UserRole.ADMIN, + role: UserRole.SUPER_ADMIN, status: UserStatus.ACTIVE, emailVerified: true, emailVerifiedAt: new Date(), diff --git a/src/auth/guards/roles.guard.ts b/src/auth/guards/roles.guard.ts index 71ec958..2391ecc 100644 --- a/src/auth/guards/roles.guard.ts +++ b/src/auth/guards/roles.guard.ts @@ -33,7 +33,11 @@ export class RolesGuard implements CanActivate { throw new ForbiddenException('Access denied'); } - const hasRole = requiredRoles.some((role) => user.role === role); + // SUPER_ADMIN has all ADMIN privileges + const hasRole = requiredRoles.some((role) => + user.role === role || + (role === UserRole.ADMIN && user.role === UserRole.SUPER_ADMIN) + ); if (!hasRole) { throw new ForbiddenException( diff --git a/src/messages/messages.gateway.ts b/src/messages/messages.gateway.ts index b7d9c30..a236fed 100644 --- a/src/messages/messages.gateway.ts +++ b/src/messages/messages.gateway.ts @@ -85,7 +85,7 @@ export class MessagesGateway implements OnGatewayConnection, OnGatewayDisconnect this.logger.log(`Client connected successfully: ${client.id} (User: ${userId}, Role: ${payload.role})`); // Auto-join admin users to admin_room for support chat notifications - if (payload.role === 'ADMIN') { + if (payload.role === 'ADMIN' || payload.role === 'SUPER_ADMIN') { client.join('admin_room'); this.logger.log(`Admin ${userId} joined admin_room`); } diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 6be1c2e..fbc657d 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -329,4 +329,34 @@ export class UsersController { ) { return this.usersService.updateVerification(id, data, admin.id); } + + // ========================================== + // ADMIN MANAGEMENT (SUPER_ADMIN only) + // ========================================== + + @Get('admin/admins') + @Roles(UserRole.SUPER_ADMIN) + @ApiOperation({ summary: 'Super Admin: Get all admin users' }) + async getAdminUsers() { + return this.usersService.getAdminUsers(); + } + + @Put('admin/admins') + @Roles(UserRole.SUPER_ADMIN) + @ApiOperation({ summary: 'Super Admin: Create a new admin user' }) + async createAdminUser( + @Body() dto: { email: string; password: string }, + ) { + return this.usersService.createAdminUser(dto.email, dto.password); + } + + @Delete('admin/admins/:id') + @Roles(UserRole.SUPER_ADMIN) + @ApiOperation({ summary: 'Super Admin: Delete an admin user' }) + async deleteAdminUser( + @Param('id') id: string, + @CurrentUser('id') requesterId: string, + ) { + return this.usersService.deleteAdminUser(id, requesterId); + } } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 1538ce5..1a66aa6 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,6 +1,7 @@ -import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common'; +import { Injectable, BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common'; import { UserStatus, UserRole, Prisma, VerificationStatus } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; +import * as argon2 from 'argon2'; type UserWithProfiles = Prisma.UserGetPayload<{ include: { userProfile: true; agentProfile: { include: { agentType: true } } }; @@ -519,4 +520,68 @@ export class UsersService { return { message: 'Account deleted successfully' }; } + + // ============================================= + // ADMIN MANAGEMENT (SUPER_ADMIN only) + // ============================================= + + async getAdminUsers() { + return this.prisma.user.findMany({ + where: { role: { in: ['ADMIN', 'SUPER_ADMIN'] } }, + select: { + id: true, + email: true, + role: true, + status: true, + createdAt: true, + lastLoginAt: true, + }, + orderBy: { createdAt: 'desc' }, + }); + } + + async createAdminUser(email: string, password: string) { + const existing = await this.prisma.user.findUnique({ where: { email } }); + if (existing) { + throw new BadRequestException('A user with this email already exists'); + } + + const hashedPassword = await argon2.hash(password); + return this.prisma.user.create({ + data: { + email, + password: hashedPassword, + role: 'ADMIN', + status: 'ACTIVE', + emailVerified: true, + emailVerifiedAt: new Date(), + authProvider: 'LOCAL', + }, + select: { + id: true, + email: true, + role: true, + status: true, + createdAt: true, + }, + }); + } + + async deleteAdminUser(id: string, requesterId: string) { + if (id === requesterId) { + throw new BadRequestException('You cannot delete your own account'); + } + + const user = await this.prisma.user.findUnique({ where: { id } }); + if (!user) throw new NotFoundException('User not found'); + if (user.role === 'SUPER_ADMIN') { + throw new ForbiddenException('Cannot delete a super admin'); + } + if (user.role !== 'ADMIN') { + throw new BadRequestException('This user is not an admin'); + } + + await this.prisma.user.delete({ where: { id } }); + return null; + } }