forked from Yara724/api
FIX Catcha
This commit is contained in:
@@ -18,7 +18,7 @@ import {
|
||||
RegisterDtoRs,
|
||||
} from "src/auth/dto/actor/register.actor.dto";
|
||||
import { StateListDtoRs } from "src/auth/dto/actor/states.dto";
|
||||
import { CaptchaAccountService } from "src/auth/auth-services/captcha-account.service";
|
||||
import { CaptchaChallengeService } from "src/captcha/captcha-challenge.service";
|
||||
import { ClientDbService } from "src/client/entities/db-service/client.db.service";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { UserType } from "src/Types&Enums/userType.enum";
|
||||
@@ -53,7 +53,7 @@ export class ActorAuthService {
|
||||
// private readonly mailService: MailService, // Mailer disabled – not used
|
||||
private readonly clientDbService: ClientDbService,
|
||||
private readonly otpService: OtpGeneratorService,
|
||||
private readonly captchaAccountService: CaptchaAccountService,
|
||||
private readonly captchaChallengeService: CaptchaChallengeService,
|
||||
) {}
|
||||
|
||||
// TODO convrt to class for dynamic controller
|
||||
@@ -178,13 +178,11 @@ export class ActorAuthService {
|
||||
if (typeof password !== "string" || !password) {
|
||||
throw new BadRequestException("password is required");
|
||||
}
|
||||
const captchaId =
|
||||
typeof body?.captchaId === "string" ? body.captchaId : undefined;
|
||||
const captcha =
|
||||
typeof body?.captcha === "string" ? body.captcha : undefined;
|
||||
await this.captchaAccountService.verifyActorCaptcha(
|
||||
username,
|
||||
role,
|
||||
captcha,
|
||||
);
|
||||
await this.captchaChallengeService.verify(captchaId, captcha);
|
||||
const actor = await this.validateActor(username, password, role);
|
||||
return this.issueActorTokens(actor);
|
||||
}
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import {
|
||||
CaptchaAuthErrorCode,
|
||||
throwCaptchaAuthError,
|
||||
} from "src/auth/auth-services/captcha-auth.error";
|
||||
import { CaptchaResponseDto } from "src/auth/dto/captcha-response.dto";
|
||||
import { CaptchaService } from "src/captcha/captcha.service";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
|
||||
import { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
|
||||
import { FieldExpertDbService } from "src/users/entities/db-service/field-expert.db.service";
|
||||
import { InsurerExpertDbService } from "src/users/entities/db-service/insurer-expert.db.service";
|
||||
import { RegistrarDbService } from "src/users/entities/db-service/registrar.db.service";
|
||||
import { HashService } from "src/utils/hash/hash.service";
|
||||
|
||||
type CaptchaRecord = {
|
||||
captcha?: string | null;
|
||||
captchaExpire?: number | null;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class CaptchaAccountService {
|
||||
constructor(
|
||||
private readonly captchaService: CaptchaService,
|
||||
private readonly hashService: HashService,
|
||||
private readonly expertDbService: ExpertDbService,
|
||||
private readonly damageExpertDbService: DamageExpertDbService,
|
||||
private readonly fieldExpertDbService: FieldExpertDbService,
|
||||
private readonly registrarDbService: RegistrarDbService,
|
||||
private readonly insurerExpertDbService: InsurerExpertDbService,
|
||||
) {}
|
||||
|
||||
async issueActorCaptcha(
|
||||
username: string,
|
||||
role: RoleEnum,
|
||||
): Promise<CaptchaResponseDto> {
|
||||
const actor = await this.findActor(role, username);
|
||||
if (!actor) {
|
||||
throw new NotFoundException("Actor account not found");
|
||||
}
|
||||
|
||||
const generated = this.captchaService.generate();
|
||||
const captchaHash = await this.hashService.hash(
|
||||
this.captchaService.normalizeAnswer(generated.text),
|
||||
);
|
||||
await this.updateActorCaptcha(role, username, captchaHash, generated.expiresAt);
|
||||
|
||||
return { image: generated.image, expiresAt: generated.expiresAt };
|
||||
}
|
||||
|
||||
async verifyActorCaptcha(
|
||||
username: string,
|
||||
role: RoleEnum,
|
||||
answer: string | undefined,
|
||||
): Promise<void> {
|
||||
const actor = await this.findActor(role, username);
|
||||
if (!actor) {
|
||||
throw new NotFoundException("Actor account not found");
|
||||
}
|
||||
await this.assertCaptcha(actor, answer);
|
||||
await this.updateActorCaptcha(role, username, null, 0);
|
||||
}
|
||||
|
||||
private async assertCaptcha(
|
||||
record: CaptchaRecord | null | undefined,
|
||||
answer: string | undefined,
|
||||
): Promise<void> {
|
||||
if (!answer?.trim()) {
|
||||
throwCaptchaAuthError(CaptchaAuthErrorCode.CAPTCHA_REQUIRED);
|
||||
}
|
||||
if (!record?.captcha) {
|
||||
throwCaptchaAuthError(CaptchaAuthErrorCode.CAPTCHA_REQUIRED);
|
||||
}
|
||||
const now = Date.now();
|
||||
if (!record.captchaExpire || record.captchaExpire < now) {
|
||||
throwCaptchaAuthError(CaptchaAuthErrorCode.CAPTCHA_EXPIRED);
|
||||
}
|
||||
const ok = await this.hashService.compare(
|
||||
this.captchaService.normalizeAnswer(answer),
|
||||
record.captcha,
|
||||
);
|
||||
if (!ok) {
|
||||
throwCaptchaAuthError(CaptchaAuthErrorCode.CAPTCHA_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
private async findActor(role: RoleEnum, username: string) {
|
||||
switch (role) {
|
||||
case RoleEnum.EXPERT:
|
||||
return this.expertDbService.findOne({ email: username });
|
||||
case RoleEnum.DAMAGE_EXPERT:
|
||||
return this.damageExpertDbService.findOne({ email: username });
|
||||
case RoleEnum.FIELD_EXPERT:
|
||||
return this.fieldExpertDbService.findOne({ email: username });
|
||||
case RoleEnum.REGISTRAR:
|
||||
return this.registrarDbService.findOne({ email: username });
|
||||
case RoleEnum.COMPANY:
|
||||
return this.insurerExpertDbService.findOne({ email: username });
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async updateActorCaptcha(
|
||||
role: RoleEnum,
|
||||
username: string,
|
||||
captcha: string | null,
|
||||
captchaExpire: number,
|
||||
) {
|
||||
const update = { captcha, captchaExpire };
|
||||
switch (role) {
|
||||
case RoleEnum.EXPERT:
|
||||
return this.expertDbService.findOneAndUpdate({ email: username }, update);
|
||||
case RoleEnum.DAMAGE_EXPERT:
|
||||
return this.damageExpertDbService.findOneAndUpdate({ email: username }, update);
|
||||
case RoleEnum.FIELD_EXPERT:
|
||||
return this.fieldExpertDbService.findOneAndUpdate({ email: username }, update);
|
||||
case RoleEnum.REGISTRAR:
|
||||
return this.registrarDbService.findOneAndUpdate({ email: username }, update);
|
||||
case RoleEnum.COMPANY:
|
||||
return this.insurerExpertDbService.updateOne({ email: username }, update);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
|
||||
export enum CaptchaAuthErrorCode {
|
||||
CAPTCHA_REQUIRED = "CAPTCHA_REQUIRED",
|
||||
CAPTCHA_NOT_FOUND = "CAPTCHA_NOT_FOUND",
|
||||
CAPTCHA_EXPIRED = "CAPTCHA_EXPIRED",
|
||||
CAPTCHA_INVALID = "CAPTCHA_INVALID",
|
||||
}
|
||||
@@ -12,6 +13,8 @@ export enum CaptchaAuthErrorCode {
|
||||
const messages: Record<CaptchaAuthErrorCode, string> = {
|
||||
[CaptchaAuthErrorCode.CAPTCHA_REQUIRED]:
|
||||
"Captcha is required. Request a new captcha image first.",
|
||||
[CaptchaAuthErrorCode.CAPTCHA_NOT_FOUND]:
|
||||
"Captcha id was not found. Request a new captcha image.",
|
||||
[CaptchaAuthErrorCode.CAPTCHA_EXPIRED]:
|
||||
"Captcha has expired. Request a new captcha image.",
|
||||
[CaptchaAuthErrorCode.CAPTCHA_INVALID]: "Captcha is invalid.",
|
||||
@@ -25,7 +28,10 @@ export function captchaAuthErrorBody(code: CaptchaAuthErrorCode) {
|
||||
}
|
||||
|
||||
export function throwCaptchaAuthError(code: CaptchaAuthErrorCode): never {
|
||||
if (code === CaptchaAuthErrorCode.CAPTCHA_REQUIRED) {
|
||||
if (
|
||||
code === CaptchaAuthErrorCode.CAPTCHA_REQUIRED ||
|
||||
code === CaptchaAuthErrorCode.CAPTCHA_NOT_FOUND
|
||||
) {
|
||||
throw new BadRequestException(captchaAuthErrorBody(code));
|
||||
}
|
||||
throw new UnauthorizedException(captchaAuthErrorBody(code));
|
||||
|
||||
Reference in New Issue
Block a user