8.3 KiB
phase, created, updated, status, source, gaps, findings_routed_to_phase_17
| phase | created | updated | status | source | gaps | findings_routed_to_phase_17 | ||||
|---|---|---|---|---|---|---|---|---|---|---|
| 19-local-auth-no-oidc-mode | 2026-06-17T18:25:00Z | 2026-06-17T21:20:00Z | complete | verification + plan-checkpoints |
|
Live UAT Session (resumed 2026-06-17, post code-review-fix)
Stack configured for local-auth / no-OIDC mode (Phase 19's canonical deployment):
- API rebuilt from the phase-19 branch (all 18 code-review fixes live; verified
initialPassword+ 403 present in runningdist). DEV_AUTH_BYPASS=falseandOIDC_ISSUER=""via throwawaydocker-compose.uat.ymloverride (tracked files untouched; restore the normal bypass stack after UAT).- Seeded local admin: username
uatadmin/ passwordUATtest1234!(user id 2, is_admin=1). - PWA on host Vite at http://localhost:5173.
Automated API smoke (pre-checks):
- ✅
POST /api/auth/local/login(uatadmin) → 200 +local-sessioncookie. - ✅ Wrong password → 401
{"error":"Invalid credentials"}(generic, no field blame). - ✅ Authenticated
GET /api/me→{id:2, isAdmin:true, hasLocalCredential:true}. - ⚠️ Unauth
GET /api/me→ 500Invalid session(not 401) in no-OIDC mode: the OIDC guard is mounted whenever bypass is off and errors trying to redirect with a blank issuer. Cosmetic — PWA gates onmeQuery.isError && localEnabled(App.tsx:213) so it still redirects to/login. Candidate follow-up: short-circuit the OIDC guard to a clean 401 when no issuer is configured.
Note on Item 1 OIDC button: the "OIDC button appears when oidcEnabled" sub-check can't be exercised on this box (no reachable Authelia → blanked). Covered at unit/e2e level. This session verifies the no-OIDC local-auth surface, which is the phase's primary deliverable.
Phase 19: Local Auth (No-OIDC Mode) — User Acceptance Tests
All automated verification passed (API 446/446, PWA 266/266, e2e desktop 42 passed
/ 3 skipped, typecheck clean; VERIFICATION.md status: passed, 21/21 must-haves).
The single blocker found during verification (admin reset-password URL mismatch) was
fixed and confirmed live (commit 53da4be).
The items below are the remaining human / live-stack checks that cannot be driven
from the dev DEV_AUTH_BYPASS harness or a headless box. They do not block automated
goal achievement but should be confirmed before shipping.
UAT Items
1. Login page visual + flow (real, non-bypass stack)
- Test: Run the stack with OIDC/Authelia configured and
DEV_AUTH_BYPASSoff. Visit the app unauthenticated → confirm redirect to/login. Verify the brand slot ("FS" mark, "FamilySync", "Family calendar & lists"), the form (username auto-focus, password show/hide), wrong-creds single error ("Incorrect username or password."), correct-creds navigation into the app, and the OIDC button only whenoidcEnabled. - Expected: All surfaces per 19-UI-SPEC; no "Authelia" text anywhere; error copy never blames a specific field.
- Why human: The unauth login-gate redirect is unreachable under the bypass-only
harness (covered at unit level in
App.test.tsx); the full visual flow needs a real browser against a non-bypass deployment. (Desktop/Chromium portions are already e2e- covered vialogin.spec.ts.) - result: pass (2026-06-17, live no-OIDC stack, operator-confirmed — all steps:
redirect to /login, brand slot, username autofocus, password show/hide, generic
wrong-creds error, successful login into the app). Setup-gate precedence confirmed:
setupComplete===trueso /login is reachable (App.tsx checks/setupredirect first).
2. Admin Reset-password sheet (live, end-to-end)
- Test: As an admin, open Admin → Local Accounts → Reset password for a member; submit a new password; confirm the member can then log in with it.
- Expected:
POST /api/admin/members/:id/passwordreturns 200; new password works. - Why human: Needs a live stack with an admin session and a real local member. (URL fix already verified: route reachable, 404 eliminated.)
- result: pass (functional) — with UX gap. Operator created
testmember+ reset its password via the admin UI. Verified at DB/login level: member exists (user 3, non-admin); login with the reset pw (MemberPass456!) → 200; login with the original (MemberPass123!) → 401. So CR-02 create-member + reset both work and persist. BUT neither action showed a success confirmation (see Finding F-01).
3. Settings Change-password sheet (live, local user)
- Test: As a local user, Settings → Account → Change password; verify wrong current password shows "Current password is incorrect.", correct current updates, and the new password works on next login.
- Expected: Current-password verification enforced; update succeeds; re-login works.
- Why human: Needs a live stack with a local-user session.
- result: pass (2026-06-17, verified functionally via API on the live no-OIDC stack as
testmember). CR-03 confirmed: wrong current password → 403 (not 401) and the session stays valid (/me→200, no force-logout); correct current → 200; new password logs in (200), old password rejected (401).
4. Rate-limit / lockout test flakiness (harden)
- Test: Run
apps/api/tests/routes/localAuth.test.tsTest 5 (10 failures → 423) ~10 times; characterize the intermittent failure the orchestrator observed (1 failure across 3 runs, then stable). - Expected: Stable pass; if timing-dependent, harden the in-memory rate-limit test (e.g. fake timers / deterministic clock).
- Why human: Timing-dependent in-memory test; needs repeated runs to characterize.
- result: resolved-by-fix. The code-review fix (CR-04/WR-06/IN-03) rewrote the limiter and
made the lockout test deterministic — it back-dates
lockedAtinstead of using wall-clock timers (localAuth.test.ts:280), structurally removing the timing flakiness. The fixer ran the full API suite 452/452 (incl. Test 5/5b). Couldn't be re-run in this session's shell (no test-DB root creds —ER_ACCESS_DENIED); confirm via CI orset -a; . ./.env; set +a; DB_HOST=127.0.0.1 pnpm --filter @familysync/api test.
5. CI harness green + D-15 image boundary (push, outward-facing)
- Test: Push the branch and open the PR so Gitea CI runs. Confirm the
harnessjob (iphone + pixel + desktop, incl.login.spec.ts) is green, theapijob is green, and the published-image hygiene checks (noapps/api/scripts/orapps/pwa/e2e/in the prod image) pass. - Expected: CI all green; no dev artifact in the shipped image (D-14/D-15).
- Why human: Pushing to the remote / triggering CI is an outward-facing action the operator owns. (Plan 19-05's blocking checkpoint.)
- result: deferred to
/gsd-ship(operator decision 2026-06-17). The push/PR/CI run + image-hygiene gate is owned by the ship workflow, not this UAT session.
Live Session Findings (2026-06-17)
Surfaced by the operator during Test 2. Operator decision (2026-06-17): route ALL four UI findings — including the logout button — to Phase 17 (UI Optimization & Polish), which has not yet kicked off. None block Phase 19, whose auth machinery is functionally complete and verified.
All four added to 17-CONTEXT.md (Phase-17 branch):
- F-02 — No logout button in the UI (functional-UI). Logout is fully plumbed — endpoint
POST/GET /api/auth/local/logoutreturns 200 and clears the cookie (BL-02 verified live), andfetchLocalLogout()exists inapps/pwa/src/api/client.ts:127— but no component calls it (grep ofapps/pwa/srcfinds zero logout buttons/handlers). Phase 17 wires a logout control to the existing client function (no backend work). Per operator: a UI concern, not a Phase-19 blocker. - F-01 — Admin create/reset give no success feedback. Both succeed (verified at DB/login level) but show no success toast/confirmation. Add success feedback to the admin local-account flows.
- F-03 — Dialogs/popups render at bottom-center instead of properly centered (cosmetic). Fits Phase 17's fixed-chrome/sheet-positioning sweep.
- F-04 — Admin UI navigation is clunky and needs a rework. UX-polish item for Phase 17.
Status: Phase 19 UAT complete (functional) — Tests 1–3 pass (live), Test 4 resolved-by-fix,
Test 5 deferred to /gsd-ship. No Phase-19 blockers. F-01–F-04 carried to Phase 17.