Files
workspace/docs/plan-google-auth.md
2026-04-10 08:44:23 +02:00

6.4 KiB

Piano: Integrazione Google Login + Avatar

Contesto

L'app adiuvAI ha un sistema auth custom (email+password, bcrypt, JWT HS256) funzionante. Obiettivo: aggiungere Google Login come primo social provider, con la prospettiva di aggiungerne 2-3 in futuro (Microsoft, GitHub).

Decisione architetturale: OAuth diretto nel sistema esistente (non Keycloak, non Auth0).

  • Il backend continua a emettere i propri JWT
  • Ogni provider e' un'implementazione concreta di un'astrazione condivisa
  • Email+password resta il metodo primario di registrazione/login
  • L'avatar utente viene preso da Google; per utenti email si usano iniziali o icona default

Nota encryption: La encryption_key Fernet e' generata random, NON derivata dalla password. Il social login non rompe la crittografia server-side. L'unico fix necessario e' sganciare il backup encryption locale (_cachedPassword) dalla password utente.


Regole

  • Completamento step: aggiornare questo documento marcando lo step come [x] e annotare eventuali lessons learned
  • Commit: al termine di ogni step, eseguire commit del codice

Step 1: Backend — DB + Model + Schema

Status: [ ] Da fare

Cosa fare:

  • Migration Alembic: creare tabella oauth_accounts (id, user_id FK, provider, provider_user_id, provider_email, created_at; UNIQUE su provider, provider_user_id)
  • Migration Alembic: rendere users.password_hash nullable
  • Migration Alembic: aggiungere colonna users.avatar_url (VARCHAR, nullable)
  • Model OAuthAccount in api/app/models.py con relationship a User
  • Campo avatar_url: str | None sul model User
  • Aggiornare UserProfile in api/app/schemas.py con avatar_url

File coinvolti:

  • api/alembic/versions/XXX_add_oauth_and_avatar.py (nuovo)
  • api/app/models.py
  • api/app/schemas.py

Lessons learned:

  • (da compilare a step completato)

Step 2: Backend — OAuth Provider + Route

Status: [ ] Da fare

Cosa fare:

  • Creare astrazione provider OAuth riusabile in api/app/auth/oauth_providers.py (nuovo)
    • Classe base con: get_authorization_url(), exchange_code(), get_userinfo()
    • Implementazione concreta GoogleOAuthProvider
  • Aggiungere settings in api/app/config/settings.py: GOOGLE_AUTH_CLIENT_ID, GOOGLE_AUTH_CLIENT_SECRET, OAUTH_REDIRECT_URI
    • Separati da GMAIL_CLIENT_ID/SECRET (scope diversi: openid email profile vs gmail.readonly)
  • Aggiungere route in api/app/api/routes/auth.py:
    • GET /auth/oauth/{provider}/authorize — genera state + PKCE code_challenge, ritorna authorize URL
    • POST /auth/oauth/{provider}/callback — valida state, scambia code, fetch userinfo, crea/linka utente, salva avatar_url da Google, emette JWT
  • Logica utente nel callback:
    • oauth_accounts match? -> login utente esistente
    • Email match + email_verified=true? -> link account a utente esistente (aggiorna avatar se mancante)
    • Nessun match? -> crea nuovo utente (Fernet key, password_hash=None, avatar da Google)
  • Aggiungere dependency authlib in requirements

Sicurezza:

  • PKCE obbligatorio (desktop app = public client)
  • Auto-link email solo se email_verified=true da Google
  • State param per prevenire CSRF

File coinvolti:

  • api/app/auth/__init__.py (nuovo)
  • api/app/auth/oauth_providers.py (nuovo)
  • api/app/api/routes/auth.py
  • api/app/config/settings.py
  • api/requirements.txt (o pyproject.toml)

Lessons learned:

  • (da compilare a step completato)

Status: [ ] Da fare

Cosa fare:

  • Registrare protocollo custom adiuvai:// in adiuvAI/forge.config.ts (packagerConfig.protocols)
  • In adiuvAI/src/main/index.ts:
    • app.setAsDefaultProtocolClient('adiuvai')
    • Windows/Linux: gestire second-instance event, parsare argv per deep link
    • macOS: gestire open-url event
    • Forward code+state all'auth manager
  • In adiuvAI/src/main/auth/auth-manager.ts:
    • Nuovo metodo loginWithOAuth(provider): chiama authorize, apre browser, attende callback, scambia code, salva token
    • Nuovo metodo handleOAuthCallback(code, state): risolve la promise pendente
  • In adiuvAI/src/main/router/index.ts:
    • Aggiungere procedura tRPC auth.loginWithOAuth

File coinvolti:

  • adiuvAI/forge.config.ts
  • adiuvAI/src/main/index.ts
  • adiuvAI/src/main/auth/auth-manager.ts
  • adiuvAI/src/main/router/index.ts

Lessons learned:

  • (da compilare a step completato)

Step 4: Electron — UI Login + Avatar

Status: [ ] Da fare

Cosa fare:

  • In adiuvAI/src/renderer/components/auth/LoginForm.tsx:
    • Email+password resta il form principale (prima opzione, in alto)
    • Divider "oppure" sotto il form
    • Bottone "Sign in with Google" sotto il divider
    • Stato "In attesa del browser..." durante il flow OAuth
    • Su successo: invalidare auth.status
  • Mostrare avatar nel profilo utente:
    • Avatar da avatar_url (immagine circolare) se disponibile
    • Fallback: iniziali del nome o icona default per utenti senza avatar
  • Aggiornare tipo UserProfile in adiuvAI/src/shared/api-types.ts con avatar_url

File coinvolti:

  • adiuvAI/src/renderer/components/auth/LoginForm.tsx
  • adiuvAI/src/shared/api-types.ts
  • Componenti profilo utente (da identificare)

Lessons learned:

  • (da compilare a step completato)

Step 5: Fix Backup Encryption + Test

Status: [ ] Da fare

Cosa fare:

  • Sganciare BackupManager da _cachedPassword:
    • Generare chiave backup random 256-bit al primo login (qualsiasi metodo auth)
    • Salvarla in electron-store via safeStorage (stessa pattern di token.ts)
    • Usare questa chiave al posto di _cachedPassword per AES backup
    • Per utenti esistenti: generare nuova chiave, usarla per backup futuri
  • Test backend (api/tests/test_auth.py):
    • Test authorize URL generation
    • Test callback: creazione utente, linking account, duplicati
    • Test callback con email match -> auto-link
    • Test state mismatch -> 401
  • Test manuale end-to-end:
    • Click "Sign in with Google" -> browser -> consent -> redirect -> autenticato
    • Account linking: stessa email via password e Google -> stesso utente
    • Utente solo-social: nuovo utente senza password
    • Backup con utente social -> chiave device-specific

File coinvolti:

  • adiuvAI/src/main/auth/auth-manager.ts
  • BackupManager (da identificare path esatto)
  • api/tests/test_auth.py

Lessons learned:

  • (da compilare a step completato)