From 044862acbace888cec97ec34e120cdea5b04cba5 Mon Sep 17 00:00:00 2001 From: Matt Mabrey Date: Wed, 2 Sep 2026 17:38:35 -0700 Subject: [PATCH] Wave 10: task breakdown for the Okta OIDC build --- docs/waves/wave-10.md | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/waves/wave-10.md diff --git a/docs/waves/wave-10.md b/docs/waves/wave-10.md new file mode 100644 index 0000000..af12972 --- /dev/null +++ b/docs/waves/wave-10.md @@ -0,0 +1,55 @@ +# Wave 10 — Okta OIDC authentication + +Fresh wave 10. The label was previously used by the LDAPS work under `D13`/`D14`, built on +`feat/ldaps-directory-auth`; that branch was deleted rather than merged and never appeared +in `IMPLEMENTATION.md`'s wave table, so it carries no claim on the number. See +`docs/waves/decisions-2026-09-02.md` (`D15`) for why LDAPS was retired before deployment +and Okta chosen instead. + +Depends only on `main` as it stands after `D15`. Not sequenced behind any other wave. + +## Tasks + +- **T10.1 — Add the Okta OIDC client.** Authlib as a dependency. Config via env vars + (`OKTA_ISSUER`, `OKTA_CLIENT_ID`, `OKTA_CLIENT_SECRET`, `OKTA_REDIRECT_URI`), same + pattern `AUTH_SECRET_KEY` already uses in `server/auth.py`. + +- **T10.2 — Login-redirect and callback routes.** A route that sends the browser to + Okta's authorize endpoint, and a callback route that exchanges the code for tokens and + validates the ID token. Access gating is Okta's job, not this app's: only accounts + assigned to the app integration in Okta can reach it at all, so there is no app-side + required-group or claim check layered on top. This is a deliberate difference from D13, + which had to gate on a required AD group itself because an LDAPS bind alone could not + distinguish an assigned user from any other domain account. + +- **T10.3 — Identity matching and JIT provisioning.** Reuses D13's shape + (`_provision_from_directory`-style matching) keyed off an OIDC claim instead of an LDAP + search result. **Open dependency:** which claim carries the AD `sAMAccountName` + equivalent (`preferred_username`, `upn`, or a custom claim) is asked of security and not + yet answered. Build with a configurable claim name and a documented default, not a + hardcoded one, so the answer can drop in without a code change. + +- **T10.4 — Remove the local password path entirely.** Drop `password_hash`, remove the + bcrypt-based `login()`, remove the username/password form. Real deletion, matching + `D15`'s "full replacement," not a toggle or a fallback. + +- **T10.5 — Frontend: login becomes a redirect, not a form.** `login.html`/`login.js` + change to a "Sign in with Okta" flow. Sign-out lands back on the app's own login page. + +- **T10.6 — Deployment docs and env var reference.** `DEPLOYMENT.md`, + `server/.env.example`, `server/README.md` describe the Okta config in place of the LDAP + config they never ended up describing (D13 never shipped, so these still describe the + original local-password system today). + +- **T10.7 — Test coverage without a live Okta dependency.** A fake-OIDC-provider test + seam, mirroring `ldap_fake.py`, so the suite runs with no live Okta tenant reachable. + +- **T10.8 — Verification.** 390px and 1440px, full suite, done-when checks per task, + matching the rigor D13 was held to. + +## Still open + +- The OIDC claim mapping (`T10.3`). +- Final confirmation of the redirect/callback URI (`https://wp.controls.dev/api/auth/okta/callback` + proposed, pending security). +- The `Business Technology Group` pilot assignment in Okta.