5.8 KiB
5.8 KiB
phase, plan, subsystem, tags, status, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | status | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260630-gbh | 01 | user-settings |
|
complete |
|
|
|
|
|
Phase quick-260630-gbh Plan 01: User Settings — Avatar + Password Change Summary
One-liner: Profile avatar upload/serve with per-user file storage and conditional password-change form gated on local-vs-LDAP user status.
Tasks Completed
| Task | Name | Commit | Files |
|---|---|---|---|
| 1 | Avatar persistence + API endpoints + enrich /auth/me | 0fba45d |
schema.prisma, migration, user.controller.ts, auth.service.ts, auth.controller.ts |
| 2 | Settings Konto page — conditional password form + avatar upload | 4482a8c |
auth-actions.ts, account-settings-form.tsx, settings-sidebar.tsx, account/page.tsx, de.json, en.json |
| 3 | Header avatar display with initial fallback | 5ae498f |
auth-store.ts, header.tsx |
What Was Built
Backend:
User.avatarPath String?column added via Prisma migration20260630095533_add_user_avatarPOST /users/me/avatar: FileInterceptor with 2 MB limit; image/png, image/jpeg, image/webp allowlist (T-gbh-01); writesuser-files/avatars/{userId}.{ext}derived from MIME type (T-gbh-04); removes stale files with other extensions; userId from@CurrentUser()only (T-gbh-02); returns{ success: true }GET /users/me/avatar: looks upavatarPath, streams buffer with correct Content-Type andCache-Control: no-storeAuthService.getMe(userId): loads user, returns public fields +isLocalUser(passwordHash set && ldapDn null) +hasAvatar(avatarPath not null); never serialises passwordHash or ldapDn (T-gbh-03)GET /auth/me: now callsauthService.getMe(user.id)instead of returning raw JWT payload
Frontend:
AuthUserinterface (both auth-actions.ts and auth-store.ts): addedisLocalUser?andhasAvatar?uploadAvatarAction: server action POSTing multipart to/users/me/avatarwith session cookie; returns{ success, error? }without redirectAccountSettingsForm(client component): avatar preview with initial fallback + file upload; password form gated onisLocalUser === true; LDAP managed notice when false/settings/general/accountpage: rendersAccountSettingsFormSettingsSidebar: Konto link added above SMTP link- i18n:
categoryAccount+account.*keys in both de.json and en.json
Header:
- Avatar button: shows
<img src="/api-proxy/users/me/avatar">whenhasAvatar=truewithonErrorfallback to initial letter
Deviations from Plan
Pre-existing condition (no action required)
[Pre-existing] Web tsc --noEmit fails across all source files
- All JSX files fail with
TS7026: JSX element implicitly has type 'any'and module resolution errors (next/link,next-intl,zustand, etc.) - This affects the entire web package, not just files in this plan
- Out of scope per deviation rule scope boundary; logged here for awareness
Auto-decisions
[Rule 2 - Correctness] Auth.me enrichment placed in AuthService not inline
- Kept DB access logic in AuthService (consistent with existing pattern) rather than inlining in controller
HEAD mismatch at startup
- Expected base
04b37f54but HEAD wasdcba4b9(3 DKV commits landed on main after plan dispatch) - Proceeded: worktree branch is
worktree-agent-a51974fb00fe6b744(correct), DKV work is orthogonal to this plan's scope
Threat Surface Scan
All T-gbh-01 through T-gbh-05 mitigations from the plan's threat model are implemented:
- T-gbh-01: 2 MB FileInterceptor limit + MIME allowlist in user.controller.ts
- T-gbh-02:
@CurrentUser()only for userId — never from request body/params - T-gbh-03:
getMe()strips passwordHash/ldapDn/avatarPath before return - T-gbh-04: filename =
{userId}.{ext}from fixed MIME map, no user-controlled path component - T-gbh-05: GET /users/me/avatar serves only the authenticated user's own file
No new threat surface was introduced beyond what the plan's threat model already covers.
Self-Check: PASSED
| Check | Result |
|---|---|
| migration.sql exists | FOUND |
| user.controller.ts | FOUND |
| auth.service.ts | FOUND |
| account-settings-form.tsx | FOUND |
| account/page.tsx | FOUND |
commit 0fba45d (Task 1) |
FOUND |
commit 4482a8c (Task 2) |
FOUND |
commit 5ae498f (Task 3) |
FOUND |