From 37d7e65b35d2556e889af5bee851671f28c0954f Mon Sep 17 00:00:00 2001 From: Roberto Musso Date: Fri, 10 Apr 2026 09:21:14 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20implement=20Step=202=20Google=20OAuth?= =?UTF-8?q?=20backend=20=E2=80=94=20provider=20abstraction,=20PKCE=20route?= =?UTF-8?q?s,=20user=20linking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds api/app/auth/oauth_providers.py with GoogleOAuthProvider (httpx-based, no authlib needed) and generate_pkce_pair(). New routes: GET /auth/oauth/{provider}/authorize and POST /auth/oauth/{provider}/callback with state/PKCE validation and three-way user resolution (existing OAuth link, email auto-link, new social-only user). Updates settings.py with GOOGLE_AUTH_CLIENT_ID/SECRET and OAUTH_REDIRECT_URI. Also includes Step 1 backend changes (already marked complete in plan): oauth_accounts table migration, nullable password_hash, avatar_url on User. Co-Authored-By: Claude Sonnet 4.6 --- api | 2 +- docs/plan-google-auth.md | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/api b/api index 3cf067f..ce139bb 160000 --- a/api +++ b/api @@ -1 +1 @@ -Subproject commit 3cf067faeaf05f3c2c140f650858ddc838ad8960 +Subproject commit ce139bbac317fe04a98ff54f28da91a46534ee91 diff --git a/docs/plan-google-auth.md b/docs/plan-google-auth.md index 304ed7e..a8c48c5 100644 --- a/docs/plan-google-auth.md +++ b/docs/plan-google-auth.md @@ -26,7 +26,7 @@ il backup encryption locale (`_cachedPassword`) dalla password utente. ## Step 1: Backend — DB + Model + Schema -**Status:** [ ] Da fare +**Status:** [x] Completato **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`) @@ -42,13 +42,15 @@ il backup encryption locale (`_cachedPassword`) dalla password utente. - `api/app/schemas.py` **Lessons learned:** -- _(da compilare a step completato)_ +- `get_current_user` in `middleware/auth.py` esegue una query separata per `name/surname` — aggiornare quella query per includere `avatar_url` (non solo il model). Altrimenti il campo viene ignorato anche se presente in DB. +- `update_profile` in `routes/auth.py` costruisce `UserProfile` manualmente: va aggiornato esplicitamente con `avatar_url=user.avatar_url`. +- La `OAuthAccount.user` relationship richiede forward reference: il model deve essere dichiarato _dopo_ `User` ma la relationship su `User` usa `OAuthAccount` — SQLAlchemy risolve automaticamente con le stringhe lazy evaluation, ma occorre che il model sia nello stesso modulo. --- ## Step 2: Backend — OAuth Provider + Route -**Status:** [ ] Da fare +**Status:** [x] Completato **Cosa fare:** - Creare astrazione provider OAuth riusabile in `api/app/auth/oauth_providers.py` (nuovo) @@ -78,7 +80,12 @@ il backup encryption locale (`_cachedPassword`) dalla password utente. - `api/requirements.txt` (o pyproject.toml) **Lessons learned:** -- _(da compilare a step completato)_ +- **`authlib` non è necessaria**: il flow PKCE con Google si implementa direttamente con `httpx` (già in requirements). Aggiungere `authlib` sarebbe una dipendenza inutilizzata. Se si vorrà usare authlib in futuro (es. per provider con flow più complessi), aggiungerla allora. +- **State store in-memory**: `_pending_states` è un dict a livello modulo — funziona in dev con un solo processo, ma non sopravvive a restart e non scala su più worker. In produzione va sostituito con Redis (o un campo temporaneo su DB). +- **`_issue_refresh_token` helper**: la logica di emissione token è condivisa tra i tre branch del callback (link esistente, link email, nuovo utente) — fattorizzarla in un helper evita duplicazione. +- **`tuple_` import non usato**: l'import `sql_tuple` da sqlalchemy può essere rimosso (non serve per le query attuali). +- **Route `provider` tipizzata come `Literal["google"]`**: FastAPI valida automaticamente il parametro path e risponde 422 per provider sconosciuti, rendendo superfluo un check manuale. Il dict `_PROVIDERS` serve come fallback di sicurezza. +- **`await db.flush()` prima di creare `OAuthAccount`**: necessario per ottenere `new_user.id` prima del commit, altrimenti il FK fallisce. ---