docs(07): capture phase context
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user