docs: OIDC migration plan — bincio-auth becomes full IdP
This commit is contained in:
+152
@@ -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 |
|
||||||
Reference in New Issue
Block a user