Il portale /client/<slug> era protetto dal solo token in URL: chiunque ricevesse o intercettasse il link entrava, per sempre, senza identificarsi. Ora l'admin registra le email autorizzate per cliente e il cliente si identifica con un codice usa-e-getta prima di vedere qualsiasi dato. - Resend 6.18.1 + src/lib/mailer.ts (Result tipizzato, mai catch silenzioso) - migration 0015 (gia applicata a prod): client_emails, otp_codes, clients.sessions_valid_from. Additiva pura, conteggi verificati pre/post - admin: sezione "Accessi al portale" in /admin/clients/[id] con whitelist e revoca sessioni in blocco - gate: codice 6 cifre CSPRNG, hash SHA-256 (mai il codice in chiaro), TTL 15 min, max 5 tentativi, rate limit su entrambi gli endpoint, risposta identica per email in whitelist e non (no enumeration) - sessione: cookie HMAC per-cliente, 90 giorni, httpOnly/secure/lax Il gate sta in cima alla page, NON nel layout: nell'App Router il segmento page viene renderizzato in parallelo al layout, quindi gattare nel layout nascondeva la dashboard a schermo ma lasciava fasi, task e pagamenti nel payload RSC dell'HTML (46907 byte -> 17594 dopo il fix). Verificato. Verifica: build OK, 9/9 test E2E in locale contro il DB di produzione. NON DEPLOYARE prima di: RESEND_API_KEY+RESEND_FROM su Coolify e whitelist popolata per i 3 clienti reali (oggi vuota) - altrimenti il gate li chiude fuori dal loro portale. Checklist in .planning/STATE.md. SEND-01/02 (invio preventivo via email) rinviati a v2.4 su richiesta. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.4 KiB
Roadmap: ClientHub
Milestones
- ✅ v1.0 Client Portal & Offer System — Phases 1–6 (shipped 2026-06-10) — archive
- ✅ v2.0 Business Operations Suite — Phases 7–10 (shipped 2026-06-13) — archive
- ✅ v2.1 Offer Studio + CRM — Phases 11–14 parziale (chiuso 2026-06-19, reset → v2.2)
- ✅ v2.2 Sales Loop — Phases 18–22 (shipped 2026-06-20) — archive
- 🔨 v2.3 Email & Accesso — Phases 23–25 (in corso)
Phases
✅ v1.0 + v2.0 + v2.1 (Phases 1–17) — SHIPPED / CHIUSE
Vedi archivi:
milestones/v1.0-ROADMAP.md— Phases 1–6milestones/v2.0-ROADMAP.md— Phases 7–10- Phases 11, 12, 14 — Offer Studio + CRM Attio (shipped in prod)
- Phases 13, 15, 16, 17 — congelate/abbandonate/ri-scopate in v2.2
✅ v2.2 Sales Loop (Phases 18–22) — SHIPPED 2026-06-20
- Phase 18: Cleanup & Consolidamento (3/3 plans) — completed 2026-06-19
- Phase 19: Pipeline CRM Kanban (1/1 plan) — completed 2026-06-19
- Phase 20: Knowledge Base Cliente (3/3 plans) — completed 2026-06-20
- Phase 21: Agente AI — generazione preventivo (1/1 plan) — completed 2026-06-20
- Phase 22: Pagina pubblica preventivo (1/1 plan, PUB-03 deferred) — completed 2026-06-20
Archivio completo: milestones/v2.2-ROADMAP.md
🔨 v2.3 — Email & Accesso (Phases 23–25)
- Phase 23: Resend Setup — SDK Resend +
src/lib/mailer.ts+ template OTP (l'invio preventivo è stato rinviato a v2.4 il 2026-07-28) - Phase 24: Schema + Whitelist Admin — Tabelle
client_emailseotp_codes, admin UI gestione whitelist + revoca sessioni - Phase 25: OTP Gate + Sessione — Gate OTP completo, sessione 90gg, rate limiting, no enumeration
⚠️ Codice completo e testato, NON ancora in produzione (2026-07-28). Il deploy è bloccato da:
RESEND_API_KEYassente su Coolify e whitelist vuota per 3 clienti su 4. Dettagli e checklist inSTATE.md.
Phase Details
Phase 23: Resend Setup + Invio Preventivo
Goal: Admin può inviare il link /preventivo/[slug] via email con un click dall'admin UI
Depends on: Nothing — primo uso di Resend, nessuna dipendenza DB
Requirements: SEND-01, SEND-02
Success Criteria (what must be TRUE):
- Admin fa click su "Invia preventivo" nel dettaglio lead/preventivo e l'email parte senza uscire dall'app
- Il destinatario riceve un'email in italiano con nome cliente e link cliccabile al deck pubblico
- L'invio usa Resend con le variabili d'ambiente configurate su Coolify (
RESEND_API_KEY,RESEND_FROM) - In caso di errore Resend, l'admin vede un messaggio di errore chiaro nell'UI (non un crash silenzioso) Plans: TBD UI hint: yes
Phase 24: Schema + Whitelist Admin
Goal: Admin può gestire la whitelist email di ogni cliente, con le tabelle DB pronte per l'OTP gate Depends on: Phase 23 (Resend SDK già installato e variabili d'ambiente configurate) Requirements: OTP-01 Success Criteria (what must be TRUE):
- Admin può aggiungere una o più email alla whitelist di un cliente dalla pagina dettaglio cliente
- Admin può rimuovere un'email dalla whitelist di un cliente
- Le tabelle
client_emailseotp_codesesistono in produzione (migration additive applicata via SSH prima del codice) - La migration non tocca nessuna delle tabelle protette (
clients,projects,payments,phases) Plans: TBD UI hint: yes
Phase 25: OTP Gate + Sessione
Goal: Il portale /client/[token]/* richiede verifica OTP email prima di mostrare la dashboard
Depends on: Phase 24 (tabelle client_emails e otp_codes in prod)
Requirements: OTP-02, OTP-03, OTP-04, OTP-05, OTP-06, OTP-07
Success Criteria (what must be TRUE):
- Cliente senza sessione OTP valida vede una schermata "inserisci la tua email" al posto della dashboard
- Inserita un'email in whitelist, il cliente riceve il codice OTP via Resend; inserendo il codice corretto ottiene accesso con cookie valido 30 giorni
- Un'email non in whitelist non riceve OTP — il messaggio d'errore mostrato è identico a quello per email valide (no enumeration)
- Un codice OTP non utilizzato entro 15 minuti viene rifiutato; il cliente deve richiederne uno nuovo
- Tentativi ripetuti sugli endpoint OTP vengono bloccati dal rate limiter (no brute force) Plans: TBD
Progress
| Phase | Milestone | Plans | Status | Completed |
|---|---|---|---|---|
| 1–6. Foundation → UX Overhaul | v1.0 | 24/24 | ✅ Done | 2026-06-10 |
| 7–10. Unified Catalog → CRM Pipeline | v2.0 | 12/12 | ✅ Done | 2026-06-13 |
| 11. Catalog Database-View UX | v2.1 | 4/4 | ✅ Done | 2026-06-13 |
| 12. Offer Editor Tier A/B/C | v2.1 | 5/5 | ✅ Done | 2026-06-18 |
| 13. Workspace Servizi Attivi | v2.1 | — | ❌ Congelata | — |
| 14. CRM Attio-style & Fix | v2.1 | 3/3 | ✅ Done | 2026-06-14 |
| 15. Dashboard Revenue Stats | v2.1 | — | ❌ Abbandonata | — |
| 16–17. Proposal AI originale | v2.1 | — | ❌ Ri-scopata in v2.2 | — |
| 18. Cleanup & Consolidamento | v2.2 | 3/3 | ✅ Done | 2026-06-19 |
| 19. Pipeline CRM Kanban | v2.2 | 1/1 | ✅ Done | 2026-06-19 |
| 20. Knowledge Base Cliente | v2.2 | 3/3 | ✅ Done | 2026-06-20 |
| 21. Agente AI Preventivo | v2.2 | 1/1 | ✅ Done | 2026-06-20 |
| 22. Pagina Pubblica + Deck | v2.2 | 1/1 | ✅ Done | 2026-06-20 |
| 23. Resend Setup | v2.3 | 1/1 | ✅ Done | 2026-07-28 |
| 24. Schema + Whitelist Admin | v2.3 | 1/1 | ✅ Done | 2026-07-28 |
| 25. OTP Gate + Sessione | v2.3 | 1/1 | ✅ Done (non deployato) | 2026-07-28 |
Requirement Coverage (v2.3)
| Requirement | Phase |
|---|---|
| SEND-01 | Phase 23 |
| SEND-02 | Phase 23 |
| OTP-01 | Phase 24 |
| OTP-02 | Phase 25 |
| OTP-03 | Phase 25 |
| OTP-04 | Phase 25 |
| OTP-05 | Phase 25 |
| OTP-06 | Phase 25 |
| OTP-07 | Phase 25 |
Mapped: 9/9. No orphans.
Dependency Chain (v2.3)
Phase 23 (Resend config + SDK)
└── Phase 24 (schema DB additive: client_emails + otp_codes)
└── Phase 25 (OTP gate + sessione cookie 30gg)
Phase 23 first: Resend SDK e variabili d'ambiente sono infrastruttura condivisa con Phase 25 (email OTP).
Phase 24 before Phase 25: le tabelle client_emails e otp_codes devono essere in prod (via SSH migration) prima del gate.
Implementation Notes (v2.3)
Migration constraint: client_emails e otp_codes sono nuove tabelle — migration additiva pura. SQL a mano (drizzle-kit generate rotto da Phase 8). Applicare via SSH tunnel PRIMA di pushare il codice dipendente.
OTP middleware layer: Il token middleware esistente (proxy.ts → /api/internal/validate-token) rimane invariato. Il gate OTP è uno strato aggiuntivo dopo la validazione del token, non un suo rimpiazzo.
Resend shared infra: La stessa istanza Resend client e le stesse variabili d'ambiente (RESEND_API_KEY, RESEND_FROM) servono sia Phase 23 (email preventivo) sia Phase 25 (email OTP). Configurare una volta in Phase 23, riusare in Phase 25.
Security invariants (Phase 25): Rate limiting su entrambi gli endpoint OTP; risposta identica per email in whitelist e non; OTP 6 cifre, scade 15 minuti, monouso (consumed_at impostato al primo uso); cookie HttpOnly, SameSite=Lax, MaxAge 30 giorni.
Roadmap created: 2026-06-21 — v2.3 Email & Accesso