This commit is contained in:
Priec
2026-06-18 18:26:40 +02:00
parent 7da4109584
commit 42bab82960
25 changed files with 1250 additions and 5 deletions

View File

@@ -0,0 +1,104 @@
# Google OAuth2 sign-in
"Continue with Google" on `/login` and `/register` is wired through
[`loco-oauth2`](https://github.com/yinho999/loco-oauth2). The code is complete
and compiles; this doc is the checklist to make the live flow work. Until the
credentials below are set, the button reaches Google and fails at the consent
screen — the rest of auth (password login, registration, verification) is
unaffected.
## How the flow works (for context)
1. User clicks **Continue with Google**`GET /api/oauth2/google` redirects to
Google's consent screen.
2. Google redirects back to `GET /api/oauth2/google/callback/cookie`.
loco-oauth2 exchanges the code, fetches the profile, upserts the user
(`OAuth2UserTrait::upsert_with_oauth`), stores an `o_auth2_sessions` row, sets
its own private session cookie, and redirects to `protected_url`.
3. `protected_url` = `GET /api/oauth2/protected` (our bridge,
`controllers/oauth2.rs::complete`). It mints **our** `auth_token` JWT cookie
and redirects: admins (email == `ADMIN_EMAIL`) → `/admin/dashboard`,
everyone else → `/`.
From there the user is a normal logged-in user (same JWT cookie as a password
login; the Casbin layer and guards treat them identically).
## 1. Create Google OAuth credentials
1. Go to <https://console.cloud.google.com/> → create/select a project.
2. **APIs & Services → OAuth consent screen**: configure it (External), add the
`.../auth/userinfo.email` and `.../auth/userinfo.profile` scopes, and add
your Google account as a **test user** while the app is in "Testing".
3. **APIs & Services → Credentials → Create Credentials → OAuth client ID**:
- Application type: **Web application**.
- **Authorized redirect URIs** — add exactly (must match the config's
`redirect_url`, no trailing slash):
- dev: `http://localhost:5150/api/oauth2/google/callback/cookie`
- prod: `https://YOUR_DOMAIN/api/oauth2/google/callback/cookie`
4. Copy the generated **Client ID** and **Client secret**.
## 2. Set environment variables (`.env`)
Read by `config/development.yaml``initializers.oauth2` (and the prod
equivalent). dotenvy loads `.env` on boot.
```bash
# Required
OAUTH_CLIENT_ID=xxxxxxxx.apps.googleusercontent.com
OAUTH_CLIENT_SECRET=xxxxxxxx
# Required in PRODUCTION (dev has working defaults)
OAUTH_PRIVATE_KEY="comma,separated,bytes >= 64 long" # key for loco-oauth2's private cookie jar
OAUTH_REDIRECT_URL=https://YOUR_DOMAIN/api/oauth2/google/callback/cookie
OAUTH_PROTECTED_URL=https://YOUR_DOMAIN/api/oauth2/protected
```
Notes:
- **dev** ships defaults for everything except `OAUTH_CLIENT_ID` /
`OAUTH_CLIENT_SECRET`, so locally you only need those two.
- `OAUTH_PRIVATE_KEY` must be ≥ 64 bytes (the dev default is a sample key — do
**not** reuse it in production). Generate a fresh one, e.g.
`python3 -c "import os;print(','.join(str(b) for b in os.urandom(64)))"`.
- `OAUTH_REDIRECT_URL` here and the Authorized redirect URI in the Google
console must be byte-for-byte identical.
## 3. Run / test
```bash
nix develop -c cargo loco start # MUST be inside nix develop (OpenSSL link, see memory)
```
- `auto_migrate: true` (dev) creates the `o_auth2_sessions` table on boot.
- Open `http://localhost:5150/login`**Continue with Google** → consent →
you should land back on `/` logged in (cart/nav reflect the session).
## 4. Production checklist
- [ ] Separate OAuth client (or at least the prod redirect URI) in Google.
- [ ] OAuth consent screen **published** (not just "Testing"), or real users
get blocked.
- [ ] `OAUTH_PRIVATE_KEY` set to a fresh ≥64-byte key (not the dev sample).
- [ ] `OAUTH_REDIRECT_URL` / `OAUTH_PROTECTED_URL` use the real `https://` origin.
- [ ] `server.host` / public origin correct so cookies + redirects resolve.
## Troubleshooting
| Symptom | Cause / fix |
|---|---|
| `redirect_uri_mismatch` at Google | Authorized redirect URI ≠ `OAUTH_REDIRECT_URL`. Make them identical (scheme, host, port, path, no trailing slash). |
| 403 / "access blocked: app not verified" | Add your account as a test user, or publish the consent screen. |
| `openssl-sys ... Could not find directory` at build | You ran `cargo` outside the dev shell. Use `nix develop -c cargo ...`. |
| Callback 500 / "could not create oauth2 store" | `initializers.oauth2` missing/invalid, or `OAUTH_PRIVATE_KEY` < 64 bytes. |
| Logged into Google but not into the app | The bridge (`/api/oauth2/protected`) didn't run check `protected_url` (`OAUTH_PROTECTED_URL`) points at it. |
## Where things live
- Config: `config/development.yaml` / `config/production.yaml`
`initializers.oauth2`
- Client store + session initializers: `src/initializers/oauth2.rs`,
`src/initializers/oauth2_session.rs`
- Routes + bridge handler: `src/controllers/oauth2.rs`
- User upsert (random password per advisory LOC-2025-04): `src/models/users.rs`
(`OAuth2UserTrait`)
- Session table: `src/models/o_auth2_sessions.rs` +
`migration/.../m20260618_000001_o_auth2_sessions.rs`