feat(auth): e-mail transacional real + recuperação de senha
Liga o adapter SMTP existente (packages/integrations, já era fail-soft) a um consumidor de verdade e adiciona o fluxo completo de recuperação de senha, faltante desde a Fase 1. - UsersService.invite() agora envia e-mail com a senha temporária (mantém o retorno na resposta também — dupla entrega, eden.md §4.8). - POST /v1/auth/forgot-password + POST /v1/auth/reset-password: token de 256 bits, só o hash SHA-256 persistido (users.reset_token_hash/ reset_token_expires_at — colunas que já existiam desde a baseline da Fase 1, nunca usadas até agora), validade de 1h, uso único, revoga todas as sessões ativas do usuário ao concluir. Mesma disciplina anti-enumeração do login: resposta idêntica para e-mail cadastrado ou não. - Frontend: link "Esqueci minha senha" no LoginPage, novas ForgotPasswordPage/ResetPasswordPage. Teste de integração novo (password-reset.spec.ts, 3 testes) usa vi.mock para interceptar sendMail e recuperar o token real do corpo do e-mail — única forma de testar o caminho feliz de ponta a ponta, já que o token nunca é devolvido na resposta da API. Suíte completa: 33/33 (30→33). pnpm turbo run typecheck build (28/28). Testado ao vivo através do proxy same-origin: anti-enumeração confirmada com curl real, token gravado no Postgres, token bogus rejeitado (400). Claude-Session: https://claude.ai/code/session_01BcWWuKfykGtUweXmHD6cKx Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,3 +65,23 @@ export async function logout(refreshToken: string): Promise<void> {
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Always resolves the same way whether or not the email is registered —
|
||||
* the API returns the same generic message either way (anti-enumeration,
|
||||
* see auth.service.ts::forgotPassword) — never treat this as confirmation
|
||||
* the account exists.
|
||||
*/
|
||||
export async function forgotPassword(email: string): Promise<{ message: string }> {
|
||||
return apiRequest<{ message: string }>("/auth/forgot-password", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
}
|
||||
|
||||
export async function resetPassword(token: string, newPassword: string): Promise<void> {
|
||||
await apiRequest<void>("/auth/reset-password", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ token, newPassword }),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user