From c341c27ad4ecc19d3b073dee8addd31f6089ad2e Mon Sep 17 00:00:00 2001 From: Davide Scaini Date: Wed, 3 Jun 2026 12:38:35 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20OIDC=20migration=20plan=20=E2=80=94=20b?= =?UTF-8?q?incio-auth=20becomes=20full=20IdP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OIDC_PLAN.md | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 OIDC_PLAN.md diff --git a/OIDC_PLAN.md b/OIDC_PLAN.md new file mode 100644 index 0000000..8d17685 --- /dev/null +++ b/OIDC_PLAN.md @@ -0,0 +1,152 @@ +# bincio-auth → OIDC Identity Provider + +**Goal:** make bincio-auth a proper OpenID Connect (OIDC) Identity Provider so every +bincio service (activity, wiki, Gitea, mobile apps, future tools) delegates auth to it. +One account, one password, self-service email reset, no manual user sync per service. + +**Status:** planning + +--- + +## Architecture + +bincio-auth becomes the IdP. Every other service is a client. + +``` +User → Gitea / activity / wiki / mobile + → "not logged in → go to bincio.org/oauth2/authorize" + → user logs in once at bincio.org + → redirected back with short-lived auth code + → client POSTs code → bincio-auth returns id_token (RS256 JWT) + → client verifies token locally via JWKS (no shared secret needed) +``` + +**Signing:** RS256 asymmetric key pair. +Private key on bincio-auth only. Public key published at `/.well-known/jwks.json`. +Clients verify tokens without any shared secret — truly decoupled. + +--- + +## New endpoints (all additive — nothing existing changes) + +| Endpoint | Purpose | +|---|---| +| `GET /.well-known/openid-configuration` | OIDC discovery — Gitea and clients read this | +| `GET /.well-known/jwks.json` | Public RSA key for token verification | +| `GET /oauth2/authorize` | Login + consent page, issues short-lived auth code | +| `POST /oauth2/token` | Exchanges auth code for `id_token` + `access_token` | +| `GET /oauth2/userinfo` | Returns profile from `access_token` | + +--- + +## New DB tables + +```sql +-- Registered OAuth2 clients (Gitea, bincio-activity, mobile app, ...) +CREATE TABLE oauth2_clients ( + client_id TEXT PRIMARY KEY, + client_secret TEXT, -- NULL for public PKCE clients (mobile) + name TEXT NOT NULL, + redirect_uris TEXT NOT NULL, -- JSON array of allowed redirect URIs + scopes TEXT NOT NULL DEFAULT 'openid profile', + created_at INTEGER NOT NULL +); + +-- Short-lived single-use authorization codes (expire in 5 min) +CREATE TABLE oauth2_codes ( + code TEXT PRIMARY KEY, + client_id TEXT NOT NULL, + handle TEXT NOT NULL, + redirect_uri TEXT NOT NULL, + scope TEXT NOT NULL, + code_challenge TEXT, -- PKCE (mobile clients) + code_challenge_method TEXT, -- 'S256' + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL, + used_at INTEGER +); +``` + +--- + +## Migration phases + +### Phase 0 — Preparation `[ ]` +- [ ] Generate RSA key pair, store private key at `/etc/bincio/oidc_private.pem` +- [ ] Choose and configure SMTP provider (Postmark or Brevo free tier — VPS IPs often blocked by Gmail/Outlook) +- [ ] Register initial OAuth2 clients in DB: Gitea, bincio-activity (client_id + secret) +- [ ] Decide on `bincio.org` as the OIDC issuer URL + +### Phase 1 — OIDC endpoints in bincio-auth `[ ]` +Additive — nothing existing changes. bincio-activity and wiki keep working unchanged. + +- [ ] Add `oauth2_clients` and `oauth2_codes` tables to DB schema +- [ ] `GET /.well-known/openid-configuration` — static JSON describing all endpoints + issuer +- [ ] `GET /.well-known/jwks.json` — RSA public key in JWK format +- [ ] `GET /oauth2/authorize` — shows login form (or reuses existing session), issues auth code +- [ ] `POST /oauth2/token` — validates code, returns `id_token` (RS256 JWT) + `access_token` +- [ ] `GET /oauth2/userinfo` — returns `sub`, `name`, `preferred_username`, custom claims +- [ ] PKCE support (`code_challenge` / `code_verifier`) for public clients +- [ ] Smoke test: complete a full OIDC flow with `curl` / a test script + +### Phase 2 — Gitea `[ ]` +- [ ] Install Gitea on VPS (systemd service, port 3000) +- [ ] nginx server block for `git.bincio.org` with Let's Encrypt cert +- [ ] Configure Gitea: disable self-registration, set admin account +- [ ] Add "Sign in with bincio" — Generic OAuth2 source pointing to bincio-auth OIDC +- [ ] Test: existing bincio users sign in → Gitea auto-provisions their profile +- [ ] Mirror bincio repos (bincio-activity, bincio-auth, bincio-autarchive, bincio-rec, bincio-wiki) + +### Phase 3 — Migrate bincio-activity to RS256 `[ ]` +The only phase with live-service risk — handled with a feature flag. + +- [ ] bincio-activity fetches JWKS from bincio-auth and caches the public key +- [ ] Add RS256 validation path alongside existing HS256 path (feature flag: `--oidc-issuer`) +- [ ] Deploy with both paths active, verify RS256 works for all users +- [ ] Remove HS256 path + `BINCIO_AUTH_JWT_SECRET` env var +- [ ] **Rollback plan:** keep `--jwt-secret` flag functional until RS256 is confirmed stable + +### Phase 4 — Email / SMTP `[ ]` +- [ ] SMTP config in bincio-auth (`--smtp-host`, `--smtp-user`, `--smtp-password`, `--smtp-from`, env vars) +- [ ] Self-service password reset: user requests via email → gets link → resets without admin +- [ ] Keep admin-code endpoint as fallback for users without a registered email +- [ ] Optional: email verification on registration + +### Phase 5 — Mobile app (PKCE) `[ ]` +- [ ] Register mobile app as a public OIDC client (no client_secret, PKCE only) +- [ ] App opens browser to `/oauth2/authorize`, handles redirect back via deep link +- [ ] Exchange code for id_token via `/oauth2/token` (PKCE verifier instead of client_secret) +- [ ] Remove current `/api/auth/token` mobile workaround + +--- + +## What stays the same throughout + +- `bincio.org/login/` — same login page, same UX until Phase 4 +- All existing sessions remain valid during Phases 1–2 +- bincio-activity users notice nothing during Phases 1–2 +- `bincio_session` cookie name and domain unchanged + +--- + +## Risk register + +| Risk | Mitigation | +|---|---| +| Phase 3 breaks bincio-activity login | Feature flag: run HS256 + RS256 in parallel for one deploy cycle | +| RSA key lost / corrupted | Back up `/etc/bincio/oidc_private.pem` offsite; all active sessions would be invalidated but users just log in again | +| SMTP deliverability from VPS IP | Use a dedicated sending service (Postmark/Brevo), not raw VPS SMTP | +| Gitea DB diverges from bincio-auth | Gitea is read-only consumer — bincio-auth is authoritative; Gitea account = auto-provisioned on first OIDC login | + +--- + +## Effort estimate + +| Phase | Estimate | +|---|---| +| 0 — prep | ~1 hour | +| 1 — OIDC endpoints | ~1.5–2 days | +| 2 — Gitea | ~2–3 hours | +| 3 — bincio-activity RS256 | ~2–3 hours | +| 4 — email/SMTP | ~3–4 hours | +| 5 — mobile PKCE | ~1 day |