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/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/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/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, 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, 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 0c3b259..f7d64a0 100644 --- a/src/guards/admin.role.guard.ts +++ b/src/guards/admin.role.guard.ts @@ -4,10 +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.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 b21632a..59abad8 100644 --- a/src/guards/user.role.guard.ts +++ b/src/guards/user.role.guard.ts @@ -4,12 +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.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/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); } 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; }