# Invio email via Microsoft 365 con OAuth2 (Microsoft Graph) — Guida riutilizzabile

Documentazione del setup fatto per **Supernova** (tool Preventivi, invio da
`sales@supernova-group.it`, piano Microsoft 365 Business/Essentials).
Riutilizzabile in qualsiasi altro progetto Node/Express che debba inviare
email a nome di un account Microsoft 365, senza salvare password.

Riferimento: [[project_gix_supernova_ms365_oauth2]] nella memoria.

---

## 1. Perché OAuth2 e non SMTP+password

Con Microsoft 365 esistono due strade per inviare email via codice:

| | SMTP AUTH (user+password/app password) | OAuth2 + Microsoft Graph |
|---|---|---|
| Setup | Veloce (10 min), va abilitato per utente/tenant | Più lungo (registrazione app Entra ID) |
| Credenziali salvate | Password (o app password) in chiaro nel DB/`.env` | Nessuna password: solo client secret + refresh token |
| Futuro | Microsoft sta disattivando Basic Auth su molti tenant per policy di sicurezza | Via raccomandata da Microsoft, nessun rischio di deprecazione imminente |
| Allegati | Sì (nodemailer) | Sì (base64 in `POST /me/sendMail`) |

Per un progetto nuovo conviene **sempre OAuth2**, salvo urgenza estrema.

---

## 2. Setup lato Microsoft Entra ID (da fare una volta per progetto/tenant)

Questi passi li fa un amministratore del tenant Microsoft 365 (Global Admin
o almeno permessi su App registrations).

### 2.1 Registra l'app

1. **entra.microsoft.com** (o portal.azure.com → cerca "Entra ID" → icona
   scudo/infinito nella griglia "Microsoft Cloud")
2. Menu sinistro → **App registrations** → **+ New registration**
3. Compila:
   - **Name**: nome descrittivo (es. `<Progetto> Mailer`)
   - **Supported account types**: *Accounts in this organizational directory
     only (Single tenant)* — corretto quando l'app serve un solo tenant/account
   - **Redirect URI**: tipo **Web**, valore `https://<dominio>/api/o2callback-ms`
     — ⚠️ **deve combaciare esattamente** con l'URL che il backend espone
     (vedi nota sotto su reverse proxy)
4. **Register**

Annota da "Panoramica": **Application (client) ID** e **Directory (tenant) ID**.

> **Nota reverse proxy**: se Apache/Nginx instrada solo un prefisso (es.
> `/api/`) al backend Node e il resto va al frontend statico, il redirect URI
> registrato su Azure **deve includere quel prefisso**
> (`https://dominio/api/o2callback-ms`, non `https://dominio/o2callback-ms`).
> Un mismatch qui produce l'errore Microsoft `AADSTS50011: redirect URI
> mismatch` al primo tentativo di autorizzazione — capitato anche a noi,
> risolto correggendo il Redirect URI in Azure per includere `/api`.

### 2.2 Crea un Client Secret

App → **Certificates & secrets** → **+ New client secret**
- Descrizione libera, scadenza consigliata **24 mesi**
- Copia subito il **Value** (visibile una sola volta) — segnati anche la
  **data di scadenza** per il rinnovo futuro

### 2.3 Assegna i permessi Graph

App → **API permissions** → **+ Add a permission** → **Microsoft Graph** →
**Delegated permissions**, aggiungi:
- `Mail.Send`
- `offline_access` (necessario per ottenere il refresh token)
- `User.Read` (per leggere l'email dell'account autorizzato via `/me`)

Poi clicca **"Grant admin consent for <tenant>"** in alto — richiede un
admin del tenant, spunta verde su tutte e 3 le righe.

### 2.4 Dati raccolti a fine setup

A questo punto hai 4 valori da portare nel codice:
- `client_id`
- `client_secret`
- `tenant_id`
- `redirect_uri` (`https://<dominio>/api/o2callback-ms`)

---

## 3. Implementazione backend (Node/Express, pattern riutilizzabile)

Il pattern è: **Authorization Code Flow** con consenso una tantum
dell'admin → refresh token salvato in DB → access token rigenerato ad ogni
invio via refresh token (che Microsoft può ruotare, va sempre risalvato se
cambia).

### 3.1 File credenziali (mai in git)

`backend/src/microsoft-oauth2-credentials.json`:
```json
{
  "client_id": "...",
  "client_secret": "...",
  "tenant_id": "...",
  "redirect_uri": "https://dominio/api/o2callback-ms"
}
```
Aggiungere a `.gitignore`.

### 3.2 Modulo OAuth2 (`services/microsoftOAuth.js`)

Espone:
- `MS_OAUTH2_SCOPES` = `['offline_access', 'https://graph.microsoft.com/Mail.Send', 'https://graph.microsoft.com/User.Read']`
- `exchangeCodeForTokens(code)` — scambia il `code` del callback con
  access+refresh token, salva il refresh token in DB (tabella settings
  key/value o equivalente), recupera l'email via `GET /v1.0/me`
