Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions IdentityProvider_DataModel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Modello dati e contratti — Identity Provider PoliNetwork

**Documenti collegati:** [`PRD_Admin_Dashboard_PoliNetwork.md`](./PRD_Admin_Dashboard_PoliNetwork.md) §2.6 · [`IdentityProvider_Design.md`](./IdentityProvider_Design.md) · [`RBAC_Ruoli_Permessi.md`](./RBAC_Ruoli_Permessi.md)
**Versione:** 2.0
**Data:** 12 ottobre 2026

## 0. Stato e responsabilità

Il modello implementato vive nella repository autonoma `auth`, nel proprio database Postgres/Drizzle. Questa revisione segue il codice integrato agli SHA del PRD §20; non certifica migrazioni o configurazioni di produzione.

La precedente proposta `capability_grant`, `institutional_email_link`, `institutional_email_domain` non è stata adottata. I nomi di endpoint proposti (`authorization.getCapabilities`, `rbac.grantCapability`, ecc.) non sono contratti disponibili. Non creare queste tabelle per integrare un nuovo consumer: usare OIDC/OAuth, i permessi IdP e il contratto della RFC v3.

## 1. Schema implementato in auth

Fonti: `src/db/auth-schema.ts`, `src/db/evidence.ts`, `src/db/rbac.ts`, `src/db/access-snapshot.ts`.

### 1.1 Identità ed evidenze

- **`user` e `account`:** identità locale e collegamenti provider; ownership del collegamento necessaria prima di usare un'evidenza. Matching di email non unisce utenti automaticamente.
- **`identity_evidence`:** PK `(issuer, subject)`, `provider_id`, `external_id`, lista `states`, `valid_until`, `telegram_id`, `telegram_username`, `email`. Indice univoco parziale su `telegram_id` non nullo. Username/email sono dati di visualizzazione; l'autorizzazione usa identificativi verificati, non lo username.
- **`student_verification_challenge`:** PK `user_id` con FK a `user`, `email` univoca, `code_hash`, `attempts`, `expires_at`, `last_sent_at`. Hash HMAC, TTL 10 minuti, cinque tentativi e cooldown 60 secondi. Dominio ammesso: solo `mail.polimi.it`. Alla conferma viene creato l'account `polimi-email` e la relativa evidenza Student.
- **Account/session/passkey e tabelle OAuth:** gestite dall'IdP e dal provider. Socio/Direttivo provengono dai gruppi Entra, ricontrollati per autorizzare; l'evidenza memorizzata non prolunga la freschezza della verifica.

Queste tabelle non includono un registro di quote annuali, numeri associativi o censimento organizzativo.

### 1.2 RBAC globale

| Tabella | Significato |
|---|---|
| `permission` | Chiave stabile univoca, nome/descrizione, flag managed |
| `permission_implication` | Permesso → permesso implicato; transitiva, senza cicli |
| `role` | Bundle con chiave univoca, flag managed e possibile `source_state` |
| `role_parent` | Ruolo → ruolo ereditato; transitiva, senza cicli |
| `role_permission` | Collegamenti tra ruoli e permessi |
| `user_role` | Assegnazione manuale utente→ruolo con `assigned_by`, `assigned_at`; i ruoli gestiti non sono assegnazioni manuali |
| `rbac_audit_event` | Attore, operazione, target, prima/dopo JSON, data; append-only, scritto nella transazione della mutazione |

Nel modello implementato non ci sono campi `level`, `scope_type`, `scope_value`, revoca temporale del grant o assegnazione diretta di permessi a utenti come prevedeva la proposta originaria. La rimozione di un'assegnazione viene tracciata nell'audit. Gli scope di OAuth delimitano il client, non il corso/team dei record.

I ruoli gestiti sono Master Admin (wildcard da configurazione), Socio/Direttivo (Entra), Student (email verificata). Il permesso `membership:read` non implementa da solo l'area tesseramento o i rinnovi. Implicazioni e ruoli personalizzati possono concedere accesso senza costituire prova dello stato associativo: distinguere `states`, `roles` e `permissions`.

### 1.3 Snapshot e notifiche

L'IdP pubblica snapshot versionati/proiettati per client e notifiche da outbox; il backend effettua anche polling. La relazione Telegram→identità deriva dai collegamenti verificati, non da import di vecchi codici. Per campi wire, validità per fonte e comportamento in caso di outage si segue la RFC v3 §§5–7 e il codice, evitando un secondo schema divergente in questo PRD.

