- Plan 10-02: Added explicit UserMailService wiring in Account.scala onRegister handler for activation emails when activateMode == User - Plan 10-04: Split into backend-only (models, services, admin controller) reducing from 16 to 15 files, estimated context ~50% - Plan 10-05: New plan for frontend components (Posts, Post, Categories, RelatedPosts), Wave 3 depends on 10-04 - Updated ROADMAP.md to reflect 5 plans for Phase 10
11 KiB
11 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10-core-plugins | 02 | execute | 2 |
|
|
true |
|
Purpose: Enable users to manage their accounts and recover access, completing the frontend user experience.
Output: Working profile and reset features:
- Profile edit form with name/surname/password change
- Avatar upload using media library
- Password reset request and completion flow
- Email templates for activation and reset
<execution_context> @/home/jin/.claude/get-shit-done/workflows/execute-plan.md @/home/jin/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/10-core-plugins/10-CONTEXT.md @.planning/phases/10-core-plugins/10-RESEARCH.mdPrior plan in this phase
@.planning/phases/10-core-plugins/10-01-SUMMARY.md
Reference: Phase 9 media library for avatar upload
Task 1: Profile management with avatar upload plugins/golem15/user/components/Account.scala (modify) plugins/golem15/user/services/FrontendUserService.scala (modify) plugins/golem15/user/resources/views/account/profile.peb plugins/golem15/user/resources/views/account/avatar.peb Add profile management to Account component:**Account.scala additions:**
- Add onUpdate HTMX handler (research Pattern 4):
- Get current user from authService, fail if not authenticated
- If requirePassword property set, validate current password
- Parse ProfileUpdate from form (name, surname, newPassword optional)
- Validate new password length if provided (min from settings)
- Call userService.updateProfile
- If password changed, re-authenticate user (new JWT)
- Return success partial with "Profile updated" message
- Add onUploadAvatar HTMX handler:
- Get current user, fail if not authenticated
- Get uploaded file from multipart form
- Validate file type (image/jpeg, image/png, image/gif, image/webp)
- Validate file size (max 2MB from settings or default)
- Call userService.updateAvatar
- Return updated avatar partial with new image
**FrontendUserService.scala additions:**
- Add updateAvatar(userId, file: UploadedFile): FrontendUser
- Use MediaLibrary from Phase 9 to store file
- Generate path: users/{userId}/avatar.{ext}
- Update user.avatarPath
- Delete old avatar if exists
- Modify updateProfile to handle password change:
- If newPassword provided, hash with Argon2id
- Update passwordHash field
- Rotate persistCode to invalidate other sessions
**profile.peb template:**
- Form with hx-post to onUpdate handler, hx-target for response
- Name input (pre-filled from user)
- Surname input (pre-filled from user)
- Current password input (if requirePassword property)
- New password input (optional)
- Confirm new password input
- Submit button
- Error/success display area
- CSRF token
**avatar.peb template:**
- Current avatar display (or placeholder)
- Form with hx-post to onUploadAvatar, hx-encoding="multipart/form-data"
- File input with accept="image/*"
- Upload button
- Preview area that updates on success
./mill summercms.compile succeeds
Account component has onUpdate and onUploadAvatar handlers
Templates have valid Pebble syntax
Users can edit profile (name, surname, password).
Password change requires current password if configured.
Avatar upload stores image via media library.
Templates render forms with proper HTMX attributes.
Task 2: Password reset flow and email service
plugins/golem15/user/components/ResetPassword.scala
plugins/golem15/user/components/Account.scala (modify)
plugins/golem15/user/services/UserMailService.scala
plugins/golem15/user/services/FrontendUserService.scala (modify)
plugins/golem15/user/resources/views/resetpassword/request.peb
plugins/golem15/user/resources/views/resetpassword/reset.peb
plugins/golem15/user/resources/views/mail/activation.peb
plugins/golem15/user/resources/views/mail/reset.peb
Create password reset component and email service:
**UserMailService.scala:**
- Trait with ZIO effects:
- sendActivationEmail(user: FrontendUser, code: String): IO[MailError, Unit]
- sendResetEmail(user: FrontendUser, code: String): IO[MailError, Unit]
- Live implementation using ZIO email service (or stub for now):
- Load Pebble template for email body
- Include activation/reset URL with code
- Send via configured SMTP or email service
- MailError ADT: SendFailed, TemplateError, ConfigurationError
**Account.scala modification (CRITICAL - wire activation email):**
- Modify the onRegister handler created in Plan 01 to integrate UserMailService:
- Inject UserMailService dependency into Account component
- After successful userService.register call, check UserSettings.activateMode
- If activateMode == ActivateMode.User, call mailService.sendActivationEmail(user, activationCode)
- This completes the key_link from Account.scala to UserMailService
**FrontendUserService.scala additions:**
- requestPasswordReset(email: String): Unit
- Find user by email (don't reveal if not found - timing attack)
- Generate secure random reset code (32 bytes hex)
- Set resetPasswordCode on user with expiry tracking
- Call mailService.sendResetEmail
- Always succeed (don't reveal user existence)
- resetPassword(code: String, newPassword: String): FrontendUser
- Find user by resetPasswordCode
- Validate code not expired (24 hours)
- Hash new password with Argon2id
- Clear resetPasswordCode
- Rotate persistCode (invalidate other sessions)
- Return updated user
**ResetPassword.scala component:**
- Extend SummerComponent trait
- ComponentDetails: name "ResetPassword", description "Password recovery flow"
- Property schema:
- paramCode: String, URL parameter for reset code, default "code"
- redirect: Dropdown, page after successful reset
- HTMX handlers:
- onRequest: Parse email from form, call userService.requestPasswordReset,
always show success message (no email enumeration)
- onReset: Get code from URL param, parse new password + confirmation,
validate password length and match, call userService.resetPassword,
auto-login user, redirect to configured page
**Templates:**
- resetpassword/request.peb:
- Form with hx-post to onRequest
- Email input
- Submit button "Send Reset Link"
- Success message: "If an account exists, you will receive an email"
- resetpassword/reset.peb:
- Form with hx-post to onReset
- Hidden input with reset code from URL
- New password input
- Confirm password input
- Submit button "Reset Password"
- Error display for invalid/expired code
- mail/activation.peb (Pebble email template):
- Subject: "Activate your account"
- Body: Welcome message, activation link with code
- Plain text fallback
- mail/reset.peb (Pebble email template):
- Subject: "Reset your password"
- Body: Reset link with code, expiry notice (24 hours)
- Plain text fallback
**Register ResetPassword component in Plugin.scala**
./mill summercms.compile succeeds
ResetPassword component registered in Plugin.scala
Account.scala onRegister calls mailService.sendActivationEmail when activateMode == User
Email templates have valid Pebble syntax
Reset code generation uses SecureRandom
Password reset request sends email (or stubs if no SMTP).
Reset link with token allows setting new password.
Activation email sent during registration when activateMode is User.
No email enumeration possible (generic success messages).
After all tasks complete:
1. User plugin compiles: `./mill summercms.compile`
2. Account component has all handlers: onSignin, onRegister, onActivate, onUpdate, onUploadAvatar
3. Account.scala onRegister integrates UserMailService for activation emails
4. ResetPassword component handles: onRequest, onReset
5. Email service can send activation and reset emails
6. Templates render valid forms with HTMX
<success_criteria>
- Logged-in user can view profile form pre-filled with their data
- Profile update saves name/surname changes
- Password change requires current password confirmation
- Avatar upload stores image and updates user record
- Registration with activateMode=User triggers activation email via UserMailService
- Password reset request always shows success (no enumeration)
- Reset link with valid code allows new password entry
- User automatically logged in after successful reset </success_criteria>