From 857d10bf449a2e55341044488a30c24ea5cb2e15 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Fri, 24 Jul 2026 13:24:00 +0330 Subject: [PATCH] auth module --- src/app.module.ts | 2 +- src/common/enums/permission.enum.ts | 3 ++ .../admin/controllers/admin.controller.ts | 2 + src/modules/auth/auth.module.ts | 5 ++- src/modules/auth/guards/adminAuth.guard.ts | 33 ++++++-------- src/modules/auth/services/auth.service.ts | 44 ++++++++++++++++--- src/seeders/data/admins.data.ts | 12 ++--- 7 files changed, 67 insertions(+), 34 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 69367d7..e1ddc20 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -31,9 +31,9 @@ import { cacheConfig } from './config/cache.config'; ConfigModule.forRoot({ isGlobal: true, cache: true }), CacheModule.registerAsync(cacheConfig()), MikroOrmModule.forRootAsync(dataBaseConfig), + AuthModule, UserModule, UtilsModule, - AuthModule, UploaderModule, AdminModule, ThrottlerModule.forRoot([ diff --git a/src/common/enums/permission.enum.ts b/src/common/enums/permission.enum.ts index 8e7d2d6..4fa64d0 100644 --- a/src/common/enums/permission.enum.ts +++ b/src/common/enums/permission.enum.ts @@ -1,5 +1,7 @@ export enum PermissionEnum { + DASHBOARD = 'dashboard', + MANAGE_PRODUCTS = 'manage_products', MANAGE_CATEGORIES = 'manage_categories', @@ -60,6 +62,7 @@ export enum PermissionEnum { export const PermissionTitles: Record = { + [PermissionEnum.DASHBOARD]: 'مشاهده داشبورد', [PermissionEnum.MANAGE_PRODUCTS]: 'مدیریت محصولات', [PermissionEnum.MANAGE_CATEGORIES]: 'مدیریت دسته‌بندی‌ها', diff --git a/src/modules/admin/controllers/admin.controller.ts b/src/modules/admin/controllers/admin.controller.ts index b884167..3e5e40d 100644 --- a/src/modules/admin/controllers/admin.controller.ts +++ b/src/modules/admin/controllers/admin.controller.ts @@ -58,12 +58,14 @@ export class AdminController { } @Get('admin/home/stats') + @Permissions(PermissionEnum.DASHBOARD) @ApiOperation({ summary: 'Get home page stats for admin panel' }) getHomeStats() { return this.adminService.getHomeStats(); } @Get('admin/home/weekly-orders') + @Permissions(PermissionEnum.DASHBOARD) @ApiOperation({ summary: 'Get weekly order counts for admin home page chart' }) getWeeklyOrders() { return this.adminService.getWeeklyOrders(); diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index 47ff945..978afd0 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -1,4 +1,4 @@ -import { Module, forwardRef } from '@nestjs/common'; +import { Global, Module, forwardRef } from '@nestjs/common'; import { AuthService } from './services/auth.service'; import { AuthController } from './controllers/auth.controller'; import { UtilsModule } from '../util/utils.module'; @@ -13,6 +13,7 @@ import { RefreshToken } from './entities/refresh-token.entity'; import { NotificationsModule } from '../notification/notifications.module'; import { OtpService } from './services/otp.service'; +@Global() @Module({ imports: [ UtilsModule, @@ -38,6 +39,6 @@ import { OtpService } from './services/otp.service'; ], controllers: [AuthController], providers: [AuthService, TokensService, AdminAuthGuard, OtpService], - exports: [AdminAuthGuard], + exports: [AdminAuthGuard, AuthService], }) export class AuthModule { } diff --git a/src/modules/auth/guards/adminAuth.guard.ts b/src/modules/auth/guards/adminAuth.guard.ts index fa38607..d27449d 100644 --- a/src/modules/auth/guards/adminAuth.guard.ts +++ b/src/modules/auth/guards/adminAuth.guard.ts @@ -13,7 +13,7 @@ import { JwtService } from '@nestjs/jwt'; import { ConfigService } from '@nestjs/config'; import { IAdminTokenPayload } from '../interfaces/IToken-payload'; import { PERMISSIONS_KEY } from '../../../common/decorators/permissions.decorator'; -import { PermissionService } from 'src/modules/roles/providers/permissions.service'; +import { AuthService } from '../services/auth.service'; export interface AdminAuthRequest extends Request { adminId: string; @@ -28,7 +28,7 @@ export class AdminAuthGuard implements CanActivate { private readonly jwtService: JwtService, @Inject(ConfigService) private readonly configService: ConfigService, - private readonly permissionsService: PermissionService, + private readonly authService: AuthService, private readonly reflector: Reflector, ) { } @@ -69,26 +69,19 @@ export class AdminAuthGuard implements CanActivate { if (!requiredPermissions || requiredPermissions.length === 0) { return true; } - //TODO : admin permissions must get it from cache - // const adminPermission = (await this.permissionsService.getAdminPermissions(payload.adminId)) - // if (!adminPermission || !Array.isArray(adminPermission)) { - // this.logger.error('No permissions found', { adminId: payload.adminId }); - // throw new ForbiddenException('No permissions found'); - // } + const adminPermissions = await this.authService.getAdminPermissionsFromCache(payload.adminId) + + const hasPermission = requiredPermissions.every(p => adminPermissions.includes(p)); - // const adminPermissionNames = adminPermission.map(p => p.name) - - // const hasPermission = requiredPermissions.every(p => adminPermissionNames.includes(p)); - - // if (!hasPermission) { - // this.logger.warn('Insufficient permissions', { - // adminId: payload.adminId, - // required: requiredPermissions, - // has: adminPermission, - // }); - // throw new ForbiddenException('You are not authorized to access this resource'); - // } + if (!hasPermission) { + this.logger.warn('Insufficient permissions', { + adminId: payload.adminId, + required: requiredPermissions, + has: hasPermission, + }); + throw new ForbiddenException('You are not authorized to access this resource'); + } return true; } catch (err) { diff --git a/src/modules/auth/services/auth.service.ts b/src/modules/auth/services/auth.service.ts index 46f576b..5c98e47 100644 --- a/src/modules/auth/services/auth.service.ts +++ b/src/modules/auth/services/auth.service.ts @@ -10,10 +10,12 @@ import { AdminLoginTransformer } from '../transformers/admin-login.transformer'; import { UserLoginTransformer } from '../transformers/user-login.transformer'; import { OtpService } from './otp.service'; import { normalizePhoneToLocal } from 'src/modules/util/phone.util'; +import { CacheService } from 'src/modules/util/cache.service'; +import { PermissionService } from 'src/modules/roles/providers/permissions.service'; @Injectable() export class AuthService { - readonly ADMIN_PERMISSIONS_KEY = 'admin-perms'; + readonly ADMIN_PERMISSIONS_EXPIRATION_TIME = 1800; constructor( private readonly smsService: SmsService, @@ -21,8 +23,10 @@ export class AuthService { private readonly tokensService: TokensService, private readonly adminRepository: AdminRepository, private readonly otpService: OtpService, + private readonly cacheService: CacheService, + private readonly permissionService: PermissionService, ) { - + } async requestOtp(dto: RequestOtpDto, isAdmin: boolean) { @@ -33,9 +37,9 @@ export class AuthService { } const code = this.generateOtpCode(); - + await this.otpService.set(phone, code) - + // await this.smsService.sendotp(phone, code); return { code }; @@ -82,6 +86,10 @@ export class AuthService { const tokens = await this.tokensService.generateAccessAndRefreshToken(admin.id, true); + const permissionNames = admin.role.permissions.map(p => p.name); + + + const adminResponse = await AdminLoginTransformer.transform(admin); return { tokens, admin: adminResponse }; @@ -103,4 +111,30 @@ export class AuthService { } } -} + async setAdminPermissionsInCache(adminId: string, permissionNames: string[]) { + await this.cacheService.set( + this.getAdminPermissionsCacheKey(adminId), + JSON.stringify(permissionNames), + this.ADMIN_PERMISSIONS_EXPIRATION_TIME, + ); + return { success: true }; + } + + async getAdminPermissionsFromCache(adminId: string): Promise { + const cacheKey = this.getAdminPermissionsCacheKey(adminId); + const cached = await this.cacheService.get(cacheKey); + + if (cached !== undefined) { + return JSON.parse(cached) as string[]; + } + + const permissions = await this.permissionService.getAdminPermissionsName(adminId); + await this.setAdminPermissionsInCache(adminId, permissions); + + return permissions; + } + + private getAdminPermissionsCacheKey(adminId: string): string { + return `admin:${adminId}:permissions`; + } +} \ No newline at end of file diff --git a/src/seeders/data/admins.data.ts b/src/seeders/data/admins.data.ts index b5e6f19..11e7ecc 100644 --- a/src/seeders/data/admins.data.ts +++ b/src/seeders/data/admins.data.ts @@ -13,13 +13,13 @@ export const adminsData: AdminData[] = [ roleName: 'admin', }, - { - phone: '09121724095', - firstName: 'آقای', - lastName: 'نواعتقاد', - roleName: 'admin', + // { + // phone: '09121724095', + // firstName: 'آقای', + // lastName: 'نواعتقاد', + // roleName: 'admin', - }, + // }, // { // phone: '09185290775', // firstName: 'حمید',