Files
eden/apps/core-web/src/lib/api/public-signature.ts
Matheus (Handix) 9c14671e19 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>
2026-09-17 21:51:18 -03:00

77 lines
2.8 KiB
TypeScript

/**
* Client for `apps/api` `/v1/public-signature/:token/*` — the signer-facing
* flow reached via the emailed link `/assinar/:token` (see
* apps/api/src/signatures/public-signature.controller.ts +
* public-signature.service.ts for the source of truth). Deliberately NOT
* authenticated: the token in the URL is the entire authorization,
* there's never an `accessToken` header here — mirrored by never taking
* one as a parameter in this file, unlike `./signatures.ts`.
*/
import { API_BASE_URL, apiRequest } from "./http.js";
import type { PartyRole, PersonType, SignerStatus } from "./signatures.js";
export interface PublicSignerState {
id: string;
party: PartyRole;
person_type: PersonType | null;
full_name: string;
cpf: string | null;
email: string;
phone: string | null;
company_name: string | null;
cnpj: string | null;
role_title: string | null;
status: SignerStatus;
documentsRead: string[];
}
export interface PublicDocumentState {
id: string;
title: string;
documentVersion: number;
pageCount: number | null;
confirmedRead: boolean;
}
export interface PublicSignatureState {
envelope: { id: string; envelopeNumber: string; status: string };
signer: PublicSignerState;
documents: PublicDocumentState[];
}
export function getPublicSignatureState(token: string): Promise<PublicSignatureState> {
return apiRequest<PublicSignatureState>(`/public-signature/${token}`, { method: "GET" });
}
/** Opens the document's PDF in a new tab — no bearer token needed, the token is already in the path, so a plain `window.open` works (unlike the authenticated backoffice equivalent in `./signatures.ts`). */
export function publicDocumentPdfUrl(token: string, documentId: string): string {
return `${API_BASE_URL}/public-signature/${token}/documents/${documentId}/pdf`;
}
export function confirmDocumentRead(token: string, documentId: string): Promise<void> {
return apiRequest<void>(`/public-signature/${token}/documents/${documentId}/confirm-read`, { method: "POST" });
}
export function confirmSignerData(token: string): Promise<void> {
return apiRequest<void>(`/public-signature/${token}/confirm-data`, { method: "POST" });
}
export function acceptConsent(token: string): Promise<void> {
return apiRequest<void>(`/public-signature/${token}/consent`, { method: "POST" });
}
export function requestOtp(token: string): Promise<void> {
return apiRequest<void>(`/public-signature/${token}/otp/request`, { method: "POST" });
}
export function verifyOtp(token: string, code: string): Promise<void> {
return apiRequest<void>(`/public-signature/${token}/otp/verify`, {
method: "POST",
body: JSON.stringify({ code }),
});
}
export function commitSignature(token: string): Promise<void> {
return apiRequest<void>(`/public-signature/${token}/sign`, { method: "POST" });
}