code in response
This commit is contained in:
@@ -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
@@ -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> {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user