153 lines
6.4 KiB
Markdown
153 lines
6.4 KiB
Markdown
# 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 |
|