docs(07-02): complete the user session plan

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-09-22 15:15:16 +02:00
parent ae9e11f65d
commit 3cf938867c
3 changed files with 125 additions and 6 deletions

View File

@@ -281,7 +281,7 @@ Plans:
**Wave 2** *(blocked on 07-01)*
- [ ] 07-02-PLAN.md — User/Throttle schema and the core session loop: login/logout/fetch/refresh/register
- [x] 07-02-PLAN.md — User/Throttle schema and the core session loop: login/logout/fetch/refresh/register
**Wave 3** *(blocked on 07-02)*
@@ -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 | 1/6 | In Progress| |
| 7. User plugin and authentication | 2/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 | - |

View File

@@ -4,13 +4,13 @@ milestone: v1.0
milestone_name: milestone
status: executing
stopped_at: Completed 07-01-PLAN.md
last_updated: "2026-09-22T11:43:03.651Z"
last_updated: "2026-09-22T13:14:46.649Z"
last_activity: 2026-09-22
progress:
total_phases: 15
completed_phases: 6
total_plans: 43
completed_plans: 38
completed_plans: 39
percent: 40
---
@@ -26,11 +26,11 @@ See: .planning/PROJECT.md (updated 2026-09-16)
## Current Position
Phase: 07 (user-plugin-and-authentication) — EXECUTING
Plan: 2 of 6
Plan: 3 of 6
Status: Ready to execute
Last activity: 2026-09-22
Progress: [█████████░] 88%
Progress: [█████████░] 91%
## Performance Metrics
@@ -82,6 +82,7 @@ Progress: [█████████░] 88%
| 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 |
| Phase 07 P02 | 83m | 3 tasks | 22 files |
## Accumulated Context

View File

@@ -0,0 +1,118 @@
---
phase: 07-user-plugin-and-authentication
plan: 02
subsystem: auth
tags: [user, jwt, throttle, register, festival]
requires:
- phase: 07-user-plugin-and-authentication
provides: bouncer Mint, Refresh, BlacklistStore, bcrypt, lagoon email/confirmed
provides:
- golem15.user login, logout, fetch, refresh, register, oauth-providers
- user_throttle and jwt_blacklist migrations
- GetApiArrayEvent collected by golem15.fonoteka
affects: [07-03, 07-04, 07-05]
tech-stack:
added: []
patterns: [Bearer-only session handlers, cookie fallback only on the registry jwt guard, pre-password throttle gate]
key-files:
created:
- ../fonoteka.go/plugins/golem15/user/controllers/api_controller.go
- ../fonoteka.go/plugins/golem15/user/routes.go
- ../fonoteka.go/plugins/golem15/user/classes/events.go
- ../fonoteka.go/plugins/golem15/user/classes/throttle.go
modified:
- ../fonoteka.go/plugins/golem15/user/plugin.go
- ../fonoteka.go/plugins/golem15/fonoteka/plugin.go
key-decisions:
- "Login gates with RejectIfThrottled before the password check and records the attempt once afterward"
- "Disabled and throttled registration return the production SafeExceptionResponse body unless app.debug is true"
- "Fonoteka listens for GetApiArrayEvent; the user plugin does not import fonoteka"
patterns-established:
- "Pattern: /_user/api/v1 group middleware is only throttle:user-api; handlers authenticate Bearer-only"
- "Pattern: mail template names go through MailTemplate and ResolveMailLocale"
requirements-completed: []
duration: 83min
completed: 2026-09-22
---
# Phase 7 Plan 02: User session loop Summary
**Login, logout, fetch, refresh, and register on `/_user/api/v1`, with a per-user login throttle, a Postgres JWT blacklist, and fonoteka's organisation fields merged through `GetApiArrayEvent`.**
## Performance
- **Duration:** 83 min
- **Started:** 2026-09-22T11:48:00Z
- **Completed:** 2026-09-22T13:11:00Z
- **Tasks:** 3
- **Files modified:** 22
## Accomplishments
- Email and password login returns a JWT whose `sub` is the user id, `prv` is the hardcoded User hash, and `iss` is the request URL. Logout forever-blacklists that jti so the next fetch is 401.
- Wrong password, an unknown email, and a suspended account share the body `{"error":true,"message":"Invalid email or password"}`.
- Register covers auto (token), user (activation mail), and admin (`{}`). Production hides disabled and throttled causes behind `{"error":"Internal server error"}`.
- `golem15.fonoteka` adds `organisation_id`, `organisation_role`, `must_change_password`, and `preferred_locale` on `GetApiArrayEvent`.
## Task Commits
1. **Task 1: User session schema and config** — `7046213` (test), `4cc7433` (feat) in `fonoteka.go`
2. **Task 2: Throttle, mail locale, login/logout/fetch/refresh** — `1076db9`, `1dd4aba` in `fonoteka.go`
3. **Task 3: Register and GetApiArrayEvent** — `bac71fe` in `fonoteka.go`
## Files Created/Modified
- `plugins/golem15/user/controllers/api_controller.go` — session and register handlers
- `plugins/golem15/user/routes.go` — `/_user/api/v1` group with `throttle:user-api`
- `plugins/golem15/user/plugin.go` — Postgres blacklist, cookie-capable jwt guard, user-api bucket, sweep
- `plugins/golem15/user/classes/throttle.go` — failed-login counter and the pre-password gate
- `plugins/golem15/user/classes/events.go` — `GetApiArrayEvent` and `RegisterEvent`
- `plugins/golem15/fonoteka/plugin.go` — payload listener
## Decisions Made
The login gate does not increment the attempt counter. `CheckAndRecordLogin(..., false)` both checks a ban and records a failure, so calling it before and after the password check would suspend on the third HTTP failure. `RejectIfThrottled` only reports an existing ban or suspension. The outcome is recorded once.
Registration's disabled and throttled branches use `{"error":"..."}` at 500. That is distinct from `wire.WriteOpaque500`, which is `{"error":true,"message":"Internal server error"}`.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] The pre-password throttle call must not increment attempts**
- **Found during:** Task 2
- **Issue:** The plan called `CheckAndRecordLogin(..., false)` before the password check and again on failure. That function increments on `ok=false`, so each failed login counted twice and the sixth HTTP login was not the one rejected before the password comparison.
- **Fix:** `RejectIfThrottled` enforces ban and suspension without writing. `CheckAndRecordLogin` runs once with the outcome.
- **Files modified:** `classes/throttle.go`, `controllers/api_controller.go`
- **Verification:** `TestLoginSixthAttempt` — five failures suspend, the sixth correct password returns the generic 401, and `attempts` stays 5.
- **Committed in:** `1dd4aba`
---
**Total deviations:** 1 auto-fixed (Rule 1)
**Impact on plan:** Keeps the 5-attempt / 15-minute suspend aligned with one failed HTTP login. No new route.
## Issues Encountered
None
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
Ready for 07-03 (account management) and 07-04 (personal tokens). AUTH-01 stays open: password reset and email verification are still 07-03. AUTH-02's payload seam is in place; the requirement checkbox stays pending until the phase requirement is signed off.
## Self-Check: PASSED
- Session and register handlers exist under `plugins/golem15/user/controllers/api_controller.go`.
- `fonoteka.go` commits `7046213`, `4cc7433`, `1076db9`, `1dd4aba`, and `bac71fe` are on master.
- `go test` for the session, throttle, register, and `TestGetApiArray` filters passed. `go vet` on the user and fonoteka plugins passed.