## 2. Contratti implementati fra servizi

- **OIDC persone:** Authorization Code/PKCE e refresh token. I consumer verificano issuer, audience, firma e claim; `polinetwork:identity` espone stati, ruoli, permessi e Telegram ID. Per API backend l'autorità corrente viene verificata tramite snapshot, non soltanto dai claim di un vecchio ID token.
- **OAuth servizi:** client credentials con `private_key_jwt`, risorsa/audience e scope assegnati al client. Token personali e di servizio hanno tipi attore distinti.
- **Backend tRPC:** `me.access` restituisce l'accesso corrente della persona. Ogni procedura ha policy per tipo attore, scope e permesso. Il bot può usare `X-PN-Actor: telegram:<id>` solo con il relativo scope act-as.
- **Snapshot IdP:** lettura di servizio autenticata, proiezione configurata per client e notifiche interne; il backend conserva in `common_idp_state` l'ultimo snapshot/chiavi validi per un uso limitato nel tempo.
- **Dashboard BFF:** sessione in Redis, token solo sul server; le funzioni server applicano middleware e inoltrano bearer token al backend.

La compatibilità legacy senza token resta configurabile nel backend; un token invalido viene rifiutato e non ripiega sul legacy. Le nuove procedure IdP, incluso lo storico giornaliero utenti Telegram, non accettano chiamate anonime.

## 3. Dati backend implementati per la transizione

- **Audit Telegram:** attore IdP (`actor_sub`), ID Telegram opzionale, client, `basis`, eventuale review e chiave di idempotenza; le azioni automatiche/native Telegram sono distinte da quelle autorizzate per permesso IdP.
- **Grant:** `tg_grants_v2` per i caller token, con attori IdP; il backend considera anche i grant legacy fino al cleanup. Non dedurre dai nuovi campi che la vecchia tabella sia già eliminata.
- **Contenuti:** colonne `*_by_sub` per l'autore IdP; non costituiscono un audit unificato prima/dopo di ogni modifica.
- **Microsoft 365:** lettura directory, workflow dedicato di creazione e membership dei gruppi tramite Graph, con permessi separati. Non è lo schema del registro associativo PoliNetwork.

## 4. Proposta residua: registro associativo e claim

Lo schema definitivo del registro del PRD §3.1 va deciso prima delle FK. Conservare un identificativo associativo univoco, stati e annualità storicizzati, consensi versionati e collegamento opzionale all'identità IdP; nessuna dipendenza obbligatoria da account Entra/Telegram.

Una possibile futura tabella **`membership_claim`** (non implementata) conserva:

| Campo logico | Requisito proposto |
|---|---|
| ID | Identificativo univoco del claim |
| Record socio / numero associativo | Riferimento al registro futuro |
| Email destinatario | Indirizzo verificato dal processo di iscrizione |
| Hash codice | Mai codice in chiaro |
| Scadenza e tentativi | TTL e limite da definire; uso singolo e invalidazione del precedente codice |
| Identità richiedente e data claim | `user.id` dell'IdP, valorizzato solo dopo verifica e controllo conflitti |
| Creazione / attore emissione | Tracciabilità dell'emissione e dei re-invii |

Il claim deve collegare il record alla persona senza duplicare l'identità, rifiutare conflitti e concedere solo accesso ai propri dati. Emissione come effetto dell'approvazione o azione manuale, re-invio, TTL e riconciliazione con lo stato Socio Entra restano aperti. Non sono disponibili oggi endpoint `identity.membershipClaim.*`.

## 5. Modello ancora da definire

1. Schema Anagrafica Soci/Censimento, annualità, collegamento fra database e retention dei dati.
2. Scope corso/team, relazioni di responsabilità e policy dei record lato backend: il RBAC globale non li rappresenta.
3. Consensi/firme e testo versionato, audit amministrativo comune oltre al solo RBAC/moderazione.
4. Catalogo IdP dei nuovi moduli: sostituire le chiavi descrittive della roadmap con permessi esatti condivisi tra UI e backend.
5. Modalità sicura di claim e merge ownership-verified, che non esiste nel semplice collegamento account corrente.
6. Conferma operativa delle fasi 5–6: backup e rimozione delle tabelle legacy restano separati da questa revisione documentale.
87 changes: 87 additions & 0 deletions IdentityProvider_Design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Design — Identity Provider PoliNetwork

