Files
summercms/.planning/phases/07-user-plugin-and-authentication/07-DISCUSSION-LOG.md
2026-09-22 00:24:21 +02:00

7.1 KiB

Phase 7: User plugin and authentication - Discussion Log

Audit trail only. Do not use as input to planning, research, or execution agents. Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.

Date: 2026-09-22 Phase: 7-User plugin and authentication Areas discussed: Route surface cut, JWT lifecycle, Parity evidence, Security vs parity


Route surface cut

Core routes

Option Description Selected
Nuxt-used + criterion 1 login, logout, fetch, refresh, register, update, change-password, marketing-consent + forgot/reset/activate/activate-by-code ✓
Nuxt-used only Drop forgot/reset/activate; conflicts with criterion 1
Everything live in routes.php Also api/user/batch and /_user/activate/{id}

User's choice: Asked first which endpoints option 1 skips versus option 3. Answer given: exactly two — GET /api/user/batch (presence, no caller) and GET /_user/activate/{id} (signed-URL legacy theme flow). Then: "OK option 1 then, we'll build presence in future milestones, legacy cms theme flow is legacy."

Social login

Option Description Selected
oauth-providers only, rest deferred Login page renders; provider flow deferred ✓
Port the full social flow now Redirect, callback, complete endpoints, bootstrap OTP
Defer all of it oauth-providers 404s

Avatar

Option Description Selected
Port both now Multipart upload on Phase 5 attachments + Thumb(128) ✓
Payload fields only Endpoints wait for Phase 12

Dead routes (PIN, device, 2FA)

Option Description Selected
Skip entirely, document No 501 shells; login never returns two_factor_required ✓
Skip, but port the 2FA login branch Enrolled accounts not downgraded at cutover

JWT lifecycle

Blacklist storage

Option Description Selected
Postgres table Survives restarts, works across replicas ✓
In-process map Restart resurrects logged-out tokens
Postgres + in-process read cache More moving parts

Cutover compatibility

Option Description Selected
Yes, wire-compatible tokens Same secret and claims; PHP blacklist not migrated ✓
Compatible claims, but no promise Cutover may force re-login
Yes, and migrate the PHP blacklist Phase 15 import

Extraction

Option Description Selected
Port the split exactly Incl. ?jwt_token= and jwt-auth default parsers
One permissive extractor everywhere Cookies on /_user too
Header + cookies only, everywhere (follow-up) Drop all URL/body/route-param sources ✓
Drop URL sources, keep POST-body jwt_token (follow-up)
Drop, but answer explicitly with 400 (follow-up)

User's choice: Free text — "jwt_token GET param should be depreciated by now per Golem15 Stack security upgrade, we don't want tokens in access logs. But we need to port the split for Bearer / Cookies as it's used trough all projects." Notes: Verified neither the Nuxt app nor fonoteka-mcp sends jwt_token/?token=; PHP's $request->get('jwt_token') also reads the POST body. Follow-up confirmed "Header + cookies only, everywhere".

TTLs

Option Description Selected
Config keys, PHP-env defaults in the app Library defaults in plugin, Płytarium values in fonoteka.go ✓
Hardcode Płytarium's values in the plugin

Parity evidence

Question Options Selected
How is the /_user contract proven? Record new tide fixtures + extend manifest / Hand-written contract tests / Both Record new tide fixtures, extend manifest
Mail-dependent codes Seed hook reads code from DB / Capture from mail sink / Pre-seeded fixed codes Seed hook reads the code from the DB
Error-path coverage Every distinct status + body per route / Happy path + one failure Every distinct status + body per route
Nuxt client flow Add a nuxt-auth flow fixture / Route fixtures are enough Add a nuxt-auth flow fixture

Security vs parity

Reset/activation codes

Option Description Selected
Add expiry, keep format and columns TTL via added timestamp column, constant-time compare, same wire bodies ✓
Exact PHP behaviour No expiry
Expiry + hash at rest Breaks PHP-issued codes and the DB-reading seed hook

User's choice: "Is your research correct? You sure PHP codes do not expire? Option 1 tho, quite sure php codes do expire" Notes: Re-verified in source: Winter Storm User.php:258-292 is a plain === with no timestamp, the plugin does not override it, the users table has no expiry column, and passwords.expire=60 belongs to the unused Laravel broker. Things that do expire: bootstrap OTP (30 min), signed invite URL (72 h). Possible that another Golem15 project has a newer plugin version. Assumption stated: null issued-at codes get one full TTL from cutover; not objected to.

Failed-login throttle

Option Description Selected
Port Winter Throttle onto user_throttle Durable, ban/suspend states, PHP bodies ✓
Limiter bucket only In-process, no ban state

Login/register quirks

Option Description Selected
Port restore-on-login, drop guest conversion ✓
Port both exactly
Drop both

Mail + hashing

Option Description Selected
Send inline now; bcrypt from config, rehash on login Seam for River in Phase 11 ✓
Inline mail; bcrypt 10 fixed, no rehash
Goroutine fire-and-forget mail

Revoke tokens on password change/reset

Option Description Selected
Invalidate all older tokens Per-user "valid after" timestamp checked by the jwt guard ✓
Exact PHP behaviour Old tokens live up to 30 days
Invalidate on reset only

Setting must_change_password

Option Description Selected
CLI command now, admin field in Phase 9 ✓
Tests and seed hooks only

Claude's Discretion

  • Locale-resolution stage placement for I18N-02
  • Package homes for minting/blacklist, Store interface, sweep interval
  • getApiArray event type and merge order
  • Minimal read models for payload fields backed by unported tables (permissions, groups, role)
  • 423-exempt grouping asserted over the route table
  • Column/config/command names; plan count and split

Deferred Ideas

  • Presence (api/user/batch) — future milestone
  • Full social login flow and password-bootstrap OTP
  • PIN login, device auth, 2FA
  • Signed admin-invite activation link (legacy)
  • Guest conversion (keios.eu), account deletion/GDPR routes
  • River-queued mail (Phase 11), PHP blacklist migration (not done), admin lock/ban UI (Phase 9)