194 lines
16 KiB
Markdown
194 lines
16 KiB
Markdown
---
|
|
phase: 07-user-plugin-and-authentication
|
|
plan: 06
|
|
type: execute
|
|
wave: 5
|
|
depends_on: ["07-05"]
|
|
files_modified:
|
|
- bouncer/mint_test.go
|
|
- bouncer/refresh_test.go
|
|
- bouncer/blacklist_test.go
|
|
- bouncer/password_test.go
|
|
- bouncer/jwt_test.go
|
|
- surf/locale_from_principal_test.go
|
|
- lagoon/validate_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/controllers/api_controller_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/classes/throttle_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/classes/codes_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/classes/events_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/updates/user_session_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/token_api_controller_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_locale_controller_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/routes_isolation_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/api_token_manager_test.go
|
|
- .planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md
|
|
autonomous: true
|
|
requirements: [AUTH-01, AUTH-02, AUTH-03, AUTH-04, I18N-02]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "go vet ./... and go test ./... -race are green in both summercms.go and fonoteka.go (QA-03)"
|
|
- "Every row in 07-VALIDATION.md's Per-Task Verification Map has a passing automated command, Task IDs are filled in, and nyquist_compliant is true"
|
|
- "The C-01/C-02/C-03/C-04/C-05 carried-forward decisions and every D-01..D-21 decision this phase implemented have at least one passing test asserting the behavior they describe"
|
|
- "postcard's memory driver proves mail.activate/mail.restore/mail.reactivate actually send with the right vars, closing the loop RESEARCH.md's Wave 0 gaps opened"
|
|
artifacts:
|
|
- path: ".planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md"
|
|
provides: "Task IDs filled in, nyquist_compliant: true, wave_0_complete: true"
|
|
key_links:
|
|
- from: "07-06 test suite"
|
|
to: "07-01..07-05 implementation"
|
|
via: "every Wave 0 gap listed in 07-VALIDATION.md now has a corresponding passing test file"
|
|
pattern: "Wave 0"
|
|
---
|
|
|
|
<objective>
|
|
Close every Wave 0 test gap 07-VALIDATION.md listed, bring `go vet`/`go test -race` green across both repos, and finalize the validation contract so `/gsd:secure-phase 7` has a clean, fully-tested baseline to review. This is the mandatory last plan of the phase per the project's lean-mode workflow rule.
|
|
|
|
Purpose: earlier plans wrote focused, task-scoped tests (TDD `behavior` blocks); this plan is the systematic pass that fills any remaining coverage hole — cross-cutting flows (full login→refresh→logout sequences, the D-20 exemption under concurrent access, `TestMustChangePasswordLock`'s full route-table proof) that don't naturally belong inside a single earlier task.
|
|
Output: green `go vet`/`go test -race` in both modules, and a `07-VALIDATION.md` with `nyquist_compliant: true`.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/07-user-plugin-and-authentication/07-CONTEXT.md
|
|
@.planning/phases/07-user-plugin-and-authentication/07-RESEARCH.md
|
|
@.planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md
|
|
@.planning/phases/07-user-plugin-and-authentication/07-01-SUMMARY.md
|
|
@.planning/phases/07-user-plugin-and-authentication/07-02-SUMMARY.md
|
|
@.planning/phases/07-user-plugin-and-authentication/07-03-SUMMARY.md
|
|
@.planning/phases/07-user-plugin-and-authentication/07-04-SUMMARY.md
|
|
@.planning/phases/07-user-plugin-and-authentication/07-05-SUMMARY.md
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 1: Complete summercms.go coverage — bouncer, surf, lagoon</name>
|
|
<files>bouncer/mint_test.go, bouncer/refresh_test.go, bouncer/blacklist_test.go, bouncer/password_test.go, bouncer/jwt_test.go, surf/locale_from_principal_test.go, lagoon/validate_test.go</files>
|
|
<read_first>
|
|
bouncer/mint.go, bouncer/refresh.go, bouncer/blacklist.go, bouncer/password.go, bouncer/jwt.go, bouncer/context.go, surf/locale_from_principal.go, lagoon/validate.go (all from 07-01, read the CURRENT state — not the plan text — since Task 3 of 07-01 may have adjusted details during execution),
|
|
.planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md (Wave 0 Requirements: `bouncer/{mint,refresh,blacklist}_test.go`, `surf/locale_from_principal_test.go`)
|
|
</read_first>
|
|
<behavior>
|
|
- Any behavior from 07-01's Task 2/3 `<behavior>` blocks not already covered by a passing test gets one now (cross-check by running `go test ./bouncer/... ./surf/... ./lagoon/... -cover` and reading the coverage report for any of `mint.go/refresh.go/blacklist.go/password.go/locale_from_principal.go`'s exported functions below a reasonable bar — every exported function needs at least one direct test, not just indirect coverage through a handler test in another repo).
|
|
- A concurrency test: two goroutines calling `MemoryBlacklist.Add`/`IsBlacklisted` on the same jti simultaneously never panics or races (`-race` clean); same for `PostgresBlacklist` against a real (testcontainers) Postgres.
|
|
- A round-trip test: `Mint` → `Verify` → `Refresh` → `Verify` (on the new token) → logout-equivalent `Add` with `validUntil=now` → the new token's `IsBlacklisted` is immediately `true`.
|
|
</behavior>
|
|
<action>
|
|
Run `go test ./bouncer/... ./surf/... ./lagoon/... -cover -short` and `-race`, read the coverage report, and add the missing direct-unit tests for any exported symbol from 07-01 that has no dedicated test today. Add the concurrency and round-trip tests described above. Do not modify production code in this task unless a test uncovers an actual bug (if so, fix it, note it in the SUMMARY's Deviations section per the standard executor protocol, and keep the fix minimal).
|
|
</action>
|
|
<verify>
|
|
<automated>go vet ./... && go test ./bouncer/... ./surf/... ./lagoon/... -race -cover</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `go test ./bouncer/... ./surf/... ./lagoon/... -race` exits 0 with no data-race report
|
|
- A coverage report shows no Phase 7 exported function in `mint.go/refresh.go/blacklist.go/password.go/locale_from_principal.go` with zero direct test references
|
|
- The concurrent `MemoryBlacklist`/`PostgresBlacklist` Add/IsBlacklisted test passes under `-race`
|
|
</acceptance_criteria>
|
|
<done>go vet and go test -race are green across bouncer/surf/lagoon; every exported Phase 7 symbol in these packages has a direct test.</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 2: Complete fonoteka.go coverage — user plugin, fonoteka plugin, mail</name>
|
|
<files>
|
|
../fonoteka.go/plugins/golem15/user/controllers/api_controller_test.go,
|
|
../fonoteka.go/plugins/golem15/user/classes/throttle_test.go,
|
|
../fonoteka.go/plugins/golem15/user/classes/codes_test.go,
|
|
../fonoteka.go/plugins/golem15/user/classes/events_test.go,
|
|
../fonoteka.go/plugins/golem15/user/updates/user_session_test.go,
|
|
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/token_api_controller_test.go,
|
|
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_locale_controller_test.go,
|
|
../fonoteka.go/plugins/golem15/fonoteka/routes_isolation_test.go,
|
|
../fonoteka.go/plugins/golem15/fonoteka/classes/auth/api_token_manager_test.go
|
|
</files>
|
|
<read_first>
|
|
../fonoteka.go/plugins/golem15/user/controllers/api_controller.go (current, all handlers from 07-02/07-03),
|
|
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/token_api_controller.go, me_locale_controller.go (07-04),
|
|
.planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md (Per-Task Verification Map — `TestApiController`, `TestGetApiArray`, `TestTokenApi`, `TestMustChangePasswordLock`, the mail memory-driver assertion)
|
|
</read_first>
|
|
<behavior>
|
|
- A full sequence test (real Postgres): register → fetch → update → change-password → refresh → logout → the logged-out token gets 401 on a subsequent fetch — the Go-side mirror of the `nuxt-auth` fixture, run as a fast in-process `httptest` sequence independent of the PHP-recorded parity fixtures (this test must be able to run in CI without the isolated PHP instance).
|
|
- `TestMustChangePasswordLock`: a user with `must_change_password=true` gets 423 on `/_fonoteka/api/v1/genres` and `/_fonoteka/api/v1/tokens`, succeeds on `GET/PUT /_fonoteka/api/v1/me/locale`, succeeds on `/_user/api/v1/change-password`, and after that call the lock is cleared and `/_fonoteka/api/v1/genres` succeeds.
|
|
- `TestGetApiArray`: the full payload shape (base fields + organisation_id/role + must_change_password + preferred_locale) for a user with all of those set, run against a real boot of both plugins together (not a unit-level mock) — the definitive AUTH-02 acceptance test.
|
|
- Mail: `postcard`'s `memory` driver captures `mail.activate` (from `Register`'s user-mode branch), `mail.restore` (from `ForgotPassword`), and `mail.reactivate` (from `Login`'s soft-delete-restore branch, D-17) each with the expected `Vars` keys (`link`/`code`/`name` as applicable), AND re-confirms the pl/en locale-suffix routing 07-03 Task 3 already tests (`mailTemplate`/`resolveMailLocale`) still holds after any coverage-driven changes in this task -- do not let a coverage fix silently regress the locale-suffix wiring.
|
|
- `TestTokenApi`: mint with each of the three individual scopes plus a two-scope combination, then a scope-ceiling violation attempt (`"admin"`) is rejected before any row is persisted; `InvScope` middleware (Phase 6, unchanged) 403s a `write`-scoped route call made with a `read`-only token, proving the ceiling is enforced end-to-end through the ALREADY-SHIPPED Phase 6 gate, not just at mint time.
|
|
- `TestBlacklistSweepStarts`: booting the `golem15.user` plugin with a short test-only `golem15.user.jwt.blacklist_sweep_interval` and a pre-inserted expired `jwt_blacklist` row observes the row removed after waiting past the interval -- the test hook confirming 07-02 Task 2's sweep goroutine actually runs during a real plugin Boot, not just that `BlacklistStore.Sweep` works in isolation (07-01).
|
|
- `TestNoDeferredRoutesResolve` (D-05): booting both plugins and asserting the real route table has no entry whose path matches any of the deferred PIN-login, device-auth, 2FA, `GET /api/user/batch`, or `GET /_user/activate/{id}` paths -- and that `POST /_user/api/v1/login`'s success body never contains a `two_factor_required` key.
|
|
</behavior>
|
|
<action>
|
|
Run `go test ./plugins/golem15/... -cover -short` and `-race` in `fonoteka.go`, fill every coverage gap the report shows for Phase 7 files, and add the six cross-cutting tests described above. Where a gap traces to a real bug (not just missing coverage), fix it minimally and record the deviation. Confirm `go list -deps ./plugins/golem15/user/...` contains no `plugins/golem15/fonoteka` import (the AUTH-02 import-direction invariant) as an explicit, named test — not just an incidental compile-time fact.
|
|
</action>
|
|
<verify>
|
|
<automated>go vet ./... && go test ./... -race -cover</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `go test ./... -race` exits 0 in fonoteka.go
|
|
- `TestMustChangePasswordLock` proves 423 on `/_fonoteka/api/v1/genres` and `/tokens`, 200 on `me/locale` and `/_user/api/v1/change-password`, and 200 on `/_fonoteka/api/v1/genres` again after the lock clears
|
|
- `TestGetApiArray` asserts the full payload shape against a real dual-plugin boot, not a mock
|
|
- The `postcard` memory driver captures all three mail sends (activate/restore/reactivate) with non-empty vars, and the pl/en locale-suffix template names for all three
|
|
- `TestBlacklistSweepStarts` observes an expired `jwt_blacklist` row removed after a real plugin Boot with a short sweep interval
|
|
- `TestNoDeferredRoutesResolve` finds zero route-table matches for PIN/device/2FA/batch/activate-link paths, and login's success body never contains `two_factor_required`
|
|
</acceptance_criteria>
|
|
<done>go vet and go test -race are green across all of fonoteka.go; TestMustChangePasswordLock and TestGetApiArray both pass against a real dual-plugin boot; mail sends (with locale suffixes) are asserted through the memory driver; the blacklist sweep goroutine and the D-05 deferred-route absence are both proven by a named test.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 3: Finalize 07-VALIDATION.md and run the full phase gate</name>
|
|
<files>.planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md</files>
|
|
<read_first>
|
|
.planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md (current draft — Per-Task Verification Map with `TBD` Task IDs, Wave 0 Requirements checklist, Validation Sign-Off checklist)
|
|
</read_first>
|
|
<action>
|
|
Fill in every `TBD` Task ID in the Per-Task Verification Map with the real `{plan}-{task}` id it landed in (e.g. `07-02-2`, `07-04-1`), set every `Status` cell to `✅ green` (confirmed by the full run below), check off every Wave 0 Requirements box, check off every Validation Sign-Off box, and set the frontmatter `wave_0_complete: true` and `nyquist_compliant: true`. Do not mark a row green without having actually re-run its `Automated Command` in this task.
|
|
|
|
Run the full phase gate: `go vet ./... && go test ./... -race` in `summercms.go`; `go vet ./... && go test ./... -race` in `fonoteka.go`; `summer parity:replay --manifest fonoteka.go/parity/manifest.yaml`. Record the final corpus coverage numbers (recorded/ported/pending) in this plan's SUMMARY.
|
|
</action>
|
|
<verify>
|
|
<automated>go vet ./... && go test ./... -race</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `07-VALIDATION.md`'s Per-Task Verification Map has zero `TBD` Task IDs and zero `⬜ pending` Status cells
|
|
- `07-VALIDATION.md` frontmatter reads `nyquist_compliant: true` and `wave_0_complete: true`
|
|
- `go vet ./... && go test ./... -race` exits 0 in both summercms.go and fonoteka.go
|
|
- `summer parity:replay --manifest fonoteka.go/parity/manifest.yaml` reports zero failing cases
|
|
</acceptance_criteria>
|
|
<done>07-VALIDATION.md has zero TBD/pending rows, nyquist_compliant: true; the full phase gate (both repos + parity replay) is green.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
No new trust boundaries — this plan tests behavior already gated in 07-01..07-05; its own threat surface is "a test asserts the wrong thing and gives false confidence," mitigated by tying every new test to a specific D-XX/C-XX/T-07-NN citation in its name or comment rather than writing generic smoke tests.
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|-------------|------------------|
|
|
| T-07-01 | Spoofing / Elevation of Privilege | Full-sequence test | mitigate (verification) | The Go-side login→refresh→logout→401 sequence test proves T-07-01's mitigation holds end-to-end, not just in the isolated 07-01/07-02 unit tests |
|
|
| T-07-08 | Elevation of Privilege | TestMustChangePasswordLock | mitigate (verification) | Proves the 423 lock's exempt set against a real dual-plugin boot, the definitive AUTH-04 acceptance test |
|
|
|
|
Note: `/gsd:secure-phase 7` runs after this plan and produces `07-SECURITY-REVIEW.md`; this plan's job is to hand it a fully green, fully tested baseline, not to perform the security review itself.
|
|
|
|
</threat_model>
|
|
|
|
<verification>
|
|
`go vet ./... && go test ./... -race` green in both `summercms.go` and `fonoteka.go`. `summer parity:replay` green across the full manifest (154-route fonoteka surface + 15-route user surface + tokens/me-locale). `07-VALIDATION.md` fully signed off.
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
Phase 7 is fully implemented, fully tested, and ready for the security-review agent — no open Wave 0 gaps, no red tests, no TBD rows in the validation contract.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/phases/07-user-plugin-and-authentication/07-06-SUMMARY.md` when done
|
|
</output>
|