- `getMsAccessToken()` — usa il refresh token salvato per ottenere un access
  token fresco ad ogni invio (i token Graph durano ~1h, non si salva
  l'access token, solo il refresh token); se Microsoft ne restituisce uno
  nuovo, lo aggiorna in DB
- `isMsOAuthEnabled()` / `getMsOAuthUser()` / `revokeMsOAuth()` — helper di stato

Endpoint token: `https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/token`

### 3.3 Tre endpoint HTTP (mirror pattern OAuth2 già visto per Google)

| Endpoint | Metodo | Chi può chiamarlo | Cosa fa |
|---|---|---|---|
| `/api/admin/microsoft-oauth2/init` | GET | admin | genera `state` anti-CSRF, lo salva, ritorna l'URL di consenso Microsoft |
| `/api/o2callback-ms` (pubblico, **non** sotto `/admin`) | GET | chiunque con `code`+`state` validi | verifica `state`, scambia `code`→token, salva refresh token, mostra pagina HTML di conferma |
| `/api/admin/microsoft-oauth2/revoke` | DELETE | admin | cancella il refresh token dal DB |
| `/api/admin/microsoft-oauth2/status` | GET | chiunque loggato | ritorna `{connected, user}` — utile a ruoli con permessi limitati che devono solo sapere se possono inviare |

L'URL di autorizzazione (`init`) è:
```
https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/authorize
  ?client_id=...
  &redirect_uri=...
  &response_type=code
  &response_mode=query
  &scope=<MS_OAUTH2_SCOPES join spazio>
  &prompt=select_account
  &state=<random>
```

### 3.4 Invio email (Microsoft Graph, non SMTP)

A differenza di Gmail (dove nodemailer può usare OAuth2 su SMTP
`smtp.gmail.com:465`), per Microsoft conviene usare direttamente **Graph
API** invece di SMTP OAuth2 — più semplice, gestisce nativamente allegati
in base64:

```
POST https://graph.microsoft.com/v1.0/me/sendMail
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "message": {
    "subject": "...",
    "body": { "contentType": "HTML", "content": "..." },
    "toRecipients": [{ "emailAddress": { "address": "..." } }],
    "attachments": [{
      "@odata.type": "#microsoft.graph.fileAttachment",
      "name": "file.pdf",
      "contentType": "application/pdf",
      "contentBytes": "<base64>"
    }]
  },
  "saveToSentItems": true
}
```

Risposta `202 Accepted` senza body se ok; errore in JSON con
`error.message` altrimenti.

### 3.5 Frontend (pattern riutilizzabile)

- Stato reattivo `{ connected, user }` caricato da `/status` al mount del
  pannello impostazioni
- Bottone "Autorizza con Microsoft" → `GET /init` → `window.location.href = data.url`
  (redirect pieno, non popup — più affidabile)
- Dopo il consenso, Microsoft reindirizza al callback backend, che mostra
  una pagina HTML di conferma con link "torna al pannello"
- Bottone "Revoca" → `DELETE /revoke` con conferma

---

## 4. Errori comuni e fix

| Errore | Causa | Fix |
|---|---|---|
| `AADSTS50011: redirect URI mismatch` | Redirect URI in Azure ≠ redirect URI effettivo del backend (spesso manca il prefisso `/api` dietro reverse proxy) | Allinea il valore in Azure (App → Authentication) con `redirect_uri` nel file credenziali |
| `Refresh token non ricevuto` / manca `refresh_token` nella risposta token | Manca lo scope `offline_access`, o l'app era già stata autorizzata senza `prompt=consent`/`select_account` | Verifica lo scope; se serve, l'utente può revocare il consenso da `myaccount.microsoft.com/permissions` e ri-autorizzare |
| 403 su `Mail.Send` all'invio | Permesso Graph non concesso con admin consent | App → API permissions → verifica spunta verde "Grant admin consent" su tutte le righe |
| SMTP AUTH disabilitato (se in futuro si usa SMTP invece di Graph) | Microsoft disabilita Basic Auth per policy di sicurezza di default su molti tenant nuovi | `Set-CASMailbox -Identity <user> -SmtpClientAuthenticationDisabled $false` via PowerShell Exchange Online, o Centro ammin Exchange |

---

## 5. Checklist per replicare in un nuovo progetto

- [ ] Registrare app su Entra ID del tenant del cliente (single-tenant)
- [ ] Redirect URI = `https://<dominio-progetto>/api/o2callback-ms` (verificare il prefisso reale dietro il proxy)
- [ ] Client secret creato, valore e scadenza annotati
- [ ] Permessi Graph: `Mail.Send`, `offline_access`, `User.Read` + admin consent
- [ ] Copiare `microsoftOAuth.js` (adattare la persistenza settings al DB del progetto)
- [ ] Copiare i 4 endpoint (`init`/`callback`/`revoke`/`status`)
- [ ] Copiare la funzione `sendMailViaGraph()` (adattare al service email del progetto)
- [ ] File credenziali in `.gitignore`
- [ ] Pagina admin: bottone autorizza + stato + revoca
- [ ] Test end-to-end: login → autorizza → invio reale
