feat(signatures): UI completa do módulo + rate limiting em rotas públicas

Fecha as duas pendências da leva anterior (módulo de Assinatura
Eletrônica): tela de backoffice para gerenciar envelopes (criação,
envio, cancelamento, checkpoint/finalização, auditoria) e as duas
páginas públicas (/assinar/:token, /verificar/:verificationId).

Backend: novos endpoints de streaming de PDF (documento congelado e
PDF final consolidado, autenticados e via token público) que faltavam
para a UI funcionar — o signatário não tinha como ver o próprio
documento antes de confirmar leitura. Rate limiting (guard próprio,
em memória, chaves por IP e por token) em todas as rotas sem sessão:
auth (login/mfa/forgot-password/reset-password/refresh/logout) e todo
o fluxo público de assinatura/verificação.

Suíte: 77/77 verdes. pnpm turbo run typecheck build: 28/28.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 21:51:18 -03:00
parent 20b114a0bf
commit 9c14671e19
30 changed files with 2946 additions and 13 deletions

View File

@@ -12,6 +12,16 @@ import { ResetPasswordDto } from "./dto/reset-password.dto.js";
import { JwtAuthGuard } from "./jwt-auth.guard.js";
import { CurrentUser } from "./decorators/current-user.decorator.js";
import type { EdenAuthenticatedUser } from "./jwt-auth.guard.js";
import { RateLimitGuard } from "../common/rate-limit/rate-limit.guard.js";
import { RateLimit } from "../common/rate-limit/rate-limit.decorator.js";
import {
AUTH_FORGOT_PASSWORD_RULE,
AUTH_LOGIN_RULE,
AUTH_MFA_CHALLENGE_RULE,
AUTH_LOGOUT_RULE,
AUTH_REFRESH_RULE,
AUTH_RESET_PASSWORD_RULE,
} from "../common/rate-limit/rate-limit-rules.js";
function requestContext(req: Request) {
return {
@@ -22,6 +32,7 @@ function requestContext(req: Request) {
@ApiTags("auth")
@Controller("v1/auth")
@UseGuards(RateLimitGuard)
export class AuthController {
constructor(
private readonly auth: AuthService,
@@ -30,12 +41,14 @@ export class AuthController {
@Post("login")
@HttpCode(200)
@RateLimit(AUTH_LOGIN_RULE)
async login(@Body() dto: LoginDto, @Req() req: Request) {
return this.auth.login(dto.email, dto.password, dto.applicationKey, requestContext(req));
}
@Post("mfa/challenge")
@HttpCode(200)
@RateLimit(AUTH_MFA_CHALLENGE_RULE)
async mfaChallenge(@Body() dto: MfaChallengeDto, @Req() req: Request) {
return this.auth.completeMfaLogin(dto.mfaChallenge, dto.code, requestContext(req));
}
@@ -65,6 +78,7 @@ export class AuthController {
@Post("forgot-password")
@HttpCode(200)
@RateLimit(AUTH_FORGOT_PASSWORD_RULE)
async forgotPassword(@Body() dto: ForgotPasswordDto, @Req() req: Request) {
await this.auth.forgotPassword(dto.email, requestContext(req));
// Always the same response regardless of whether the email is
@@ -74,18 +88,21 @@ export class AuthController {
@Post("reset-password")
@HttpCode(204)
@RateLimit(AUTH_RESET_PASSWORD_RULE)
async resetPassword(@Body() dto: ResetPasswordDto, @Req() req: Request) {
await this.auth.resetPassword(dto.token, dto.newPassword, requestContext(req));
}
@Post("refresh")
@HttpCode(200)
@RateLimit(AUTH_REFRESH_RULE)
async refresh(@Body() dto: RefreshDto, @Req() req: Request) {
return this.auth.refresh(dto.refreshToken, requestContext(req));
}
@Post("logout")
@HttpCode(204)
@RateLimit(AUTH_LOGOUT_RULE)
async logout(@Body() dto: RefreshDto, @Req() req: Request) {
await this.auth.logout(dto.refreshToken, requestContext(req));
}

View File

@@ -8,8 +8,10 @@ import { JwtAuthGuard } from "./jwt-auth.guard.js";
import { PermissionGuard } from "./permission.guard.js";
import { SuperAdminGuard } from "./super-admin.guard.js";
import { FieldEncryptionService } from "../crypto/field-encryption.service.js";
import { RateLimitModule } from "../common/rate-limit/rate-limit.module.js";
@Module({
imports: [RateLimitModule],
controllers: [AuthController],
providers: [
AuthService,