**Documenti collegati:** [`PRD_Admin_Dashboard_PoliNetwork.md`](./PRD_Admin_Dashboard_PoliNetwork.md) §2.6 · [`PRD_Classificazione_Feature.md`](./PRD_Classificazione_Feature.md) #0 · [`RBAC_Ruoli_Permessi.md`](./RBAC_Ruoli_Permessi.md) · [`IdentityProvider_DataModel.md`](./IdentityProvider_DataModel.md)
**Versione:** 2.0
**Data:** 12 ottobre 2026
**Base tecnica implementata:** repository autonoma `auth`, Better Auth e `@better-auth/oauth-provider` 1.7.2, Drizzle/Postgres.

## 0. Baseline e fonti

Questa revisione descrive il codice integrato, non certifica il deploy o il completamento della migrazione operativa. SHA e fonti per repository sono nel PRD §20. Per il contratto interservizio si segue la [RFC IdP–Telegram v3](https://github.com/PoliNetworkOrg/auth/blob/7ae7521f895c342fe33ee77264f4f9ef1531055c/docs/idp-telegram-integration-rfc-v3.md), con le successive modifiche Azure descritte nel codice dei consumer.

La proposta precedente di estendere il Better Auth del backend mantenendo OTP email, link Telegram a codice e `capability_grant` è superata. L'IdP è autonomo; admin è un consumer OIDC. Il backend conserva temporaneamente l'autenticazione e i dati legacy per la transizione.

## 1. Identità e collegamenti implementati

L'identità locale è `user.id`; gli account esterni sono identificati per issuer/subject verificati. Email uguali non fanno un merge automatico di due persone. I collegamenti possono avere indirizzi diversi.

- **Google e PoliNetwork Entra:** login e registrazione. Entra è vincolato al tenant PoliNetwork e richiede `acct: 0`: gli account guest sono rifiutati.
- **Passkey:** registrabili dopo il login e utilizzabili per gli accessi successivi. Il server impedisce di scollegare l'ultimo account Google/Entra anche se rimangono passkey o account di verifica.
- **Telegram:** collegamento opzionale nell'account tramite OIDC ufficiale, PKCE e ID token RS256; non è un login diretto. L'ID Telegram numerico è salvato in `identity_evidence` con unicità; lo username è metadato aggiornabile per visualizzazione, non una chiave di autorizzazione. Collegare Telegram non assegna alcun ruolo.
- **Student:** prova del possesso di un indirizzo `@mail.polimi.it` tramite codice email. Socio, Student, Direttivo e Telegram collegato sono fatti indipendenti.
- **Socio/Direttivo:** oggi derivano dai gruppi configurati del tenant Entra PoliNetwork; non dal pagamento della quota o da un codice di claim del registro associativo.

L'account personale gestisce collegamenti, passkey e sessioni. La directory amministrativa IdP mostra gli stati individuali e i metodi di accesso; la gestione dei ruoli ha permessi separati. Questa directory non è l'Anagrafica Soci o il Censimento Admin/Team del PRD.

## 2. Verifica Polimi implementata

Sono ammessi solo indirizzi del dominio esatto `mail.polimi.it`, non `polimi.it` e non un'allowlist di domini amministrabile.

Il codice ha sei cifre, TTL di 10 minuti, massimo cinque tentativi e cooldown di reinvio di 60 secondi su utente e destinatario. Il database salva l'HMAC del codice. La verifica riuscita crea il collegamento `polimi-email` e l'evidenza Student con durata `STUDENT_VERIFICATION_TTL_DAYS`. L'invio usa Microsoft Graph e `AZURE_EMAIL_SENDER`.

Questa verifica attesta il possesso della casella, non corso/anno, tesseramento, quota o consenso privacy. L'estensione ad altri domini resta una decisione di prodotto.

## 3. Socio: stato corrente e collegamento futuro al registro

Lo stato IdP `socio` deriva dalla membership Entra del gruppo Soci. Non prova autonomamente che la quota annuale sia pagata e non fornisce numero associativo, scadenze o storico.

Resta necessario il registro indipendente del PRD §3.1, accessibile anche senza account Microsoft 365. La proposta di claim via codice resta **da progettare e implementare**, insieme alla riconciliazione con Entra:

1. Un operatore approva l'iscrizione e genera il numero associativo.
2. Il sistema invia all'email di iscrizione un codice di claim con hash, TTL e limite tentativi.
3. La persona accede all'IdP con un metodo disponibile e dimostra il possesso del codice.
4. Il registro viene collegato al suo `user.id`, senza richiedere email PoliNetwork o Telegram.
5. L'accesso alla propria scheda, ai rinnovi e ai consensi è limitato al proprio record.

Invio automatico o manuale, TTL, re-invio a un'altra email e conflitti tra claim/Entra sono decisioni aperte. `membership_claim` e i relativi endpoint non esistono oggi. Il claim di un registro non deve essere confuso con l'ereditarietà del ruolo gestito Socio.

## 4. RBAC implementato e residuo

L'IdP salva ruoli, permessi, assegnazioni utente→ruolo, ereditarietà dei ruoli e implicazioni dei permessi. Non implementa una tabella di grant individuali con `level`, `scopeType` e `scopeValue`.

I ruoli gestiti sono `master-admin`, `socio`, `direttivo`, `student`. Master Admin è un wildcard derivato dalla configurazione di bootstrap/gruppo Entra; Socio e Direttivo sono verificati tramite Graph, con cache di autorizzazione di 60 secondi e diniego se la verifica fallisce. I ruoli personalizzati sono assegnabili con `idp:roles:assign`, separato da `idp:roles:write`. La delega non può eccedere i permessi correnti dell'operatore. Le mutazioni RBAC sono registrate atomicamente in `rbac_audit_event`, append-only.

Admin richiede `admin:access` e i permessi puntuali dei moduli; il backend li verifica indipendentemente. I ruoli Telegram legacy non vengono mappati automaticamente, e il veto globale `creator` non è riprodotto. Il catalogo iniziale non assegna i permessi Telegram ai ruoli ordinari: le assegnazioni vanno approvate e riconciliate.

**Residuo:** scope corso/team verificato sui record anche dal backend; permessi per Anagrafica Soci, rinnovi, consensi e onboarding. Il modello globale attuale non può garantire la vista Capo Admin limitata al proprio ambito.

## 5. OIDC e comunicazione fra servizi

- **polinet.cc:** login OIDC implementato, consumer autonomo; non è necessario condividere cookie.
- **admin:** Authorization Code con PKCE/state/nonce; verifica degli ID token, token mantenuti nelle sessioni Redis del BFF e bearer token verso backend. Il browser riceve dati utente e permessi, non i token. Refresh coordinato per sessione e logout sono parte del contratto BFF.
- **web:** token di servizio client credentials con `backend:public:read` per le letture server→backend. Non è ancora un login OIDC personale degli studenti sul sito; alcune ricerche gruppi restano anonime durante la transizione.
- **telegram:** token di servizio e attori Telegram isolati (`X-PN-Actor`); modalità shadow/enforce e outbox audit. Il bot non conserva refresh token personali degli admin.
- **backend:** verifica token/scope/tipo attore e permessi correnti tramite snapshot IdP, aggiornato da notifiche e polling; conserva ultimo snapshot/chiavi validi per disponibilità limitata.

Le applicazioni e le risorse OAuth sono amministrate nell'IdP, con client di servizio e interattivi, rotazione chiavi/segreti e gestione degli scope. Scope del client e permesso della persona sono controlli distinti.

## 6. Migrazione ancora da verificare operativamente

Le fasi di implementazione 0–4 della RFC hanno codice nei consumer. Non si assume concluso il provisioning o il cutover perché i commit sono integrati:

- riconciliare persone/ruoli legacy e collegamenti Telegram verificati nell'IdP;
- provisionare risorse, client e chiavi separate, e verificare i flussi nell'ambiente di destinazione;
- confrontare shadow/enforce del bot, poi effettuare il cutover coordinato;
- chiudere il percorso anonimo (`LEGACY_ANONYMOUS=deny`) e rimuovere input/API legacy secondo fase 5;
- eseguire backup, cleanup dei dati/auth legacy e hardening di rete secondo fase 6.

Il backend conserva ancora i grant legacy e `tg_grants_v2`; un grant attivo in una delle due fonti conta durante la transizione. Cleanup e migrazioni distruttive non sono attività di questa PR documentale.

## 7. Decisioni residue

1. Fonte e riconciliazione dello status associativo pagante rispetto al ruolo Socio Entra.
2. Modalità di claim del registro, invio/re-invio dei codici e gestione dei conflitti di identità.
3. Scope corso/team e catalogo dei nuovi moduli, con enforcement nel backend.
4. Primo flusso del sito pubblico che richiede login personale.
5. Eventuale estensione dei domini di verifica studente.
6. Evidenze operative del completamento del cutover e cleanup, senza dedurle dagli SHA applicativi.
Loading
Loading