add cooldown notification repeat
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { BadRequestException, HttpException, HttpStatus, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
@@ -214,6 +214,30 @@ export class AuthService {
|
||||
async sendOtp(recipient: string, channel?: string, ipAddress?: string) {
|
||||
const existingUser = await this.findUserByRecipient(recipient);
|
||||
const target = this.normalizeOtpTarget(this.resolveOtpTarget(recipient, channel, existingUser));
|
||||
const cooldownSeconds = await this.settings.getNumber('OTP_RESEND_COOLDOWN_SECONDS', 60);
|
||||
const resendKey = this.otpResendKey(target);
|
||||
|
||||
const ttl = await this.redis.client.ttl(resendKey);
|
||||
if (ttl > 0) {
|
||||
throw new HttpException(
|
||||
{
|
||||
message: `Запросить новый код можно через ${this.formatOtpCooldown(ttl)}`,
|
||||
code: 'OTP_RESEND_COOLDOWN',
|
||||
resendAvailableAt: new Date(Date.now() + ttl * 1000).toISOString()
|
||||
},
|
||||
HttpStatus.TOO_MANY_REQUESTS
|
||||
);
|
||||
}
|
||||
|
||||
await this.prisma.authCode.updateMany({
|
||||
where: {
|
||||
target,
|
||||
purpose: 'passwordless-login',
|
||||
usedAt: null
|
||||
},
|
||||
data: { usedAt: new Date() }
|
||||
});
|
||||
|
||||
const deliveryChannel = target.includes('@') ? 'email' : 'sms';
|
||||
const expiryMinutes = await this.settings.getNumber('OTP_EXPIRY_MINUTES', 10);
|
||||
const code = String(randomInt(100000, 999999));
|
||||
@@ -238,7 +262,9 @@ export class AuthService {
|
||||
await this.publishOtpPushNotification(existingUser.id, code, ipAddress, 'Вход в аккаунт');
|
||||
}
|
||||
await this.redis.client.del(this.otpAttemptKey(target));
|
||||
return { sent: true, expiresAt: expiresAt.toISOString(), maskedTarget: this.maskTarget(target) };
|
||||
await this.redis.client.set(resendKey, '1', 'EX', cooldownSeconds);
|
||||
const resendAvailableAt = new Date(Date.now() + cooldownSeconds * 1000).toISOString();
|
||||
return { sent: true, expiresAt: expiresAt.toISOString(), maskedTarget: this.maskTarget(target), resendAvailableAt };
|
||||
}
|
||||
|
||||
private async publishOtpPushNotification(userId: string, code: string, ipAddress: string | undefined, action: string) {
|
||||
@@ -329,6 +355,22 @@ export class AuthService {
|
||||
return `otp:attempts:passwordless-login:${target.toLowerCase()}`;
|
||||
}
|
||||
|
||||
private otpResendKey(target: string) {
|
||||
return `otp:resend:passwordless-login:${target.toLowerCase()}`;
|
||||
}
|
||||
|
||||
private formatOtpCooldown(seconds: number) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const rest = seconds % 60;
|
||||
if (minutes > 0 && rest > 0) {
|
||||
return `${minutes}:${String(rest).padStart(2, '0')}`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes} мин`;
|
||||
}
|
||||
return `${rest} сек`;
|
||||
}
|
||||
|
||||
private maskTarget(value: string) {
|
||||
if (value.includes('@')) {
|
||||
const [name, domain] = value.split('@');
|
||||
|
||||
Reference in New Issue
Block a user