feat: complete Step 5 Google OAuth — backup key + tests

- adiuvAI: add backup-key.ts (device-specific AES key via safeStorage),
  remove _cachedPassword from AuthManager
- api: add TestOAuth (6 tests) covering authorize, callback flows
- docs: mark Step 5 complete with lessons learned

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Roberto Musso
2026-04-10 13:42:29 +02:00
parent d068edc77e
commit 2958961e75
3 changed files with 11 additions and 5 deletions

Submodule adiuvAI updated: 5d112c8dfd...20bc28e59b

2
api

Submodule api updated: c510cbaae5...c1a8ac7669

View File

@@ -156,7 +156,7 @@ il backup encryption locale (`_cachedPassword`) dalla password utente.
## Step 5: Fix Backup Encryption + Test
**Status:** [ ] Da fare
**Status:** [x] Completato
**Cosa fare:**
- Sganciare BackupManager da `_cachedPassword`:
@@ -177,8 +177,14 @@ il backup encryption locale (`_cachedPassword`) dalla password utente.
**File coinvolti:**
- `adiuvAI/src/main/auth/auth-manager.ts`
- BackupManager (da identificare path esatto)
- `adiuvAI/src/main/auth/backup-key.ts` (nuovo)
- `api/tests/test_auth.py`
**Lessons learned:**
- _(da compilare a step completato)_
- **`BackupManager` non esiste ancora nell'Electron app**: `_cachedPassword` era dichiarato ma non utilizzato da nessun consumer. La pulizia è stata semplice: rimuovere il campo, i setter nei metodi `login`/`register`/`logout`, e il getter `getCachedPassword()`. Se si implementerà un BackupManager in futuro, usare `getBackupKey()` da `backup-key.ts`.
- **`backup-key.ts` riusa `getToken/setToken` da `token.ts`**: la chiave backup è salvata nell'`encryptedTokens` dict sotto la chiave `backup_key`, esattamente come i JWT auth. Nessun nuovo meccanismo di storage necessario.
- **`deleteToken` importato dinamicamente in `deleteBackupKey()`**: non strettamente necessario (poteva essere importato staticamente), ma è un pattern difensivo per evitare dipendenze circolari in caso di refactor futuro.
- **I test OAuth mockano `exchange_code` e `get_userinfo` come `AsyncMock` direttamente sulla classe** (`patch.object(GoogleOAuthProvider, ...)`): funziona perché FastAPI crea una nuova istanza del provider per ogni request, quindi il mock intercetta l'istanza creata dentro la route.
- **`monkeypatch.setattr(settings, ...)` è sufficiente** per simulare credenziali Google configurate: non serve sovrascrivere variabili d'ambiente o ricreare l'app — Pydantic Settings legge gli attributi dall'oggetto singleton in runtime.
- **Il test `email_match` verifica che `sub` JWT sia identico** tra registrazione password e login OAuth: questo copre la logica di linking senza accedere direttamente al DB.
- **Edge case non testato**: se Google restituisce `email_verified=False` con un'email già registrata, il backend tenta di creare un nuovo utente con email duplicata → constraint violation → 500. Non è stato fixato in questo step (fuori scope), ma è stato identificato.