Files
simone f7eb7eec23 docs(planning): archivia v2.1/v2.2/v2.3 e documenta v2.4
.planning/ documentava in dettaglio cio che era vecchio e per niente cio
che e in produzione: le fasi 11-22 (v2.1 e v2.2, chiuse a giugno) erano
ancora in phases/ mentre v1.0 e v2.0 stavano gia in milestones/, e il
lavoro degli ultimi due mesi - gate OTP e ciclo di vita dei retainer, cioe
quello che gira su hub.iamcavalli.net - non aveva nessuna cartella.

- phases/{11,12,14} -> milestones/v2.1-phases/, phases/{18..22} ->
  milestones/v2.2-phases/. Ora phases/ contiene solo la milestone in
  corso, che e quello che state.cjs conta per il progresso
- v2.1-ROADMAP.md ricostruito: era l'unica milestone senza archivio,
  interrotta dal reset del 19/06 e mai chiusa formalmente
- v2.3-ROADMAP.md + v2.3-REQUIREMENTS.md: v2.3 e stata eseguita fuori dal
  ciclo GSD, non esistono PLAN/SUMMARY per fase. L'archivio E la doc
- REQUIREMENTS.md riscritto per v2.4 con il backlog reale
- phases/13 e phases/26: SUMMARY ricostruiti da commit, migration e
  STATUS.md. 26 e il primo numero libero
- research/: cancellate 4 varianti dello stesso PITFALLS e FEATURES/
  SUMMARY, superati da PROJECT.md. Diverse anti-feature erano ormai
  contraddette dai fatti (il Kanban e stato costruito in Phase 19,
  l'email in v2.3, il time tracking esiste)
- cancellati UI-RULES.md e DESIGN-SYSTEM.md (CLAUDE.md li dichiara
  superseded: impongono l'inverso della regola attuale) e HANDOFF.md,
  fermo al 13/06
- SECURITY-*.md -> security/: audit chiuso, ma i report restano la doc di
  cosa e stato ruotato e perche
- PROJECT.md/MILESTONES.md/ROADMAP.md allineati: milestone corrente v2.4,
  sessione OTP 90gg non 30, migrazioni fino alla 0016

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:38:36 +02:00

100 lines
4.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Archivio milestone v2.3 — Email & Accesso
**Fasi:** 2325 · **Aperta:** 2026-06-21 · **Shipped:** 2026-07-29 (commit `27da969`)
**Requisiti:** [v2.3-REQUIREMENTS.md](v2.3-REQUIREMENTS.md)
> **Nota di archivio.** v2.3 è stata eseguita **fuori dal ciclo GSD**: non sono mai
> esistite cartelle `phases/23`, `24`, `25` con PLAN/SUMMARY. Questo file *è*
> la documentazione della milestone — non cercare altrove.
## Obiettivo
Aggiungere uno strato email all'app: gate OTP per il portale cliente e invio del
link preventivo dall'admin, con un'unica integrazione Resend condivisa.
Il portale non doveva più essere apribile col solo link: chiunque avesse l'URL
vedeva il progetto del cliente.
## Fasi
### Phase 23 — Resend Setup ✅ 2026-07-28
**Goal:** infrastruttura email condivisa.
**Requisiti:** SEND-01, SEND-02 (poi ridotti — vedi sotto).
Consegnato: `resend@6.18.1`, `src/lib/mailer.ts` (Result tipizzato, mai un catch
silenzioso), template OTP in italiano. `RESEND_API_KEY` e `RESEND_FROM` configurate
su Coolify (production **e** preview).
**Riduzione di scope del 2026-07-28:** SEND-01/SEND-02 (invio del preventivo via
email dall'admin) spostati al backlog. Il preventivo si manda a mano; l'automazione
non serviva subito. Phase 23 si è ridotta alla sola infrastruttura Resend, che il
gate OTP usa comunque.
### Phase 24 — Schema + Whitelist Admin ✅ 2026-07-28
**Goal:** l'admin gestisce la whitelist email di ogni cliente; tabelle pronte per il gate.
**Requisiti:** OTP-01. **Dipende da:** Phase 23.
Migration `0015_otp_access.sql`, **additiva pura**, applicata a prod via SSH prima
del codice dipendente: `client_emails` (whitelist, unique case-insensitive),
`otp_codes` (hash del codice, mai il codice in chiaro), `clients.sessions_valid_from`
(revoca in blocco). Conteggi pre/post identici sulle tabelle protette —
clients 4 / projects 5 / payments 11 / phases 10.
UI: sezione "Accessi al portale" in `/admin/clients/[id]` — aggiungi/rimuovi email,
"Revoca sessioni attive". Server actions in `clients/[id]/actions.ts`.
### Phase 25 — OTP Gate + Sessione ✅ 2026-07-29
**Goal:** `/client/[token]/*` richiede verifica OTP prima di mostrare la dashboard.
**Requisiti:** OTP-02..OTP-07. **Dipende da:** Phase 24.
Consegnato: `src/lib/otp.ts` (codice 6 cifre CSPRNG, hash SHA-256 con
`NEXTAUTH_SECRET`+clientId, TTL 15 minuti, monouso, max 5 tentativi),
`src/lib/client-session.ts` (cookie HMAC per-cliente `ch_sess_<id>`, httpOnly +
secure + SameSite=lax, `path=/client`), `src/lib/client-gate.ts`, le route
`/api/client/otp/request|verify`, il componente `OtpGate`.
**Scostamento dalla spec del 21/06:** sessione **90 giorni** invece di 30 — rientro
più fluido, compensato da OTP-08 (revoca in blocco lato admin).
## Catena di dipendenze
```
Phase 23 (Resend SDK + env)
└── Phase 24 (schema additivo: client_emails + otp_codes)
└── Phase 25 (gate OTP + sessione cookie 90gg)
```
## Copertura requisiti
| Requisito | Fase | Esito |
|---|---|---|
| SEND-01, SEND-02 | 23 | ⏭ Rinviati al backlog il 2026-07-28 |
| OTP-01 | 24 | ✅ |
| OTP-02 … OTP-07 | 25 | ✅ |
| OTP-08 (revoca) | 24 | ✅ |
## Verifica in produzione (2026-07-29, `hub.iamcavalli.net`)
Gate mostrato senza cookie e **zero dati di progetto nell'HTML** (12.487 byte);
email fuori e dentro whitelist danno risposta identica e solo la seconda genera un
OTP; codice sbagliato rifiutato, corretto accettato; cookie `ch_sess_<id>` con
`Secure` + `HttpOnly` + `SameSite=lax` + `Max-Age=7776000`; rientro col cookie
mostra la dashboard; la sessione di un cliente sull'URL di un altro mostra il gate.
Nessun errore d'invio nei log del container. Dati di test rimossi, tabelle protette
invariate.
## Lezioni
Le due lezioni operative di questa milestone (il gate non va nel layout App Router;
ricreare il dominio su Resend rigenera la chiave DKIM) sono in `STATUS.md`,
sezione "Lezioni operative" — è lì che si vanno a cercare.
## Strascico alla chiusura
La whitelist è stata seedata solo con `mario@test.it` (cliente di test). Tre clienti
reali su quattro hanno whitelist vuota e finché lo è **il loro portale non è
accessibile**. Voce aperta in `STATUS.md`.