Assembled avatar POST is 200. UAT is 12/12. AUTH-02 through AUTH-04 and I18N-02 are marked complete. Do not auto-advance. Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
6.9 KiB
Markdown
104 lines
6.9 KiB
Markdown
---
|
|
status: resolved
|
|
phase: 07-user-plugin-and-authentication
|
|
source: [07-01-SUMMARY.md, 07-02-SUMMARY.md, 07-03-SUMMARY.md, 07-04-SUMMARY.md, 07-05-SUMMARY.md, 07-06-SUMMARY.md, 07-07-SUMMARY.md, 07-08-SUMMARY.md]
|
|
started: 2026-09-23T08:13:12Z
|
|
updated: 2026-09-23T08:52:00Z
|
|
---
|
|
|
|
## Current Test
|
|
|
|
[testing complete]
|
|
|
|
## Tests
|
|
|
|
### 1. Session loop — register, login, fetch, refresh, logout
|
|
expected: POST /_user/api/v1/register (auto mode) returns {token,user}. Login returns a JWT whose sub is the user id, prv is the hardcoded User class hash, and iss is the request URL. Fetch returns that user. Refresh returns a new token. Logout forever-blacklists the jti; a following fetch with that bearer is 401.
|
|
result: pass
|
|
reported: |
|
|
Curled the assembled app.Handler. Register 200 with token+user. Login JWT sub=user id, prv=a867434cbc213adfbe78a02bed7082a6bd99c883, iss ends with /_user/api/v1/login. Fetch 200. Refresh returns a new token. Logout {"message":"Logged out"}. Fetch after logout 401.
|
|
|
|
### 2. Invalid login shares one body
|
|
expected: Wrong password, an unknown email, and a suspended account all return the same 401 body {"error":true,"message":"Nieprawidłowy email lub hasło"} (or the English Invalid email or password equivalent). No account enumeration.
|
|
result: pass
|
|
reported: |
|
|
Wrong password, unknown email, and the sixth attempt after five failures all returned 401 {"error":true,"message":"Nieprawidłowy email lub hasło"}.
|
|
|
|
### 3. Forgot-password is enumeration-safe; reset invalidates older tokens
|
|
expected: POST forgot-password always answers 200 {"message":"If that email exists, a reset link has been sent."} whether the email exists or not. Reset consumes {id}!{code}, stores the new hash, and sets tokens_valid_after so older JWTs fail.
|
|
result: pass
|
|
reported: |
|
|
Known and unknown emails both returned 200 {"message":"If that email exists, a reset link has been sent."}. Reset with {id}!{code} returned {"message":"Password has been reset"}. Old JWT fetch 401. New password login 200.
|
|
|
|
### 4. Activation and already-activated Winter page
|
|
expected: Public activate-by-code with a valid {id}!{code} activates (or restores a soft-deleted user) and returns a token. Already-activated Activate and ActivateByCode serve the embedded Winter production error page (500, text/html), not a JSON 422.
|
|
result: pass
|
|
reported: |
|
|
Valid activate-by-code 200 with token and is_activated true. Reusing that code and authenticated activate on the now-activated user both returned 500 text/html starting with the Polish Winter error page.
|
|
|
|
### 5. Profile, password change, marketing consent, and avatar
|
|
expected: Authenticated update, change-password, and marketing-consent write the signed-in row. A password change keeps the presenting token and invalidates older ones. Avatar upload sniffs the first bytes, stores an attach.File, and fills has_avatar plus a 128px avatar_url; remove clears them.
|
|
result: pass
|
|
reported: |
|
|
Profile update, marketing consent, and change-password work over curl (presenting JWT kept, older JWT 401 after a 2s iat gap). After 07-08, TestAvatarAssembled boots app.Handler, POSTs a JPEG to /_user/api/v1/avatar (200, has_avatar true, non-empty avatar_url), then POSTs avatar/remove (has_avatar false).
|
|
|
|
### 6. Organisation fields arrive through GetApiArrayEvent
|
|
expected: Login, fetch, and register user objects include organisation_id, organisation_role, must_change_password, and preferred_locale from fonoteka's GetApiArray listener. The user plugin does not import fonoteka.
|
|
result: pass
|
|
reported: |
|
|
Login/register payloads include organisation_id, organisation_role, must_change_password, preferred_locale. go list -deps on the user module does not import plugins/golem15/fonoteka.
|
|
|
|
### 7. Personal API tokens — mint, list, revoke, scope ceiling
|
|
expected: POST /_fonoteka/api/v1/tokens mints an inv_ secret shown once. GET lists tokens without the secret. DELETE is owner-scoped (missing or foreign id is the same 404). A scope outside read, write, and ai is 422 before insert. A read token on a write route is 403.
|
|
result: pass
|
|
reported: |
|
|
POST tokens with scopes=["admin"] is 422. Mint 201 returns inv_ secret once, no token_hash. GET list omits the secret. Foreign and missing DELETE are both 404 {"error":"Token not found"}. Revoke 200 {"data":{"revoked":true}}. InvScope write-without-read is 403 in TestInvScope; no write HTTP route is mounted yet.
|
|
|
|
### 8. Password-change lock with locale exemption
|
|
expected: A locked user gets 423 {"error":"Password change required","must_change_password":true} on genres and tokens. GET/PUT /_fonoteka/api/v1/me/locale still succeed. After change-password the lock is gone and genres succeed.
|
|
result: pass
|
|
reported: |
|
|
lock@uat.test login has must_change_password true. GET genres and GET tokens are 423 with the exact lock body. GET me/locale is 200. After change-password, GET genres is 200.
|
|
|
|
### 9. Locale persist and per-request resolution
|
|
expected: GET/PUT me/locale persist pl or en (empty preferred_locale is JSON null). Per-request locale is preferred_locale, then Accept-Language, then app.locale — including while the password lock is active.
|
|
result: pass
|
|
reported: |
|
|
GET me/locale is {"preferred_locale":null}. PUT en then pl persist. PUT de is 422. Locale GET succeeded while the lock was active (test 8). Invalid-login messages used the app locale (pl).
|
|
|
|
### 10. Register modes and production-safe errors
|
|
expected: auto/not-required returns {token,user}; user mode returns {message:'Activation email sent'} and sends activation mail with link and code; admin mode returns {}. With allow_registration=false or after the per-IP register limit, production returns {"error":"Internal server error"} at 500.
|
|
result: pass
|
|
reported: |
|
|
Curl auto-mode register returned token+user. TestRegisterUserModeMail, TestRegisterAdminMode, TestRegisterDisabled, and TestRegisterThrottle passed.
|
|
|
|
### 11. user:require-password-change console command
|
|
expected: user:require-password-change <email> sets must_change_password so the 423 lock is reachable without SQL.
|
|
result: pass
|
|
reported: |
|
|
TestRequirePasswordChange passed: the command sets must_change_password and rejects an unknown email.
|
|
|
|
### 12. User-API parity corpus is ported
|
|
expected: The 15 /_user/api/v1 routes and both nuxt-auth / nuxt-auth-lock flows replay green against Go and are status: ported. Corpus inventory is recorded 169, ported 22, pending 147, failing 0.
|
|
result: pass
|
|
reported: |
|
|
go test ./parity/ -run 'TestParityCorpus|TestUserAPINuxtFlows' passed. expectedPHPRoutes=169, expectedPortedRoutes=22.
|
|
|
|
## Summary
|
|
|
|
total: 12
|
|
passed: 12
|
|
issues: 0
|
|
pending: 0
|
|
skipped: 0
|
|
blocked: 0
|
|
|
|
## Gaps
|
|
|
|
- truth: "Avatar upload sniffs the first bytes, stores an attach.File, and fills has_avatar plus a 128px avatar_url; remove clears them"
|
|
status: resolved
|
|
reason: "07-08 published *blob.Bucket on serve and Handler; TestAvatarAssembled is 200 then remove."
|
|
severity: blocker
|
|
test: 5
|
|
debug_session: ".planning/debug/resolved/avatar-bucket-not-published.md"
|