docs(07-01): complete framework auth primitives plan
This commit is contained in:
@@ -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