fix idp on started
This commit is contained in:
@@ -11,6 +11,7 @@ import { normalizeLdapUserFilter, resolveLdapBindIdentity, resolveLdapPort, vali
|
||||
import { AuthTokens, LoginCommand, PublicUser, RegisterCommand } from './dto';
|
||||
import { AccessService } from './access.service';
|
||||
import { SettingsService } from './settings.service';
|
||||
import { MessagingService } from '../infra/messaging.service';
|
||||
|
||||
const FIRST_ADMIN_LOCK = 'locks:first-super-admin';
|
||||
|
||||
@@ -23,7 +24,8 @@ export class AuthService {
|
||||
private readonly config: ConfigService,
|
||||
private readonly access: AccessService,
|
||||
private readonly settings: SettingsService,
|
||||
private readonly ldapClient: LdapClientService
|
||||
private readonly ldapClient: LdapClientService,
|
||||
private readonly messaging: MessagingService
|
||||
) {}
|
||||
|
||||
async register(command: RegisterCommand): Promise<PublicUser> {
|
||||
@@ -131,19 +133,27 @@ export class AuthService {
|
||||
async sendOtp(recipient: string, channel?: string) {
|
||||
const existingUser = await this.findUserByRecipient(recipient);
|
||||
const target = this.resolveOtpTarget(recipient, channel, existingUser);
|
||||
const deliveryChannel = target.includes('@') ? 'email' : 'sms';
|
||||
const expiryMinutes = await this.settings.getNumber('OTP_EXPIRY_MINUTES', 10);
|
||||
const code = String(randomInt(100000, 999999));
|
||||
const expiresAt = new Date(Date.now() + 5 * 60_000);
|
||||
const expiresAt = new Date(Date.now() + expiryMinutes * 60_000);
|
||||
await this.prisma.authCode.create({
|
||||
data: {
|
||||
userId: existingUser?.id,
|
||||
target,
|
||||
channel: target.includes('@') ? 'email' : 'sms',
|
||||
channel: deliveryChannel,
|
||||
purpose: 'passwordless-login',
|
||||
codeHash: await bcrypt.hash(code, 10),
|
||||
expiresAt
|
||||
}
|
||||
});
|
||||
console.log('[OTP MOCK] Code for', target, 'is:', code);
|
||||
await this.messaging.sendVerificationCode({
|
||||
channel: deliveryChannel as 'email' | 'sms',
|
||||
target,
|
||||
code,
|
||||
expiryMinutes
|
||||
});
|
||||
await this.redis.client.del(this.otpAttemptKey(target));
|
||||
return { sent: true, expiresAt: expiresAt.toISOString(), maskedTarget: this.maskTarget(target) };
|
||||
}
|
||||
|
||||
@@ -160,8 +170,20 @@ export class AuthService {
|
||||
});
|
||||
|
||||
if (!authCode) throw new UnauthorizedException('Код входа не найден или истек');
|
||||
|
||||
const attemptKey = this.otpAttemptKey(authCode.target);
|
||||
const maxAttempts = await this.settings.getNumber('OTP_MAX_ATTEMPTS', 5);
|
||||
const attempts = Number((await this.redis.client.get(attemptKey)) ?? 0);
|
||||
if (attempts >= maxAttempts) {
|
||||
throw new UnauthorizedException('Превышено число попыток ввода кода');
|
||||
}
|
||||
|
||||
const matches = await bcrypt.compare(command.code, authCode.codeHash);
|
||||
if (!matches) throw new UnauthorizedException('Неверный код входа');
|
||||
if (!matches) {
|
||||
await this.redis.client.set(attemptKey, String(attempts + 1), 'EX', 15 * 60);
|
||||
throw new UnauthorizedException('Неверный код входа');
|
||||
}
|
||||
await this.redis.client.del(attemptKey);
|
||||
await this.prisma.authCode.update({ where: { id: authCode.id }, data: { usedAt: new Date() } });
|
||||
|
||||
const user = await this.findOrCreatePasswordlessUser(command.recipient);
|
||||
@@ -183,6 +205,10 @@ export class AuthService {
|
||||
return map[channel] ?? recipient;
|
||||
}
|
||||
|
||||
private otpAttemptKey(target: string) {
|
||||
return `otp:attempts:passwordless-login:${target.toLowerCase()}`;
|
||||
}
|
||||
|
||||
private maskTarget(value: string) {
|
||||
if (value.includes('@')) {
|
||||
const [name, domain] = value.split('@');
|
||||
|
||||
Reference in New Issue
Block a user