Files
bincio-auth/OIDC_PLAN.md
T

153 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 12
- bincio-activity users notice nothing during Phases 12
- `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.52 days |
| 2 — Gitea | ~23 hours |
| 3 — bincio-activity RS256 | ~23 hours |
| 4 — email/SMTP | ~34 hours |
| 5 — mobile PKCE | ~1 day |