f023192b741099a0c821ecaa4d54958400b57f8f
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 290c9b078c |
T10.7 - a fake-OIDC-provider test seam, mirroring ldap_fake.py
Only two calls actually touch the network: Authlib's authorize_redirect and
authorize_access_token. server/okta_fake.py stands in for both, dispatched from
okta_auth._build_oauth() before the real Okta config is even considered, and
production-refusing the same way ldap_fake.is_active() does - a non-SQLite
DATABASE_URL means production, full stop, no matter what WP_OKTA_FAKE_DIRECTORY
says. Everything this app itself decides stays real: the ?next= open-redirect
guard, the disabled-account check, JIT provisioning, and which claim carries
identity all run unmodified in app.py's okta_login()/okta_callback().
The fake needed one thing ldap_fake.py never did: something to actually redirect
the browser to and back, since Okta's real flow leaves the site and LDAP's never
did. Two routes stand in for Okta's own sign-in screen - a plain picker listing
whatever WP_OKTA_FAKE_DIRECTORY defines, and a consent step that hands back an
authorization code (or an error) at okta_callback, exactly the shape a real Okta
redirect would carry. Both are registered in app.py only when the fake is active
at import time, so in production they do not exist at all, not merely refuse a
request - confirmed by starting the app with the env var unset and checking
app.routes directly.
tests/browser_check.py's start_server() takes an optional extra_env now (no
existing caller passes a third positional arg, so none of the ~40 files that
import it needed touching) and sets WP_OKTA_FAKE_DIRECTORY unconditionally,
same reasoning the LDAP predecessor used: almost nothing signs in (seed() mints
tokens directly), but the one check that does should not fail mysteriously.
tests/url_state_check.py scenario 2, SKIPPED since T10.4, is un-skipped and now
drives the real round trip: login.html's own button, the fake picker page, the
fake consent redirect, okta_callback(). Carries forward the LDAP predecessor's
own bug fix too - asserting the app actually LEFT login.html, not just that
wp-creation-index.html appears somewhere in the URL (which the ?next= parameter
alone would satisfy).
tests/okta_auth_check.py is new, mirroring ldap_auth_check.py's two-layer shape:
guards that need no server (the production refusal, single-use/replay on the
authorization code), then a real running app for sign-in itself - an existing
admin surviving unchanged, JIT provisioning at the lowest role, a disabled
account refused despite Okta approving it, an unsolicited callback hit refused
without a 500, a tampered state refused, a denied consent refused, an unknown
identity refused BY THE SERVER (not just absent from the picker), a same-site
next= surviving and an off-site one ignored, and OKTA_IDENTITY_CLAIM genuinely
working under a non-default claim name. 22/22.
One thing this could not verify in this environment: url_state_check.py and
browser_check.py both need a headless Edge/Chrome via cdp.py, and this sandbox
has neither installed and no way to install one (no sudo). Confirmed the failure
is the tests' own designed-for exit 2 ("no headless-capable browser found; set
WP_BROWSER"), not a crash, and separately confirmed start_server() itself boots
cleanly with the fake wired in - health check, the picker page rendering with
the seeded identities, login.html all responding correctly - so the only gap is
the DOM-level click-through, not the server-side mechanism url_state_check
exercises (which okta_auth_check.py covers directly via HTTP instead).
wave-10.md's T10.7 bullet records the shape of what got built and the 22/22
result.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|||
| 73da684b99 |
T10.4: remove the local password path entirely
Real deletion (D15's 'full replacement'), not a toggle. Okta is now the only credential this app accepts anywhere. Backend: - server/models.py: drop User.password_hash. - server/alembic/versions/1d60a608bb51_...: matching migration (op.drop_column, same plain-drop precedent as project_role/locked_until/etc.; downgrade re-adds it with server_default=''). - server/auth.py: remove hash_password/verify_password/password_problem/ MIN_PASSWORD_LEN/_COMMON_PASSWORDS, create_reset_token/decode_reset_token/ RESET_MINUTES, the bcrypt import. Roles/tokens/cookies/get_current_user untouched. - server/app.py: remove login(), the whole self-service reset-password block (forgot-password/reset-available/reset-password), and change_password() (POST /api/auth/password). Rework create_user() to drop the password field (with a docstring note: the username must exactly match the eventual Okta identity claim, or a later sign-in provisions a second account instead of matching this one). Remove admin_reset_password() outright - nothing left to reset. Fixes a bug this task's own predecessor left behind: okta_callback()'s JIT provisioning (T10.3) was still setting password_hash="", which would have raised TypeError the moment the column was actually dropped. Admin bootstrap (D16): server/manage_users.py moves from creating accounts (create/create-admin/reset-password, all password-based) to a single 'promote <username> --role <role>' command that changes the role on a row Okta's JIT provisioning already created - the documented path for naming the first admin. list/disable/enable unchanged. Frontend: html/users.js drops the password field and validation from createUser(), removes resetPw() and its button (nothing left to reset). html/users.html drops the #nu-password input, adds a tooltip on username explaining the exact-match-to-Okta requirement. html/auth-guard.js removes the wpChangePassword dialog; html/wp-sidenav.js removes the 'Password' menu item that opened it. Tests: tests/browser_check.py and tests/launcher_check.py stop hashing a password to seed fixture rows (and the --keep-server hint now prints a ready-to-use cookie-setting snippet instead of a dead username/password). tests/pipeline_check.py and tests/token_check.py drop an unused PW import. tests/console_dialogs_check.py: the admin password-reset dialog it drove no longer exists, so that scenario is removed - the prompt-with-validate() UI pattern it exercised is still covered via creator_dialogs_check.py's wp-creation-app.js call sites, noted in this file's docstring so the coverage move isn't silent. tests/url_state_check.py: the "next= survives a real sign-in via login" scenario is explicitly marked SKIPPED (not deleted, not faked) - that promise is specific to the login FORM this task removed and can't be honestly re-proven until T10.5 rebuilds it as an Okta redirect; a minted-token cookie now stands in as setup only, so scenarios 3-6 in that file still get a signed-in page to run against. server/smoketest.py and server/seed_demo.py: switched from POST /api/auth/login to minting a session the same way okta_callback() does (auth.create_token(), seeded into the cookie jar) rather than waiting on T10.7. This is a real operational change, documented in both files' own AUTHENTICATION sections: they now need to run where AUTH_SECRET_KEY and the database match the target server's (inside the api container, or local dev) - they can no longer sign in to an arbitrary remote URL from an unrelated workstation, because Okta requires a real browser and these are stdlib scripts. The account must already exist; neither script creates or promotes one. server/requirements.txt: bcrypt dropped, nothing imports it anymore. Verified: full Alembic chain (baseline through this migration) upgrades and downgrades cleanly against a throwaway SQLite DB. okta_callback() JIT provisioning re-tested against the post-migration schema (would have thrown before the password_hash="" fix above). create_user() verified via a live HTTP call with no password field. manage_users.py promote verified end to end (seed a JIT-shaped row at project_user, promote to admin, list). smoketest.py and seed_demo.py both run to completion against a live uvicorn instance using the new minted-session path - 25/25 checks, including logout actually invalidating the session (proving the cookie-jar seeding didn't just fake the sign-in, it preserved the real expiry mechanics). wave-10.md T10.4 / D15 / D16 |
|||
| 23ee0b052f |
T9.9 - C4 + the backlog sweep: nine entries closed, each re-measured first
The colour half (C4, approved Aug 18 - "change them"):
- BL-004: the help centre's own 52-colour palette collapsed onto theme tokens
- BL-005: the JS-built dialogs (auth-guard, wp-format) and project-data's
badges read tokens; the creator's categorical badge palette moved to
theme-light as --wp-chart-1..10, read by computed style at boot; the print
popup - a document with no stylesheet - inlines live token VALUES
- BL-008: the second brand blue (#2563d6) is deleted; .sop-inherited tints
with THE blue at the same 7% alpha
- BL-009: the ninth amber (--wp-status-warning-text-alt) is deleted
- theme-light gained the two missing feedback tokens the consoles carried as
literals (--wp-status-success-text / -error-text)
- NEW tests/color_check.py 4/4: zero hex literals outside theme-light.css,
comments stripped (the BL-017 lesson), with the exceptions named in full
(meta theme-color cannot resolve a var; rgba alphas are opacity recipes)
The correctness half, each re-measured before touching, as the task ordered:
- BL-011 STILL REPRODUCED: the sync badge mounted on the first async sync
event; its holder now mounts at DOMContentLoaded, so the three overlays land
in script order deterministically
- BL-012 fixed and MEASURED: baseline_shots freezes Date and Math.random per
document; two consecutive admin captures came back byte-identical
- BL-016 fixed: a step-less wizard URL is step 1; stepper_check's deliberately
wrong pin flipped with the fix, exactly as the entry planned
- BL-018 fixed both halves: the false-complete write now requires the
{sop,state} production shape, and browser_check.seed writes that shape -
which un-detoured four probes' creators from the SOP gate. stepper_check
re-pointed at projB (no SOP) because its premise is a wizard someone is
STARTING, and projA now legitimately restores a finished one.
- BL-019 fixed: a stored cost code that left COST_CODES is kept as an option
(the gov_wosize pattern), so opening a package no longer blanks its record
- hold_check's AST sweep refined in passing detection: it flagged T8.3's
notification-row .status as a release transition; it now reads wp.status only
Every wave-9-pointing backlog entry is closed with its measurement recorded.
Verification (each probe run alone): color_check 4/4, stepper_check 71/71,
validation_check 77/77, url_state_check 23/23, autosave_check 34/34,
a11y_check 22/22, launcher_check 58/58, aggregates_check 16/16,
kitting_check 26/26, hold_check 50/50, mobile_check 24/24, frame_check 38/38,
sections_check 95/95, form_structure_check 50/51 (BL-022's question).
Items: C4, BL-004, BL-005, BL-008, BL-009, BL-011, BL-012, BL-016, BL-018, BL-019
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|||
| 9459e76a6c |
Add a front-end browser check, so the pages are testable and not just readable
server/smoketest.py proves the API works; nothing proved the PAGES work. That gap
is why users.js, wp-sidenav.js and the extracted console.css shipped unexecuted and
had to be written up as a known issue instead of verified. This closes the gap with
a tool rather than a one-off, so the next front-end change is cheap to check.
tests/cdp.py a minimal DevTools Protocol client — hand-rolled stdlib
WebSocket (handshake, masked frames), browser discovery for
Edge/Chrome across platforms, and process teardown.
tests/browser_check.py the fixture and 71 assertions.
Stdlib only, matching smoketest.py's rule: these have to run on a plain Python
install on whatever machine is to hand. No pip, no Selenium, no node.
Self-contained — it builds a throwaway database, seeds a fixture, starts its own
uvicorn on a free port, drives the browser, and tears everything down. The real
database is never touched. Sessions come from minting a token with the app's own
auth.create_token() rather than scripting the login form.
What it asserts, beyond "no JavaScript errors on boot" (the thing that actually
went unverified): the three role-dependent renderings of the directory, one-line
rows and no sideways scroll, the roles each caller may grant, the project-access
dialog opening and closing, the drawer's open/Escape/scrim/focus/aria behaviour and
its role gating, ?project= carried only onto project-scoped links, and — the reason
this matters most — that admin.html still has its tokens, cards, headings and dense
sticky tables after console.css was lifted out of its inline <style>.
Three things the build had to get right, each learned the hard way:
- Teardown kills the browser's whole process tree AND sweeps anything still
holding the unique temp profile, matched on that path so a browser window the
user has open is never touched. proc.kill() alone left 98 strays.
- Launching retries with a fresh profile and port: a browser can hand off to
another instance and exit rc=0 without ever binding the debugging port.
- Cleanup waits for the server to exit and disposes the harness's own SQLAlchemy
engine before removing the temp directory, or the open SQLite file blocks the
delete and ignore_errors hides it.
The fixture includes an account on a project the super user cannot see, without
which the admin and the super user would see the same number of rows and the
scoping assertion would prove nothing.
Documented in DEPLOYMENT.md next to the smoke test. 71/71 across repeated runs,
leaving no stray processes or temp directories.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|