From c30a21b04f5d81042e6952a01774f4b10721e864 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 7 May 2024 09:48:16 +0300 Subject: [PATCH 1/5] Add Unique constraint to user and roleType fields in UserRoleEntity --- libs/common/src/modules/user-role/entities/user.role.entity.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/common/src/modules/user-role/entities/user.role.entity.ts b/libs/common/src/modules/user-role/entities/user.role.entity.ts index e375723..34b49c4 100644 --- a/libs/common/src/modules/user-role/entities/user.role.entity.ts +++ b/libs/common/src/modules/user-role/entities/user.role.entity.ts @@ -1,10 +1,11 @@ -import { Entity, ManyToOne } from 'typeorm'; +import { Entity, ManyToOne, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { UserRoleDto } from '../dtos'; import { UserEntity } from '../../user/entities'; import { RoleTypeEntity } from '../../role-type/entities'; @Entity({ name: 'user-role' }) +@Unique(['user', 'roleType']) export class UserRoleEntity extends AbstractEntity { @ManyToOne(() => UserEntity, (user) => user.role, { nullable: false, From d1fa15cff1cfbd6716799fc60f6578353abae2ec Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 11 May 2024 21:01:39 +0300 Subject: [PATCH 2/5] Update role types in enums and role guards --- libs/common/src/constants/role.type.enum.ts | 3 ++- src/guards/admin.role.guard.ts | 5 ++++- src/guards/user.role.guard.ts | 5 ++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libs/common/src/constants/role.type.enum.ts b/libs/common/src/constants/role.type.enum.ts index 34b3929..72bf14e 100644 --- a/libs/common/src/constants/role.type.enum.ts +++ b/libs/common/src/constants/role.type.enum.ts @@ -1,4 +1,5 @@ export enum RoleType { - USER = 'USER', + SUPER_ADMIN = 'SUPER_ADMIN', ADMIN = 'ADMIN', + USER = 'USER', } diff --git a/src/guards/admin.role.guard.ts b/src/guards/admin.role.guard.ts index 0c3b259..fc26067 100644 --- a/src/guards/admin.role.guard.ts +++ b/src/guards/admin.role.guard.ts @@ -4,7 +4,10 @@ import { AuthGuard } from '@nestjs/passport'; export class AdminRoleGuard extends AuthGuard('jwt') { handleRequest(err, user) { - const isAdmin = user.roles.some((role) => role.type === RoleType.ADMIN); + const isAdmin = user.roles.some( + (role) => + role.type === RoleType.SUPER_ADMIN || role.type === RoleType.ADMIN, + ); if (err || !user) { throw err || new UnauthorizedException(); } else { diff --git a/src/guards/user.role.guard.ts b/src/guards/user.role.guard.ts index b21632a..4864fe0 100644 --- a/src/guards/user.role.guard.ts +++ b/src/guards/user.role.guard.ts @@ -5,7 +5,10 @@ import { AuthGuard } from '@nestjs/passport'; export class UserRoleGuard extends AuthGuard('jwt') { handleRequest(err, user) { const isUserOrAdmin = user.roles.some( - (role) => role.type === RoleType.ADMIN || role.type === RoleType.USER, + (role) => + role.type === RoleType.SUPER_ADMIN || + role.type === RoleType.ADMIN || + role.type === RoleType.USER, ); if (err || !user) { throw err || new UnauthorizedException(); From 3e9fff3822e44f8d855b0f9a8efdf7072283436b Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 11 May 2024 21:02:25 +0300 Subject: [PATCH 3/5] Add super admin configuration and service --- libs/common/src/config/index.ts | 3 +- libs/common/src/config/super.admin.config.ts | 9 +++ libs/common/src/helper/helper.module.ts | 25 ++++++- .../helper/services/super.admin.sarvice.ts | 72 +++++++++++++++++++ src/main.ts | 4 ++ 5 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 libs/common/src/config/super.admin.config.ts create mode 100644 libs/common/src/helper/services/super.admin.sarvice.ts diff --git a/libs/common/src/config/index.ts b/libs/common/src/config/index.ts index 94380ce..d4cbbdb 100644 --- a/libs/common/src/config/index.ts +++ b/libs/common/src/config/index.ts @@ -1,2 +1,3 @@ import emailConfig from './email.config'; -export default [emailConfig]; +import superAdminConfig from './super.admin.config'; +export default [emailConfig, superAdminConfig]; diff --git a/libs/common/src/config/super.admin.config.ts b/libs/common/src/config/super.admin.config.ts new file mode 100644 index 0000000..90a156e --- /dev/null +++ b/libs/common/src/config/super.admin.config.ts @@ -0,0 +1,9 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs( + 'super-admin', + (): Record => ({ + SUPER_ADMIN_EMAIL: process.env.SUPER_ADMIN_EMAIL, + SUPER_ADMIN_PASSWORD: process.env.SUPER_ADMIN_PASSWORD, + }), +); diff --git a/libs/common/src/helper/helper.module.ts b/libs/common/src/helper/helper.module.ts index 826883a..f5fc400 100644 --- a/libs/common/src/helper/helper.module.ts +++ b/libs/common/src/helper/helper.module.ts @@ -1,11 +1,30 @@ import { Global, Module } from '@nestjs/common'; import { HelperHashService } from './services'; +import { UserRepository } from '../modules/user/repositories'; +import { UserRepositoryModule } from '../modules/user/user.repository.module'; +import { UserRoleRepository } from '../modules/user-role/repositories'; +import { UserRoleRepositoryModule } from '../modules/user-role/user.role.repository.module'; +import { RoleTypeRepository } from '../modules/role-type/repositories'; +import { RoleTypeRepositoryModule } from '../modules/role-type/role.type.repository.module'; +import { ConfigModule } from '@nestjs/config'; +import { SuperAdminService } from './services/super.admin.sarvice'; @Global() @Module({ - providers: [HelperHashService], - exports: [HelperHashService], + providers: [ + HelperHashService, + SuperAdminService, + UserRepository, + UserRoleRepository, + RoleTypeRepository, + ], + exports: [HelperHashService, SuperAdminService], controllers: [], - imports: [], + imports: [ + ConfigModule.forRoot(), + UserRepositoryModule, + UserRoleRepositoryModule, + RoleTypeRepositoryModule, + ], }) export class HelperModule {} diff --git a/libs/common/src/helper/services/super.admin.sarvice.ts b/libs/common/src/helper/services/super.admin.sarvice.ts new file mode 100644 index 0000000..e9efbce --- /dev/null +++ b/libs/common/src/helper/services/super.admin.sarvice.ts @@ -0,0 +1,72 @@ +import { HelperHashService } from './helper.hash.service'; +import { Injectable } from '@nestjs/common'; +import { UserRepository } from '@app/common/modules/user/repositories'; +import { RoleType } from '@app/common/constants/role.type.enum'; +import { UserRoleRepository } from '@app/common/modules/user-role/repositories'; +import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class SuperAdminService { + constructor( + private readonly configService: ConfigService, + private readonly userRepository: UserRepository, + private readonly userRoleRepository: UserRoleRepository, + private readonly roleTypeRepository: RoleTypeRepository, + private readonly helperHashService: HelperHashService, + ) {} + + async createSuperAdminIfNotFound(): Promise { + try { + const superAdminData = await this.userRoleRepository.find({ + where: { roleType: { type: RoleType.SUPER_ADMIN } }, + relations: ['roleType'], + }); + + if (superAdminData.length <= 0) { + // Create the super admin user if not found + console.log('Creating super admin user...'); + + await this.createSuperAdmin(); + } + } catch (err) { + console.error('Error while checking super admin:', err); + throw err; + } + } + private async getRoleUuidByRoleType(roleType: string) { + const role = await this.roleTypeRepository.findOne({ + where: { type: roleType }, + }); + + return role.uuid; + } + private async createSuperAdmin(): Promise { + const salt = this.helperHashService.randomSalt(10); // Hash the password using bcrypt + const hashedPassword = await this.helperHashService.bcrypt( + this.configService.get('super-admin.SUPER_ADMIN_PASSWORD'), + salt, + ); + try { + const user = await this.userRepository.save({ + email: this.configService.get('super-admin.SUPER_ADMIN_EMAIL'), + password: hashedPassword, + firstName: 'Super', + lastName: 'Admin', + isUserVerified: true, + isActive: true, + }); + const defaultUserRoleUuid = await this.getRoleUuidByRoleType( + RoleType.SUPER_ADMIN, + ); + + await this.userRoleRepository.save({ + user: { uuid: user.uuid }, + roleType: { uuid: defaultUserRoleUuid }, + }); + } catch (err) { + console.error('Error while creating super admin:', err); + throw err; + } + } +} diff --git a/src/main.ts b/src/main.ts index 129f319..61dfb92 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ import rateLimit from 'express-rate-limit'; import helmet from 'helmet'; import { setupSwaggerAuthentication } from '../libs/common/src/util/user-auth.swagger.utils'; import { ValidationPipe } from '@nestjs/common'; +import { SuperAdminService } from '@app/common/helper/services/super.admin.sarvice'; async function bootstrap() { const app = await NestFactory.create(AuthModule); @@ -33,6 +34,9 @@ async function bootstrap() { }, }), ); + // Create super admin user + const superAdminService = app.get(SuperAdminService); + await superAdminService.createSuperAdminIfNotFound(); await app.listen(process.env.PORT || 4000); } From e7024a5cb86fd773d904306902046b8f0adee148 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 11 May 2024 21:02:35 +0300 Subject: [PATCH 4/5] Add Unique constraint to 'type' column in RoleTypeEntity --- libs/common/src/modules/role-type/entities/role.type.entity.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/common/src/modules/role-type/entities/role.type.entity.ts b/libs/common/src/modules/role-type/entities/role.type.entity.ts index c65db72..0621b7d 100644 --- a/libs/common/src/modules/role-type/entities/role.type.entity.ts +++ b/libs/common/src/modules/role-type/entities/role.type.entity.ts @@ -1,10 +1,11 @@ -import { Column, Entity, OneToMany } from 'typeorm'; +import { Column, Entity, OneToMany, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { RoleTypeDto } from '../dtos/role.type.dto'; import { RoleType } from '@app/common/constants/role.type.enum'; import { UserRoleEntity } from '../../user-role/entities'; @Entity({ name: 'role-type' }) +@Unique(['type']) export class RoleTypeEntity extends AbstractEntity { @Column({ nullable: false, From ea19361a5995a0009e3c30ce37bcec18bb7ca1a1 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 11 May 2024 21:19:53 +0300 Subject: [PATCH 5/5] Update role guards to differentiate between admin and super admin roles --- src/auth/controllers/user-auth.controller.ts | 6 +++--- src/guards/admin.role.guard.ts | 8 ++++---- src/guards/super.admin.role.guard.ts | 21 ++++++++++++++++++++ src/guards/user.role.guard.ts | 12 +++++------ src/role/controllers/role.controller.ts | 6 +++--- src/role/dtos/role.edit.dto.ts | 9 ++++++--- 6 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 src/guards/super.admin.role.guard.ts diff --git a/src/auth/controllers/user-auth.controller.ts b/src/auth/controllers/user-auth.controller.ts index fd139f9..86f9ce6 100644 --- a/src/auth/controllers/user-auth.controller.ts +++ b/src/auth/controllers/user-auth.controller.ts @@ -16,7 +16,7 @@ import { ResponseMessage } from '../../../libs/common/src/response/response.deco import { UserLoginDto } from '../dtos/user-login.dto'; import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos'; import { RefreshTokenGuard } from '@app/common/guards/jwt-refresh.auth.guard'; -import { AdminRoleGuard } from 'src/guards/admin.role.guard'; +import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; @Controller({ version: '1', @@ -52,7 +52,7 @@ export class UserAuthController { } @ApiBearerAuth() - @UseGuards(AdminRoleGuard) + @UseGuards(SuperAdminRoleGuard) @Delete('user/delete/:id') async userDelete(@Param('id') id: string) { await this.userAuthService.deleteUser(id); @@ -98,7 +98,7 @@ export class UserAuthController { } @ApiBearerAuth() - @UseGuards(AdminRoleGuard) + @UseGuards(SuperAdminRoleGuard) @Get('user/list') async userList() { const userList = await this.userAuthService.userList(); diff --git a/src/guards/admin.role.guard.ts b/src/guards/admin.role.guard.ts index fc26067..f7d64a0 100644 --- a/src/guards/admin.role.guard.ts +++ b/src/guards/admin.role.guard.ts @@ -4,13 +4,13 @@ import { AuthGuard } from '@nestjs/passport'; export class AdminRoleGuard extends AuthGuard('jwt') { handleRequest(err, user) { - const isAdmin = user.roles.some( - (role) => - role.type === RoleType.SUPER_ADMIN || role.type === RoleType.ADMIN, - ); if (err || !user) { throw err || new UnauthorizedException(); } else { + const isAdmin = user.roles.some( + (role) => + role.type === RoleType.SUPER_ADMIN || role.type === RoleType.ADMIN, + ); if (!isAdmin) { throw new BadRequestException('Only admin role can access this route'); } diff --git a/src/guards/super.admin.role.guard.ts b/src/guards/super.admin.role.guard.ts new file mode 100644 index 0000000..ef93a75 --- /dev/null +++ b/src/guards/super.admin.role.guard.ts @@ -0,0 +1,21 @@ +import { RoleType } from '@app/common/constants/role.type.enum'; +import { BadRequestException, UnauthorizedException } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +export class SuperAdminRoleGuard extends AuthGuard('jwt') { + handleRequest(err, user) { + if (err || !user) { + throw err || new UnauthorizedException(); + } else { + const isSuperAdmin = user.roles.some( + (role) => role.type === RoleType.SUPER_ADMIN, + ); + if (!isSuperAdmin) { + throw new BadRequestException( + 'Only super admin role can access this route', + ); + } + } + return user; + } +} diff --git a/src/guards/user.role.guard.ts b/src/guards/user.role.guard.ts index 4864fe0..59abad8 100644 --- a/src/guards/user.role.guard.ts +++ b/src/guards/user.role.guard.ts @@ -4,15 +4,15 @@ import { AuthGuard } from '@nestjs/passport'; export class UserRoleGuard extends AuthGuard('jwt') { handleRequest(err, user) { - const isUserOrAdmin = user.roles.some( - (role) => - role.type === RoleType.SUPER_ADMIN || - role.type === RoleType.ADMIN || - role.type === RoleType.USER, - ); if (err || !user) { throw err || new UnauthorizedException(); } else { + const isUserOrAdmin = user.roles.some( + (role) => + role.type === RoleType.SUPER_ADMIN || + role.type === RoleType.ADMIN || + role.type === RoleType.USER, + ); if (!isUserOrAdmin) { throw new BadRequestException( 'Only admin or user role can access this route', diff --git a/src/role/controllers/role.controller.ts b/src/role/controllers/role.controller.ts index ebabe93..2a6c917 100644 --- a/src/role/controllers/role.controller.ts +++ b/src/role/controllers/role.controller.ts @@ -11,7 +11,7 @@ import { import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { RoleService } from '../services/role.service'; import { UserRoleEditDto } from '../dtos'; -import { AdminRoleGuard } from 'src/guards/admin.role.guard'; +import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; @ApiTags('Role Module') @Controller({ @@ -21,7 +21,7 @@ import { AdminRoleGuard } from 'src/guards/admin.role.guard'; export class RoleController { constructor(private readonly roleService: RoleService) {} @ApiBearerAuth() - @UseGuards(AdminRoleGuard) + @UseGuards(SuperAdminRoleGuard) @Get('types') async fetchRoleTypes() { try { @@ -36,7 +36,7 @@ export class RoleController { } } @ApiBearerAuth() - @UseGuards(AdminRoleGuard) + @UseGuards(SuperAdminRoleGuard) @Put('edit/user/:userUuid') async editUserRoleType( @Param('userUuid') userUuid: string, diff --git a/src/role/dtos/role.edit.dto.ts b/src/role/dtos/role.edit.dto.ts index 5cd0aac..9e9b394 100644 --- a/src/role/dtos/role.edit.dto.ts +++ b/src/role/dtos/role.edit.dto.ts @@ -1,13 +1,16 @@ import { RoleType } from '@app/common/constants/role.type.enum'; import { ApiProperty } from '@nestjs/swagger'; -import { IsEnum } from 'class-validator'; +import { IsEnum, IsIn } from 'class-validator'; export class UserRoleEditDto { @ApiProperty({ - description: 'role type', - enum: RoleType, + description: 'Role type (USER or ADMIN)', + enum: [RoleType.USER, RoleType.ADMIN], required: true, }) @IsEnum(RoleType) + @IsIn([RoleType.USER, RoleType.ADMIN], { + message: 'roleType must be one of the following values: USER, ADMIN', + }) roleType: RoleType; }