From 31c548318b2c4428330a0d210397603f7f95d5f2 Mon Sep 17 00:00:00 2001 From: Matt Mabrey Date: Wed, 2 Sep 2026 17:20:01 -0700 Subject: [PATCH] D15: retire D13/D14 (LDAPS, never deployed), move straight to Okta OIDC --- docs/waves/decisions-2026-09-02.md | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs/waves/decisions-2026-09-02.md diff --git a/docs/waves/decisions-2026-09-02.md b/docs/waves/decisions-2026-09-02.md new file mode 100644 index 0000000..a967cca --- /dev/null +++ b/docs/waves/decisions-2026-09-02.md @@ -0,0 +1,75 @@ +# Decisions — September 2, 2026 + +One item, and it retires two decided-and-built items rather than amending them. + +--- + +## D15 — Authentication moves to Okta OIDC; D13 and D14 are retired before deployment + +- **Amends:** retires `D13` (LDAPS simple bind against `prime.local`) and `D14` (the CLI + authenticates against the domain). Both were decided and reaffirmed August 21 2026, + built across nine tasks (`T10.1`–`T10.9`), and verified against the live domain. Neither + reached production. Approved by Nick Siegfried. +- **Surface:** `server/auth.py`, `server/app.py` (`login()`), `html/login.html`, + `html/login.js`, `html/auth-guard.js`. `server/ldap_auth.py` does not carry forward — + there is no LDAPS bind in the new design, not even as a fallback. +- **Wave:** 10. The label is reused fresh: the LDAPS work that previously answered to + "wave 10" was built on `feat/ldaps-directory-auth`, which is deleted rather than merged, + and never appeared in `IMPLEMENTATION.md`'s wave table. It carries no claim on the + number. + +### What D13/D14 were, for the record + +The branch carrying them is deleted, not merged, so their decision record +(`docs/waves/decisions-2026-08-21.md`) no longer exists on any branch. Preserved here so +the reasoning isn't lost along with it: + +D13 chose a direct LDAPS simple bind to `ldaps://prime.local:636` as the sign-in +mechanism: no password stored, a successful bind was the authentication, accounts were +provisioned just-in-time from the directory, and roles stayed local. D14 moved the CLI +onto the same bind, removing `create-admin`/`create`. Both were built, tested +(1284/1288 checks, Aug 24), and screenshotted at 390px and 1440px. Neither ever deployed — +`wp.controls.dev` still runs the pre-D13 local-password login as of this decision. + +### The decision + +Skip LDAPS entirely. Authentication becomes an Okta OIDC authorization-code flow, +replacing local passwords directly — the same full replacement D13 intended, just via +Okta instead of a domain bind. No LDAPS bind exists in this design at any point. + +Four things carry forward from D13 unchanged, because they were never LDAPS-specific to +begin with: + +1. **No password is stored.** The app never sees a credential of any kind; Okta owns + authentication entirely. +2. **Accounts are provisioned just-in-time.** A first successful Okta sign-in with no + matching local `users` row creates one, at the default role. The matching logic that + was going to key off a directory search instead keys off an OIDC identity claim. +3. **Roles stay local.** Okta, and AD behind it, supplies identity only. This app decides + what an identity may do. Restated because it is the one rule the whole access-control + design depends on — see `BL-029` and the governance discussion that followed it. +4. **Existing accounts keep their roles** on first Okta login, exactly as D13's criterion + 4 read for LDAPS. + +### Why this, and not LDAPS first and Okta second + +`BL-029` (recorded on this branch as `BL-027` before the renumbering forced by main's +independent use of that number) already laid out why OIDC beats the LDAPS bind on three +counts: this app never sees a password, MFA comes from Okta rather than needing to be +built, and the domain-lockout hazard that forced `AUTH_MAX_ATTEMPTS` down to 2 disappears, +because failed attempts land on Okta rather than on a bind this app makes. D13 was decided +before it was known the company already runs an Okta tenant. Once that was confirmed +(security's scoping reply, September 2026), shipping LDAPS first and replacing it with +Okta days later would mean building and deploying the weaker mechanism on purpose. Going +straight to Okta avoids that. + +### What still needs answering before this is buildable + +Open from the security scoping thread, not yet closed: + +- Which OIDC claim carries the AD `sAMAccountName` equivalent (`preferred_username`, + `upn`, or a custom claim) — asked of security, answer pending. +- The exact redirect/callback URI once the hostname situation is reconfirmed + (`https://wp.controls.dev/api/auth/okta/callback` proposed). +- The `Business Technology Group` pilot in Okta, requested for initial testing, with + normal Okta session/MFA behavior rather than a stricter per-app rule.