# 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`
