From 74314eeb68171bddd6ed722efaa9fc4164428990 Mon Sep 17 00:00:00 2001 From: Eugene Blikh Date: Sun, 26 Apr 2026 18:05:24 +0300 Subject: [PATCH] docs(lethe-web-ui-login): record verify checks --- docs/tasks/lethe-web-ui-login.md | 40 +++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/tasks/lethe-web-ui-login.md b/docs/tasks/lethe-web-ui-login.md index 0bff3f432880d2aeeefa0afca91b78104c0e9ea6..9830daddabf483dcb8a98b9cdd6294421e1d59be 100644 --- a/docs/tasks/lethe-web-ui-login.md +++ b/docs/tasks/lethe-web-ui-login.md @@ -323,7 +323,45 @@ Restating Design's per-area compat with concrete phase steps: Greenfield modulo the additions enumerated above. No schema migrations. No `internal/shared/wire/` changes. ## Verify - + +**Result:** passed + +Positive: +- CK1 — `go test ./internal/testutil/oidcstub/... -race -count=1` → ok (codestore + handler tests, ~22 cases) +- CK2 — `go test ./internal/server/web/... -count=1` → ok (3 embed-injection tests) +- CK3 — `go test ./internal/... -count=1` → ok (all packages) +- CK4 — `go test ./cmd/lethe/... -count=1` → ok (RK4: e2e steward graph still works) +- CK5 — `go build ./...` → clean +- CK6 — `cd web && npm test -- --run` → 84 passed across 6 files +- CK7 — `cd web && npm run build` → 382 modules, 499.94 KB JS / 152.73 KB gzip; `dist/index.html` matches tracked artifact + +Negative: +- CK8 (IV1 bad verifier) — `TestToken_BadVerifier_Returns400` → 400 invalid_grant +- CK9 (IV2 code reuse) — `TestToken_CodeReuse_Returns400` → first 200, second 400 +- CK10 (IV3 expired code) — `TestCodeStore_Consume_Expired` → expired entry returns false. **Note**: `TestToken_ExpiredCode_Returns400` is `t.Skip`'d because handleAuthorize uses `5*time.Minute` against `time.Now` (not injectable); IV3 is fully covered at the codestore unit-test layer with injected clock. Recorded as a deviation. +- CK11 — `TestAuthorize_MissingRequiredParam_Returns400` → 4 sub-cases (response_type, client_id, redirect_uri, code_challenge) → 400 invalid_request + +Invariants / assumptions: +- CK12 (IV1) — `oidcstub.go:340-346` rejects `code_challenge_method != S256` with 400 invalid_request; `oidcstub.go:405-410` verifies `base64url(SHA-256(verifier)) == code_challenge` before mint +- CK13 (IV2) — `codestore.go:71` `delete(s.entries, code)` runs unconditionally on `Consume` (single-use enforced) +- CK14 (IV3) — `oidcstub.go:350` issues codes with `5*time.Minute` TTL; `codestore.go:62-72` rejects expired +- CK15 (IV4) — `grep -n "localStorage.setItem" web/src/lib/auth.ts` → empty; tokenStore writes only to closure state +- CK16 (IV5) — `web/src/api/client.ts:25-28` returns `{Authorization: \`Bearer ${token}\`}` only when `token !== null`; null returns empty object spread (no header) +- CK17 (IV6) — `AuthGate.tsx:32-58` `auth_error` branch renders error card with manual "Try again" button (`onClick={() => signIn(...)}`); no `useEffect` auto-retry from this branch +- CK18 (IV7) — `AuthGate.tsx:22-26` useEffect auto-redirect fires only when `status === 'unauthenticated' && hasBeenAuthenticated === true`; cold render (`hasBeenAuthenticated === false`) falls through to manual button at line 65+ +- CK19 (IV8) — `oidcstub.go:125` `mux.HandleFunc("/dev/token", s.handleDevToken)` unchanged; `TestDevToken_StillWorks` regression-tests it +- CK20 (AS1) — `web/src/test-setup.ts:3-11` polyfills `globalThis.crypto` from Node `webcrypto` in vitest; production browsers carry `crypto.subtle` natively per modern-browser baseline +- CK21 (AS2) — `internal/server/server.go` keeps `validateLoopbackBind`; dev-stub binds to `127.0.0.1:`; redirect URIs of the form `http://127.0.0.1:/auth/callback` are accepted by the stub (verified via `TestAuthorize_RedirectsWithCodeAndState`) +- CK22 (AS3) — unverifiable at this layer; deferred to UK1 (real-OP smoke is the user's call) + +Interfaces: +- CK23 (IF1) — `embed.go:25-28` `Config{Issuer\`json:"issuer"\`, ClientID\`json:"client_id"\`}` produces `window.__LETHE_CONFIG__={"issuer":"…","client_id":"…"}`; `web/src/lib/config.ts:36-37` consumes with the same key names; `TestHandler_InjectsConfigIntoIndex` asserts the JSON shape +- CK24 (IF2) — `tokenStore` exported at `web/src/lib/auth.ts:115`, consumed at `web/src/api/client.ts:26`, `web/src/lib/authContext.tsx:94,126,189`, `web/src/routes/auth.callback.tsx:196` — five call sites total +- CK25 (IF3) — `useAuth()` exported at `web/src/lib/authContext.tsx:64`, consumed at `web/src/shell/AuthGate.tsx:17`, `web/src/routes/login.tsx:13`, `web/src/routes/auth.callback.tsx:42` — three call sites + +Notes: + +- **Browser smoke deferred** — the new `/login` and `/auth/callback` routes plus the dev-stub `/authorize`+`/token` round-trip were not exercised end-to-end in a real browser. tsc + vitest + Go tests are clean and the dev stub is now RFC-compliant for auth-code+PKCE per the test suite, but a full Chrome walk requires standing up the lethe server with OIDC enabled, the dev stub, AND removing the `Remote-User` injection from `web/vite.config.ts` (RK2: otherwise dev fetches succeed via forward-auth and AuthGate never triggers). Out of reach in autonomous mode without user setup. Recommended walk: (a) start the lethe server with `auth.oidc.enabled=true` and `auth.oidc.dev_stub.enabled=true`, (b) comment out the `Remote-User` injection in `web/vite.config.ts`, (c) `npm run dev`, (d) hit `/`, see the AuthGate "Sign in with OIDC" card, click it, watch the round-trip, land back authenticated. ## Conclusion