code in response

This commit is contained in:
2026-07-03 12:27:21 +03:30
parent 713734b842
commit fb4ddb4dac
4 changed files with 46 additions and 15 deletions
+2 -1
View File
@@ -20,6 +20,7 @@ import { CheckSalonMobileDto } from './dto/check-salon-mobile.dto';
import { CheckSalonMobileResponseDto } from './dto/check-salon-mobile-response.dto'; import { CheckSalonMobileResponseDto } from './dto/check-salon-mobile-response.dto';
import { RefreshTokenDto } from './dto/refresh-token.dto'; import { RefreshTokenDto } from './dto/refresh-token.dto';
import { RequestOtpDto } from './dto/request-otp.dto'; import { RequestOtpDto } from './dto/request-otp.dto';
import { RequestOtpResponseDto } from './dto/request-otp-response.dto';
import { RequestSalonOtpResponseDto } from './dto/request-salon-otp-response.dto'; import { RequestSalonOtpResponseDto } from './dto/request-salon-otp-response.dto';
import { SalonAuthResponseDto } from './dto/salon-auth-response.dto'; import { SalonAuthResponseDto } from './dto/salon-auth-response.dto';
import { VerifyOtpDto } from './dto/verify-otp.dto'; import { VerifyOtpDto } from './dto/verify-otp.dto';
@@ -63,7 +64,7 @@ export class AuthController {
@Public() @Public()
@ApiPublicEndpoint('Request OTP code via SMS') @ApiPublicEndpoint('Request OTP code via SMS')
@ApiStandardMessageResponse('OTP sent') @ApiStandardOkResponse(RequestOtpResponseDto)
@ApiStandardTooManyRequestsResponse() @ApiStandardTooManyRequestsResponse()
@Post('otp/request') @Post('otp/request')
requestOtp(@Body() dto: RequestOtpDto) { requestOtp(@Body() dto: RequestOtpDto) {
+25 -13
View File
@@ -48,7 +48,6 @@ export class AuthService {
private readonly otpSalonSignupExpiresInMinutes: number; private readonly otpSalonSignupExpiresInMinutes: number;
private readonly otpMaxAttempts: number; private readonly otpMaxAttempts: number;
private readonly otpCooldownSeconds: number; private readonly otpCooldownSeconds: number;
private readonly otpDebug: boolean;
private readonly refreshExpiresIn: string; private readonly refreshExpiresIn: string;
constructor( constructor(
@@ -86,14 +85,13 @@ export class AuthService {
this.configService.get<string>('OTP_COOLDOWN_SECONDS') ?? '60', this.configService.get<string>('OTP_COOLDOWN_SECONDS') ?? '60',
10, 10,
); );
this.otpDebug = this.configService.get<string>('OTP_DEBUG') === 'true';
this.refreshExpiresIn = this.refreshExpiresIn =
this.configService.get<string>('JWT_REFRESH_EXPIRES_IN') ?? '7d'; this.configService.get<string>('JWT_REFRESH_EXPIRES_IN') ?? '7d';
} }
async requestOtp(mobile: string): Promise<{ message: string }> { async requestOtp(mobile: string): Promise<{ message: string; code?: string }> {
await this.sendOtp(mobile, this.otpExpiresInMinutes); const code = await this.sendOtp(mobile, this.otpExpiresInMinutes);
return { message: 'OTP sent' }; return this.buildOtpRequestResponse(code);
} }
async checkSalonMobile( async checkSalonMobile(
@@ -108,6 +106,7 @@ export class AuthService {
message: string; message: string;
isRegistered: boolean; isRegistered: boolean;
expiresInSeconds: number; expiresInSeconds: number;
code?: string;
}> { }> {
await this.ensureSalonMobileCanProceed(mobile); await this.ensureSalonMobileCanProceed(mobile);
const isRegistered = await this.isSalonOwnerRegistered(mobile); const isRegistered = await this.isSalonOwnerRegistered(mobile);
@@ -115,10 +114,10 @@ export class AuthService {
? this.otpExpiresInMinutes ? this.otpExpiresInMinutes
: this.otpSalonSignupExpiresInMinutes; : this.otpSalonSignupExpiresInMinutes;
await this.sendOtp(mobile, expiresInMinutes); const code = await this.sendOtp(mobile, expiresInMinutes);
return { return {
message: 'OTP sent', ...this.buildOtpRequestResponse(code),
isRegistered, isRegistered,
expiresInSeconds: expiresInMinutes * 60, expiresInSeconds: expiresInMinutes * 60,
}; };
@@ -145,7 +144,7 @@ export class AuthService {
private async sendOtp( private async sendOtp(
mobile: string, mobile: string,
expiresInMinutes: number, expiresInMinutes: number,
): Promise<void> { ): Promise<string> {
const recentOtp = await this.otpRepository.findOne({ const recentOtp = await this.otpRepository.findOne({
where: { mobile, verified: false }, where: { mobile, verified: false },
order: { createdAt: 'DESC' }, order: { createdAt: 'DESC' },
@@ -176,16 +175,29 @@ export class AuthService {
const sent = await this.smsService.sendSms(mobile, `کد ورود: ${code}`); const sent = await this.smsService.sendSms(mobile, `کد ورود: ${code}`);
if (!sent) { if (!sent && !this.shouldExposeOtpInResponse()) {
throw new BadRequestException('Failed to send OTP'); throw new BadRequestException('Failed to send OTP');
} }
if ( if (this.shouldExposeOtpInResponse()) {
this.otpDebug &&
this.configService.get<string>('NODE_ENV') === 'development'
) {
this.logger.debug(`OTP for ${mobile}: ${code}`); this.logger.debug(`OTP for ${mobile}: ${code}`);
} }
return code;
}
private shouldExposeOtpInResponse(): boolean {
return this.configService.get<string>('NODE_ENV') !== 'production';
}
private buildOtpRequestResponse(code: string): {
message: string;
code?: string;
} {
return {
message: 'OTP sent',
...(this.shouldExposeOtpInResponse() ? { code } : {}),
};
} }
private async consumeOtp(mobile: string, code: string): Promise<void> { private async consumeOtp(mobile: string, code: string): Promise<void> {
+12
View File
@@ -0,0 +1,12 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class RequestOtpResponseDto {
@ApiProperty({ example: 'OTP sent' })
message: string;
@ApiPropertyOptional({
description: 'Only returned in non-production environments when SMS is unavailable',
example: '12345',
})
code?: string;
}
@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class RequestSalonOtpResponseDto { export class RequestSalonOtpResponseDto {
@ApiProperty({ example: 'OTP sent' }) @ApiProperty({ example: 'OTP sent' })
@@ -14,4 +14,10 @@ export class RequestSalonOtpResponseDto {
example: 900, example: 900,
}) })
expiresInSeconds: number; expiresInSeconds: number;
@ApiPropertyOptional({
description: 'Only returned in non-production environments when SMS is unavailable',
example: '12345',
})
code?: string;
} }