From 232d2ec07205548abf7abc2c933a4f3852870e58 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Wed, 15 Apr 2026 14:22:52 +0330 Subject: [PATCH] auth : update --- src/modules/auth/DTO/request-otp.dto.ts | 2 +- .../auth/DTO/verify-reseller-otp.dto.ts | 27 ++++++++++++ src/modules/auth/auth.controller.ts | 3 +- src/modules/auth/guards/reseller.guard.ts | 4 +- src/modules/auth/interfaces/IToken-payload.ts | 3 +- src/modules/auth/providers/auth.service.ts | 43 ++++++++----------- src/modules/auth/providers/sso.service.ts | 2 - src/modules/auth/providers/tokens.service.ts | 6 +-- src/modules/auth/strategies/jwt.strategy.ts | 7 ++- 9 files changed, 61 insertions(+), 36 deletions(-) create mode 100755 src/modules/auth/DTO/verify-reseller-otp.dto.ts diff --git a/src/modules/auth/DTO/request-otp.dto.ts b/src/modules/auth/DTO/request-otp.dto.ts index d0ad5b3..52fac0f 100755 --- a/src/modules/auth/DTO/request-otp.dto.ts +++ b/src/modules/auth/DTO/request-otp.dto.ts @@ -7,6 +7,6 @@ export class RequestOtpDto { @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY }) @Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT }) @IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT }) - @ApiProperty({ description: "phone number", default: "09922320740" }) + @ApiProperty({ description: "phone number", default: "09185290775" }) phone: string; } diff --git a/src/modules/auth/DTO/verify-reseller-otp.dto.ts b/src/modules/auth/DTO/verify-reseller-otp.dto.ts new file mode 100755 index 0000000..2619c36 --- /dev/null +++ b/src/modules/auth/DTO/verify-reseller-otp.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsMobilePhone, IsNotEmpty, IsNumberString, IsEnum, Length } from "class-validator"; + +import { AuthMessage } from "../../../common/enums/message.enum"; + +export enum ResellerType { + agent = 'agent', + reseller = 'reseller' +} + +export class VerifyResellerOtpDto { + @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY }) + @Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT }) + @IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT }) + @ApiProperty({ description: "phone number", default: "09185290775" }) + phone: string; + + @ApiProperty({ description: "OTP code received via SMS", example: "" }) + @IsNotEmpty({ message: AuthMessage.OTP_NOT_EMPTY }) + @IsNumberString(undefined, { message: AuthMessage.OTP_FORMAT_INVALID }) + @Length(5, 5, { message: AuthMessage.OTP_FORMAT_INVALID }) + code: string; + + @ApiProperty({ enum: ResellerType }) + @IsEnum(ResellerType) + type: ResellerType; +} diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 9514499..b679dbb 100755 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -16,6 +16,7 @@ import { UserDec } from "../../common/decorators/user.decorator"; import { User } from "../users/entities/user.entity"; import { ForgetPasswordDto } from "./DTO/forget-password.dto"; import { VerifyForgotPasswordOtpDto } from "./DTO/verify-forgot-otp.dto"; +import { VerifyResellerOtpDto } from "./DTO/verify-reseller-otp.dto"; @ApiTags("Auth") @Controller("auth") @@ -80,7 +81,7 @@ export class AuthController { @ApiOperation({ summary: "verify otp for login" }) @HttpCode(HttpStatus.OK) @Post("otp/verify/reseller") - resellerVerifyOtp(@Body() verifyOtpDto: VerifyOtpDto) { + resellerVerifyOtp(@Body() verifyOtpDto: VerifyResellerOtpDto) { return this.authService.resellerVerifyLoginOtp(verifyOtpDto); } diff --git a/src/modules/auth/guards/reseller.guard.ts b/src/modules/auth/guards/reseller.guard.ts index 7d94ec4..4477a53 100755 --- a/src/modules/auth/guards/reseller.guard.ts +++ b/src/modules/auth/guards/reseller.guard.ts @@ -16,15 +16,13 @@ export class ResellerRouteGuard implements CanActivate { if (!requiredReseller) return true; - console.log('hi') const req = context.switchToHttp().getRequest(); const user = req.user; - console.log(user) if (!user) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS); - // if (!user.isReseller) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS); + if (!user.resellerType) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS); return true; } diff --git a/src/modules/auth/interfaces/IToken-payload.ts b/src/modules/auth/interfaces/IToken-payload.ts index 6a173c8..02e5844 100755 --- a/src/modules/auth/interfaces/IToken-payload.ts +++ b/src/modules/auth/interfaces/IToken-payload.ts @@ -1,10 +1,11 @@ import { PermissionEnum } from "../../users/enums/permission.enum"; +import { ResellerType } from "../DTO/verify-reseller-otp.dto"; // import { RoleEnum } from "../../users/enums/role.enum"; export interface ITokenPayload { id: string; isAdmin: boolean; - isReseller: boolean; + resellerType?: ResellerType; permissions: PermissionEnum[]; audience?: string; // Client application identifier for SSO } diff --git a/src/modules/auth/providers/auth.service.ts b/src/modules/auth/providers/auth.service.ts index 7f99c01..5904f56 100755 --- a/src/modules/auth/providers/auth.service.ts +++ b/src/modules/auth/providers/auth.service.ts @@ -21,6 +21,7 @@ import { ForgetPasswordDto } from "../DTO/forget-password.dto"; import { VerifyForgotPasswordOtpDto } from "../DTO/verify-forgot-otp.dto"; import { EmailService } from "../../utils/providers/email.service"; import { User } from "../../users/entities/user.entity"; +import { ResellerType, VerifyResellerOtpDto } from "../DTO/verify-reseller-otp.dto"; @Injectable() export class AuthService { @@ -115,8 +116,14 @@ export class AuthService { async requestLoginOtpReseller(requestOtpDto: RequestOtpDto) { const { phone } = requestOtpDto; - // check if agent with this phone exist or not - await this.checkAgentExistWithPhone(phone) + + let user: User | null = null + + user = (await this.usersService.getAgentByUserPhone(phone)) ?? (await this.usersService.getResellerByUserPhone(phone)) + + if (!user) { + throw new BadRequestException(AuthMessage.USER_NOT_FOUND) + } const existCode = await this.otpService.checkExistOtp(phone, "LOGIN_RESELLER_AGENT"); if (existCode) { @@ -227,15 +234,18 @@ export class AuthService { } //****************** */ - async resellerVerifyLoginOtp(verifyOtpDto: VerifyOtpDto) { - const { code, phone } = verifyOtpDto; + async resellerVerifyLoginOtp(verifyOtpDto: VerifyResellerOtpDto) { + const { code, phone, type } = verifyOtpDto; await this.checkResellerOtpWithPhone(phone, code) - const { agent, reseller } = await this.checkAgentExistWithPhone(phone); + let user: User | null = null - - const user: User | null = agent ?? reseller + if (type == ResellerType.agent) { + user = await this.usersService.getAgentByUserPhone(phone) + } else if (type == ResellerType.reseller) { + user = await this.usersService.getResellerByUserPhone(phone) + } if (!user) { throw new BadRequestException(AuthMessage.USER_NOT_FOUND) @@ -243,7 +253,7 @@ export class AuthService { if (user.deletedAt) throw new BadRequestException(AuthMessage.ACCESS_DENIED); - const tokens = await this.tokensService.generateTokensForReseller(user); + const tokens = await this.tokensService.generateTokensForReseller(user, type); return { message: AuthMessage.LOGIN_SUCCESS, @@ -310,20 +320,7 @@ export class AuthService { return true; } - //****************** */ - //****************** */ - private async checkAgentExistWithPhone(phone: string) { - const { reseller } = await this.usersService.getResellerByUserPhone(phone); - - const { agent } = await this.usersService.getAgentByUserPhone(phone); - - if (!reseller && !agent) { - throw new BadRequestException(AuthMessage.USER_NOT_FOUND) - } - - return { reseller, agent } - } //****************** */ //****************** */ async requestForgotPasswordOtp(dto: ForgetPasswordDto) { @@ -383,7 +380,5 @@ export class AuthService { } //****************** */ //****************** */ - // private checkUserIsAgent(roles: Role[]) { - // return roles.some((role) => role.isAgent); - // } + } diff --git a/src/modules/auth/providers/sso.service.ts b/src/modules/auth/providers/sso.service.ts index 1d0bba1..906160a 100644 --- a/src/modules/auth/providers/sso.service.ts +++ b/src/modules/auth/providers/sso.service.ts @@ -45,7 +45,6 @@ export class SSOService { // Generate token with client-specific audience const payload: ITokenPayload = { id: user.id, - isReseller:false, isAdmin: user.roles.some((r) => r.isAdmin), permissions: user.roles.flatMap((r) => (r?.permissions?.length ? r.permissions.map((p) => p.name) : [])), audience: clientId, @@ -106,7 +105,6 @@ export class SSOService { const payload: ITokenPayload = { id: user.id, isAdmin: user.roles.some((r) => r.isAdmin), - isReseller:false, permissions: user.roles.flatMap((r) => (r?.permissions?.length ? r.permissions.map((p) => p.name) : [])), audience: clientId, }; diff --git a/src/modules/auth/providers/tokens.service.ts b/src/modules/auth/providers/tokens.service.ts index 77b8277..8ac7a26 100755 --- a/src/modules/auth/providers/tokens.service.ts +++ b/src/modules/auth/providers/tokens.service.ts @@ -10,6 +10,7 @@ import { AuthMessage } from "../../../common/enums/message.enum"; import { User } from "../../users/entities/user.entity"; import { RefreshTokensRepository } from "../../users/repositories/refresh-token.repository"; import { ITokenPayload } from "../interfaces/IToken-payload"; +import { ResellerType } from "../DTO/verify-reseller-otp.dto"; @Injectable() export class TokensService { @@ -27,19 +28,18 @@ export class TokensService { { id: user.id, isAdmin: user.roles.some((r) => r.isAdmin), - isReseller: false, permissions: user.roles.flatMap((r) => (r?.permissions?.length ? r.permissions.map((p) => p.name) : [])), }, queryRunner, ); } // ----------------------------- - async generateTokensForReseller(user: User, queryRunner?: QueryRunner) { + async generateTokensForReseller(user: User, resellerType: ResellerType, queryRunner?: QueryRunner) { return this.generateAccessAndRefreshToken( { id: user.id, isAdmin: false, - isReseller: true, + resellerType, permissions: [], }, queryRunner, diff --git a/src/modules/auth/strategies/jwt.strategy.ts b/src/modules/auth/strategies/jwt.strategy.ts index b089942..377e5b7 100755 --- a/src/modules/auth/strategies/jwt.strategy.ts +++ b/src/modules/auth/strategies/jwt.strategy.ts @@ -21,6 +21,11 @@ export class JwtStrategy extends PassportStrategy(Strategy, JWT_STRATEGY_NAME) { } async validate(payload: ITokenPayload) { - return { id: payload.id, permissions: payload.permissions, isAdmin: payload.isAdmin }; + return { + id: payload.id, + permissions: payload.permissions, + isAdmin: payload.isAdmin, + resellerType: payload.resellerType + }; } }