docs(07-01): complete framework auth primitives plan
This commit is contained in:
@@ -277,7 +277,7 @@ Plans:
|
||||
Plans:
|
||||
**Wave 1**
|
||||
|
||||
- [ ] 07-01-PLAN.md — bouncer JWT lifecycle, password hashing, I18N-02 locale-from-principal, lagoon.Validate extensions
|
||||
- [x] 07-01-PLAN.md — bouncer JWT lifecycle, password hashing, I18N-02 locale-from-principal, lagoon.Validate extensions
|
||||
|
||||
**Wave 2** *(blocked on 07-01)*
|
||||
|
||||
@@ -448,7 +448,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
|
||||
| 4. CLI scaffolding, i18n and mail | 4/4 | Complete | 2026-09-18 |
|
||||
| 5. Data layer full fidelity | 6/6 | Complete | 2026-09-18 |
|
||||
| 6. HTTP routing, auth groups and rate limiting | 14/14 | Complete | 2026-09-21 |
|
||||
| 7. User plugin and authentication | 0/TBD | Not started | - |
|
||||
| 7. User plugin and authentication | 1/6 | In Progress| |
|
||||
| 8. OAuth2.1 authorization server | 0/TBD | Not started | - |
|
||||
| 9. Backend admin authentication and schema pipeline | 0/TBD | Not started | - |
|
||||
| 10. Admin Vue SPA | 0/TBD | Not started | - |
|
||||
|
||||
@@ -3,14 +3,14 @@ gsd_state_version: 1.0
|
||||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: executing
|
||||
stopped_at: Phase 7 context gathered
|
||||
last_updated: "2026-09-22T10:39:04.511Z"
|
||||
last_activity: 2026-09-22 -- Phase 7 planning complete
|
||||
stopped_at: Completed 07-01-PLAN.md
|
||||
last_updated: "2026-09-22T11:43:03.651Z"
|
||||
last_activity: 2026-09-22
|
||||
progress:
|
||||
total_phases: 15
|
||||
completed_phases: 6
|
||||
total_plans: 43
|
||||
completed_plans: 37
|
||||
completed_plans: 38
|
||||
percent: 40
|
||||
---
|
||||
|
||||
@@ -21,16 +21,16 @@ progress:
|
||||
See: .planning/PROJECT.md (updated 2026-09-16)
|
||||
|
||||
**Core value:** An existing WinterCMS-shaped app can be ported plugin by plugin to a single Go binary without its frontend noticing: the PHP version's API contract is the acceptance test.
|
||||
**Current focus:** Phase 7 — user plugin and authentication
|
||||
**Current focus:** Phase 07 — user-plugin-and-authentication
|
||||
|
||||
## Current Position
|
||||
|
||||
Phase: 7
|
||||
Plan: Not started
|
||||
Phase: 07 (user-plugin-and-authentication) — EXECUTING
|
||||
Plan: 2 of 6
|
||||
Status: Ready to execute
|
||||
Last activity: 2026-09-22 -- Phase 7 planning complete
|
||||
Last activity: 2026-09-22
|
||||
|
||||
Progress: [██████████] 100%
|
||||
Progress: [█████████░] 88%
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
@@ -81,6 +81,7 @@ Progress: [██████████] 100%
|
||||
| Phase 06 P09 | 4 min | 1 tasks | 2 files |
|
||||
| Phase 06 P10 | 3h 15m | 1 tasks | 2 files |
|
||||
| Phase 06 P11 | 12h 30m | 1 tasks | 1 files |
|
||||
| Phase 07 P01 | 12 min | 3 tasks | 20 files |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
@@ -191,6 +192,7 @@ Recent decisions affecting current work:
|
||||
- [Phase 06]: Assert exact denial bytes before JSON shape checks — Whitespace normalization would hide response-contract regressions.
|
||||
- [Phase 06]: Retain all four earlier accepted risks unchanged; T-06-23 through T-06-27 are mitigated, not accepted or deferred. — Both repositories' authoritative race and vet gates passed, and each new threat has concrete source and named regression evidence.
|
||||
- [Phase 06]: Anonymous inline limiter identity is documented only as inline:domainless|<ClientIP>, excluding policy text and request or forwarded Host inputs. — The production resolver and three executed regressions prove Host rotation and inline-parameter changes cannot create fresh anonymous budgets while authenticated principals keep isolated u:<id> keys.
|
||||
- [Phase 07]: Blacklist storage expiry follows PHP jwt-auth (later of exp and iat+refreshTTL, plus one minute). — Using the raw access exp would drop a logged-out token that is still inside the refresh window.
|
||||
|
||||
### Pending Todos
|
||||
|
||||
@@ -212,6 +214,6 @@ Items acknowledged and carried forward from previous milestone close:
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-09-21T22:24:22.108Z
|
||||
Stopped at: Phase 7 context gathered
|
||||
Resume file: .planning/phases/07-user-plugin-and-authentication/07-CONTEXT.md
|
||||
Last session: 2026-09-22T11:42:50.114Z
|
||||
Stopped at: Completed 07-01-PLAN.md
|
||||
Resume file: None
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
---
|
||||
phase: 07-user-plugin-and-authentication
|
||||
plan: 01
|
||||
subsystem: auth
|
||||
tags: [jwt, bcrypt, blacklist, locale, validation]
|
||||
|
||||
requires:
|
||||
- phase: 06-http-routing-auth-groups-and-rate-limiting
|
||||
provides: Bearer JWT guard, Principal, lagoon.Validate, surf middleware registration
|
||||
provides:
|
||||
- bouncer.Mint, Refresh, BlacklistStore, VerifyClaims
|
||||
- bcrypt HashPassword/CheckPassword/NeedsRehash
|
||||
- Principal.PreferredLocale and TokensValidAfter
|
||||
- surf locale.from-principal middleware
|
||||
- lagoon email, confirmed, different, and mimes rules
|
||||
affects: [07-02, 07-03, 07-04]
|
||||
|
||||
tech-stack:
|
||||
added: [golang.org/x/crypto v0.57.0]
|
||||
patterns: [HS256 mint with hardcoded prv hash, refresh without exp validation, grace-windowed jti blacklist]
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- bouncer/mint.go
|
||||
- bouncer/refresh.go
|
||||
- bouncer/blacklist.go
|
||||
- bouncer/password.go
|
||||
- surf/locale_from_principal.go
|
||||
modified:
|
||||
- bouncer/jwt.go
|
||||
- bouncer/context.go
|
||||
- surf/router.go
|
||||
- lagoon/validate.go
|
||||
- go.mod
|
||||
|
||||
key-decisions:
|
||||
- "Blacklist storage expiry follows PHP jwt-auth: later of exp and iat+refreshTTL, plus one minute"
|
||||
- "A blacklisted jti reuses the existing bad-signature 401 text"
|
||||
- "Refresh rebuilds the access TTL from the old token's exp-iat because the signature has no separate ttl argument"
|
||||
- "golang.org/x/crypto was promoted with go get @latest (v0.57.0) after the human checkpoint"
|
||||
|
||||
patterns-established:
|
||||
- "Pattern: Mint stamps iss from the calling endpoint URL and prv from the hardcoded User class hash"
|
||||
- "Pattern: only Refresh uses jwt.WithoutClaimsValidation; Verify and the guard still require exp"
|
||||
|
||||
requirements-completed: [AUTH-01, I18N-02]
|
||||
|
||||
duration: 12min
|
||||
completed: 2026-09-22
|
||||
---
|
||||
|
||||
# Phase 7 Plan 01: Framework auth primitives Summary
|
||||
|
||||
**JWT mint, sliding refresh, and a grace-windowed jti blacklist, plus bcrypt, a post-auth locale override, and email/confirmed/different/mimes validation.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 12 min
|
||||
- **Started:** 2026-09-22T11:28:00Z
|
||||
- **Completed:** 2026-09-22T11:39:34Z
|
||||
- **Tasks:** 3
|
||||
- **Files modified:** 20
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- `bouncer.Mint` / `Refresh` / `BlacklistStore` / `VerifyClaims` are in place for the user plugin's login, refresh, and logout handlers.
|
||||
- `Principal` now carries `PreferredLocale` and `TokensValidAfter`, and `surf` registers `locale.from-principal`.
|
||||
- `lagoon.Validate` accepts `email`, `confirmed`, `different:field`, and `mimes:list`. `golang.org/x/crypto` is a direct dependency, and a real PHP `$2y$` hash verifies.
|
||||
|
||||
## Task Commits
|
||||
|
||||
1. **Task 1: Approve golang.org/x/crypto** — human checkpoint, approved. Promotion landed in the Task 3 commit.
|
||||
2. **Task 2: JWT lifecycle primitives** — `251f3cc` (test), `cad445a` (feat)
|
||||
3. **Task 3: Password hashing, locale override, validation** — `bccd7f8` (test), `8fcaff7` (feat)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `bouncer/mint.go` — HS256 mint with the hardcoded `prv` hash
|
||||
- `bouncer/refresh.go` — sliding refresh that skips `exp` and blacklists the old jti
|
||||
- `bouncer/blacklist.go` — memory and Postgres stores with a grace window
|
||||
- `bouncer/password.go` — bcrypt hash, check, and rehash
|
||||
- `bouncer/jwt.go` — cookie fallback, blacklist check, `TokensValidAfter` cutoff, `VerifyClaims`
|
||||
- `bouncer/context.go` — `PreferredLocale` and `TokensValidAfter`
|
||||
- `surf/locale_from_principal.go` — post-auth locale override
|
||||
- `surf/router.go` — registers `locale.from-principal`
|
||||
- `lagoon/validate.go` — `email`, `confirmed`, `different`, `mimes`
|
||||
- `go.mod` — direct `golang.org/x/crypto v0.57.0`
|
||||
|
||||
## Decisions Made
|
||||
|
||||
Blacklist rows live until the later of the old `exp` and `iat+refreshTTL`, plus one minute, matching PHP `Blacklist::getMinutesUntilExpired`. A blacklisted token returns the existing "Token Signature could not be verified." body. `Refresh` copies the previous access lifetime (`exp-iat`) onto the new token.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 1 - Bug] Blacklist storage expiry was the raw access `exp`**
|
||||
- **Found during:** Task 2 (JWT lifecycle primitives)
|
||||
- **Issue:** The plan set `expiresAt` to the old token's `exp`. For a token that is already expired but still inside `refreshTTL`, that timestamp is in the past, so lazy expiry and `Sweep` would drop the row and a logged-out token could be refreshed again.
|
||||
- **Fix:** Storage expiry is the later of `exp` and `iat+refreshTTL`, plus one minute. `validUntil` is still `now+grace`.
|
||||
- **Files modified:** `bouncer/refresh.go`
|
||||
- **Verification:** `TestRefreshBlacklistsOldJTI` (expired access token, grace 0, still blacklisted; grace window still open otherwise)
|
||||
- **Committed in:** `cad445a`
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 1 auto-fixed (Rule 1)
|
||||
**Impact on plan:** Correctness fix so logout and refresh revocation survive the refresh window. No new API surface.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
Ready for 07-02. The user plugin can import `Mint`, `Refresh`, `NewPostgresBlacklist`, `HashPassword`, and the new `Principal` fields. AUTH-01 and I18N-02 are not fully delivered yet: the session routes, locale endpoints, and must-change-password exemption are still 07-02 through 07-04.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- `bouncer/mint.go`, `bouncer/refresh.go`, `bouncer/blacklist.go`, `bouncer/password.go`, and `surf/locale_from_principal.go` exist.
|
||||
- `git log --oneline --grep=07-01` shows the test and feat commits above.
|
||||
- `go vet ./...` and `go test ./... -short` passed. `go test ./bouncer/... ./surf/... ./lagoon/... -race -short` passed.
|
||||
Reference in New Issue
Block a user