docs(quick-260630-gbh): User Settings: Passwort ändern (nur non-LDAP) + Profilbild setzen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,5 +2,6 @@
|
||||
"enabledPlugins": {
|
||||
"claude-mem@thedotmack": true,
|
||||
"claude-code-setup@claude-plugins-official": true
|
||||
}
|
||||
},
|
||||
"enabledMcpjsonServers": ["playwright"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"playwright": {
|
||||
"command": "npx",
|
||||
"args": ["@playwright/mcp@latest"]
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-5
@@ -3,8 +3,8 @@ gsd_state_version: 1.0
|
||||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: executing
|
||||
stopped_at: context exhaustion at 76% (2026-06-28)
|
||||
last_updated: "2026-06-28T21:05:27.533Z"
|
||||
stopped_at: context exhaustion at 77% (2026-06-30)
|
||||
last_updated: "2026-06-30T06:40:23.631Z"
|
||||
last_activity: 2026-06-27 -- Phase 07 execution started
|
||||
progress:
|
||||
total_phases: 7
|
||||
@@ -28,7 +28,7 @@ See: .planning/PROJECT.md (updated 2026-06-18)
|
||||
Phase: 07 (dkv-fleet-module) — EXECUTING
|
||||
Plan: 2 of 6
|
||||
Status: Ready to execute
|
||||
Last activity: 2026-06-27 -- Phase 07 execution started
|
||||
Last activity: 2026-06-30 - Completed quick task 260630-gbh: User Settings: Passwort ändern (nur non-LDAP) + Profilbild setzen
|
||||
|
||||
Progress: [█████████░] 93%
|
||||
|
||||
@@ -128,6 +128,12 @@ None yet.
|
||||
|
||||
None yet.
|
||||
|
||||
### Quick Tasks Completed
|
||||
|
||||
| # | Description | Date | Commit | Directory |
|
||||
|---|-------------|------|--------|-----------|
|
||||
| 260630-gbh | User Settings: Passwort ändern (nur non-LDAP) + Profilbild setzen | 2026-06-30 | merge | [260630-gbh-user-settings-passwort-ndern-nur-non-lda](.planning/quick/260630-gbh-user-settings-passwort-ndern-nur-non-lda/) |
|
||||
|
||||
## Deferred Items
|
||||
|
||||
Items acknowledged and carried forward from previous milestone close:
|
||||
@@ -138,6 +144,6 @@ Items acknowledged and carried forward from previous milestone close:
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-06-28T21:05:27.525Z
|
||||
Stopped at: context exhaustion at 76% (2026-06-28)
|
||||
Last session: 2026-06-30T06:40:23.623Z
|
||||
Stopped at: context exhaustion at 77% (2026-06-30)
|
||||
Resume file: .planning/phases/07-dkv-fleet-module/07-06-PLAN.md
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
---
|
||||
phase: quick-260630-gbh
|
||||
plan: "01"
|
||||
subsystem: user-settings
|
||||
tags: [avatar, password-change, user-settings, auth, api]
|
||||
status: complete
|
||||
dependency_graph:
|
||||
requires: []
|
||||
provides: [avatar-api, enriched-auth-me, account-settings-page, header-avatar]
|
||||
affects: [auth-controller, user-controller, header, settings-sidebar]
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns: [FileInterceptor-memory-storage, self-scoped-routes-no-roles, same-origin-img-proxy]
|
||||
key_files:
|
||||
created:
|
||||
- apps/api/prisma/migrations/20260630095533_add_user_avatar/migration.sql
|
||||
- apps/web/src/components/settings/account-settings-form.tsx
|
||||
- apps/web/src/app/(portal)/settings/general/account/page.tsx
|
||||
modified:
|
||||
- apps/api/prisma/schema.prisma
|
||||
- apps/api/src/user/user.controller.ts
|
||||
- apps/api/src/auth/auth.service.ts
|
||||
- apps/api/src/auth/auth.controller.ts
|
||||
- apps/web/src/lib/auth-actions.ts
|
||||
- apps/web/src/components/settings/settings-sidebar.tsx
|
||||
- apps/web/src/lib/stores/auth-store.ts
|
||||
- apps/web/src/components/layout/header.tsx
|
||||
- apps/web/src/messages/de.json
|
||||
- apps/web/src/messages/en.json
|
||||
decisions:
|
||||
- "Avatar routes added to UserController (not AuthController) — RolesGuard returns true when no @Roles metadata, confirmed from guard source"
|
||||
- "Used Prisma v6.19.3 (installed version), not v7 specified in CLAUDE.md — package.json pins ^6.0.0"
|
||||
- "Plain <img> tag in header (not next/image) — /api-proxy is same-origin rewrite, no remote config needed"
|
||||
- "Web tsc --noEmit has pre-existing failures across all files (missing JSX/module type declarations); not introduced by this plan"
|
||||
metrics:
|
||||
duration: "~15 minutes"
|
||||
completed: "2026-06-30"
|
||||
tasks_completed: 3
|
||||
tasks_total: 3
|
||||
files_modified: 10
|
||||
files_created: 3
|
||||
---
|
||||
|
||||
# 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 migration `20260630095533_add_user_avatar`
|
||||
- `POST /users/me/avatar`: FileInterceptor with 2 MB limit; image/png, image/jpeg, image/webp allowlist (T-gbh-01); writes `user-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 up `avatarPath`, streams buffer with correct Content-Type and `Cache-Control: no-store`
|
||||
- `AuthService.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 calls `authService.getMe(user.id)` instead of returning raw JWT payload
|
||||
|
||||
**Frontend:**
|
||||
- `AuthUser` interface (both auth-actions.ts and auth-store.ts): added `isLocalUser?` and `hasAvatar?`
|
||||
- `uploadAvatarAction`: server action POSTing multipart to `/users/me/avatar` with session cookie; returns `{ success, error? }` without redirect
|
||||
- `AccountSettingsForm` (client component): avatar preview with initial fallback + file upload; password form gated on `isLocalUser === true`; LDAP managed notice when false
|
||||
- `/settings/general/account` page: renders `AccountSettingsForm`
|
||||
- `SettingsSidebar`: 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">` when `hasAvatar=true` with `onError` fallback 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 `04b37f54` but HEAD was `dcba4b9` (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 |
|
||||
Reference in New Issue
Block a user