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 suprovider, provider_user_id) - Migration Alembic: rendere
users.password_hashnullable - Migration Alembic: aggiungere colonna
users.avatar_url(VARCHAR, nullable) - Model
OAuthAccountinapi/app/models.pycon relationship aUser - Campo
avatar_url: str | Nonesul modelUser - Aggiornare
UserProfileinapi/app/schemas.pyconavatar_url
File coinvolti:
api/alembic/versions/XXX_add_oauth_and_avatar.py(nuovo)api/app/models.pyapi/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
- Classe base con:
- 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 profilevsgmail.readonly)
- Separati da
- Aggiungere route in
api/app/api/routes/auth.py:GET /auth/oauth/{provider}/authorize— genera state + PKCE code_challenge, ritorna authorize URLPOST /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_accountsmatch? -> 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
authlibin requirements
Sicurezza:
- PKCE obbligatorio (desktop app = public client)
- Auto-link email solo se
email_verified=trueda 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.pyapi/app/config/settings.pyapi/requirements.txt(o pyproject.toml)
Lessons learned:
- (da compilare a step completato)
Step 3: Electron — Deep Link + Auth Manager
Status: [ ] Da fare
Cosa fare:
- Registrare protocollo custom
adiuvai://inadiuvAI/forge.config.ts(packagerConfig.protocols) - In
adiuvAI/src/main/index.ts:app.setAsDefaultProtocolClient('adiuvai')- Windows/Linux: gestire
second-instanceevent, parsare argv per deep link - macOS: gestire
open-urlevent - 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
- Nuovo metodo
- In
adiuvAI/src/main/router/index.ts:- Aggiungere procedura tRPC
auth.loginWithOAuth
- Aggiungere procedura tRPC
File coinvolti:
adiuvAI/forge.config.tsadiuvAI/src/main/index.tsadiuvAI/src/main/auth/auth-manager.tsadiuvAI/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
- Avatar da
- Aggiornare tipo
UserProfileinadiuvAI/src/shared/api-types.tsconavatar_url
File coinvolti:
adiuvAI/src/renderer/components/auth/LoginForm.tsxadiuvAI/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
_cachedPasswordper 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)