Phase 10: Core Plugins - 4 plans in 2 waves - Wave 1: 10-01 (User auth), 10-03 (Blog posts) - parallel - Wave 2: 10-02 (User profiles), 10-04 (Blog categories/tags) - sequential - Ready for execution
334 lines
14 KiB
Markdown
334 lines
14 KiB
Markdown
---
|
|
phase: 10-core-plugins
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- plugins/golem15/user/plugin.yaml
|
|
- plugins/golem15/user/Plugin.scala
|
|
- plugins/golem15/user/models/FrontendUser.scala
|
|
- plugins/golem15/user/models/UserGroup.scala
|
|
- plugins/golem15/user/models/UserThrottle.scala
|
|
- plugins/golem15/user/models/UserSettings.scala
|
|
- plugins/golem15/user/services/FrontendUserService.scala
|
|
- plugins/golem15/user/services/FrontendAuthService.scala
|
|
- plugins/golem15/user/services/ThrottleService.scala
|
|
- plugins/golem15/user/components/Session.scala
|
|
- plugins/golem15/user/components/Account.scala
|
|
- plugins/golem15/user/resources/db/migration/V10_1_1__user_plugin.sql
|
|
- plugins/golem15/user/resources/views/account/login.peb
|
|
- plugins/golem15/user/resources/views/account/register.peb
|
|
autonomous: true
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Frontend visitor can register with email, password, name, surname"
|
|
- "Registered user receives activation email if activation mode is user"
|
|
- "User can log in with email and password"
|
|
- "User can log out and session is terminated"
|
|
- "Session component restricts page access based on security mode"
|
|
- "Login throttling prevents brute force attacks"
|
|
artifacts:
|
|
- path: "plugins/golem15/user/models/FrontendUser.scala"
|
|
provides: "Frontend user domain model with validation"
|
|
contains: "case class FrontendUser"
|
|
- path: "plugins/golem15/user/services/FrontendAuthService.scala"
|
|
provides: "Authentication logic with JWT session tokens"
|
|
exports: ["FrontendAuthService", "login", "logout", "getCurrentUser"]
|
|
- path: "plugins/golem15/user/components/Account.scala"
|
|
provides: "Registration and login HTMX handlers"
|
|
exports: ["AccountComponent", "onSignin", "onRegister"]
|
|
- path: "plugins/golem15/user/components/Session.scala"
|
|
provides: "Page access control component"
|
|
exports: ["SessionComponent", "onLogout"]
|
|
- path: "plugins/golem15/user/resources/db/migration/V10_1_1__user_plugin.sql"
|
|
provides: "Database schema for frontend users"
|
|
contains: "CREATE TABLE frontend_users"
|
|
key_links:
|
|
- from: "plugins/golem15/user/components/Account.scala"
|
|
to: "FrontendAuthService"
|
|
via: "ZIO service injection"
|
|
pattern: "ZIO\\.service\\[FrontendAuthService\\]"
|
|
- from: "plugins/golem15/user/services/FrontendAuthService.scala"
|
|
to: "FrontendUserRepository"
|
|
via: "repository lookup"
|
|
pattern: "userRepo\\.findByEmail"
|
|
- from: "plugins/golem15/user/components/Session.scala"
|
|
to: "pageContext"
|
|
via: "user injection"
|
|
pattern: "pageContext\\.set\\(\"user\""
|
|
---
|
|
|
|
<objective>
|
|
Create the User plugin foundation with registration and authentication functionality.
|
|
|
|
Purpose: Establish frontend user management as the first core plugin, demonstrating the complete plugin pattern with models, services, components, and migrations.
|
|
|
|
Output: Working User plugin with:
|
|
- FrontendUser model with validation rules
|
|
- Registration flow with configurable activation
|
|
- Login/logout with JWT cookie sessions
|
|
- Session component for page access control
|
|
- Login throttling for security
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/home/jin/.claude/get-shit-done/workflows/execute-plan.md
|
|
@/home/jin/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<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.md
|
|
|
|
# Reference patterns from prior phases
|
|
# Phase 2: Plugin manifest and lifecycle patterns
|
|
# Phase 3: Component system with HTMX handlers
|
|
# Phase 6: Authentication patterns (Argon2id, JWT)
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: User plugin models and database schema</name>
|
|
<files>
|
|
plugins/golem15/user/plugin.yaml
|
|
plugins/golem15/user/Plugin.scala
|
|
plugins/golem15/user/models/FrontendUser.scala
|
|
plugins/golem15/user/models/UserGroup.scala
|
|
plugins/golem15/user/models/UserThrottle.scala
|
|
plugins/golem15/user/models/UserSettings.scala
|
|
plugins/golem15/user/resources/db/migration/V10_1_1__user_plugin.sql
|
|
</files>
|
|
<action>
|
|
Create User plugin structure following Phase 2 plugin patterns:
|
|
|
|
**plugin.yaml:**
|
|
- name: Golem15.User
|
|
- description: Frontend user authentication and management
|
|
- author: Golem15
|
|
- version: 1.0.0
|
|
- require: [] (no plugin dependencies)
|
|
|
|
**Plugin.scala:**
|
|
- Extend SummerPlugin trait
|
|
- Register components: Session, Account (ResetPassword in plan 02)
|
|
- Register settings model
|
|
- Boot method for any initialization
|
|
|
|
**FrontendUser.scala:**
|
|
- Case class with fields from research Pattern 1:
|
|
id, email, passwordHash, name, surname, username (Option), avatarPath (Option),
|
|
isActivated, activationCode, activatedAt, persistCode, resetPasswordCode,
|
|
lastLogin, lastSeen, createdIpAddress, lastIpAddress, isGuest, isSuperuser,
|
|
deletedAt (soft delete), createdAt, updatedAt
|
|
- Validation rules object: email (required, unique, 6-255), password (required, 8-255),
|
|
name (required, 2-100), surname (required, 2-100)
|
|
- Quill table mapping to `frontend_users`
|
|
|
|
**UserGroup.scala:**
|
|
- Case class: id, name, code (unique), description, createdAt, updatedAt
|
|
- Quill table mapping to `user_groups`
|
|
- UsersGroups pivot case class for many-to-many
|
|
|
|
**UserThrottle.scala:**
|
|
- Case class: id, userId (Option), ipAddress (Option), attempts, lastAttemptAt,
|
|
isSuspended, suspendedAt, isBanned, bannedAt
|
|
- Quill table mapping to `user_throttle`
|
|
|
|
**UserSettings.scala:**
|
|
- Sealed traits: ActivateMode (Auto, User, Admin), RememberLogin (Always, Never, Ask)
|
|
- Case class UserPluginSettings with fields from research Pattern 2:
|
|
allowRegistration, requireActivation, activateMode, useThrottle,
|
|
useRegisterThrottle, blockPersistence, rememberLogin, minPasswordLength
|
|
- Default values matching WinterCMS
|
|
|
|
**V10_1_1__user_plugin.sql:**
|
|
- CREATE TABLE frontend_users (all fields with proper types, indexes)
|
|
- CREATE TABLE user_groups (id, name, code unique, description, timestamps)
|
|
- CREATE TABLE users_groups (user_id, group_id, composite PK)
|
|
- CREATE TABLE user_throttle (id, user_id nullable, ip_address inet, attempts, timestamps, ban flags)
|
|
- Indexes on email, username, persist_code, throttle lookups
|
|
</action>
|
|
<verify>
|
|
./mill summercms.compile succeeds with new models
|
|
SQL migration syntax valid (no syntax errors in IDE)
|
|
</verify>
|
|
<done>
|
|
FrontendUser, UserGroup, UserThrottle, UserSettings models exist with Quill mappings.
|
|
Migration creates all required tables with proper indexes.
|
|
</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Authentication and user services</name>
|
|
<files>
|
|
plugins/golem15/user/repositories/FrontendUserRepository.scala
|
|
plugins/golem15/user/services/FrontendUserService.scala
|
|
plugins/golem15/user/services/FrontendAuthService.scala
|
|
plugins/golem15/user/services/ThrottleService.scala
|
|
</files>
|
|
<action>
|
|
Create service layer for user operations:
|
|
|
|
**FrontendUserRepository.scala:**
|
|
- Trait with ZIO effects following Phase 1 repository pattern
|
|
- Methods: findById, findByEmail, findByPersistCode, create, update, delete (soft)
|
|
- Live implementation using QuillContext
|
|
- Error handling with RepositoryError ADT
|
|
|
|
**FrontendUserService.scala:**
|
|
- Trait following research Pattern 1
|
|
- Methods:
|
|
- findByEmail(email): Option[FrontendUser]
|
|
- findById(id): Option[FrontendUser]
|
|
- register(data: RegistrationData): FrontendUser (handles activation code generation)
|
|
- activate(userId, code): FrontendUser
|
|
- updateProfile(userId, data: ProfileUpdate): FrontendUser
|
|
- touchLastSeen(userId): Unit
|
|
- touchIpAddress(userId, ip): Unit
|
|
- RegistrationData case class: email, password, name, surname, ipAddress
|
|
- ProfileUpdate case class: name, surname, newPassword (Option)
|
|
- Use Password4j for hashing (Argon2id, matching Phase 6 patterns)
|
|
- Live implementation with ZLayer
|
|
|
|
**FrontendAuthService.scala:**
|
|
- Trait following research Pattern 10
|
|
- Methods:
|
|
- login(user, remember): String (returns JWT token)
|
|
- logout: Unit (clears session, rotates persistCode)
|
|
- getCurrentUser: Option[FrontendUser]
|
|
- isAuthenticated: Boolean
|
|
- verifyPassword(userId, password): Boolean
|
|
- validateAndSetSession(token): Unit (for middleware)
|
|
- JWT token generation using jwt-scala (15min session, 7day remember)
|
|
- Cookie configuration helper (httpOnly, secure in prod, SameSite Lax)
|
|
- Session state via Ref[Option[FrontendUser]]
|
|
- Live implementation with ZLayer
|
|
|
|
**ThrottleService.scala:**
|
|
- Trait for login/registration throttling
|
|
- Methods:
|
|
- isThrottled(ip): Boolean
|
|
- recordAttempt(ip, success): Unit
|
|
- clearAttempts(ip): Unit
|
|
- isBanned(ip): Boolean
|
|
- Configurable thresholds (5 attempts, 15 min lockout)
|
|
- Live implementation with ZLayer
|
|
</action>
|
|
<verify>
|
|
./mill summercms.compile succeeds
|
|
All service traits have Live implementations
|
|
ZLayers compose correctly (no missing dependencies)
|
|
</verify>
|
|
<done>
|
|
FrontendUserRepository provides data access.
|
|
FrontendUserService handles registration with Argon2id password hashing.
|
|
FrontendAuthService manages JWT sessions with cookie helpers.
|
|
ThrottleService prevents brute force attacks.
|
|
</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 3: Session and Account components with templates</name>
|
|
<files>
|
|
plugins/golem15/user/components/Session.scala
|
|
plugins/golem15/user/components/Account.scala
|
|
plugins/golem15/user/resources/views/session/default.peb
|
|
plugins/golem15/user/resources/views/account/login.peb
|
|
plugins/golem15/user/resources/views/account/register.peb
|
|
plugins/golem15/user/resources/views/account/success.peb
|
|
</files>
|
|
<action>
|
|
Create frontend components following Phase 3 component patterns:
|
|
|
|
**Session.scala (research Pattern 3):**
|
|
- Extend SummerComponent trait
|
|
- ComponentDetails: name "Session", description "User session and access control"
|
|
- Property schema:
|
|
- security: Dropdown (all/user/guest), default "all"
|
|
- allowedUserGroups: Set, optional group filtering
|
|
- redirect: Dropdown, page to redirect unauthorized users
|
|
- onRun lifecycle:
|
|
- Check security mode against current auth state
|
|
- If unauthorized and redirect set, fail with ComponentRedirect
|
|
- If unauthorized and no redirect, fail with AccessDenied
|
|
- Inject "user" into pageContext (null if guest)
|
|
- Touch lastSeen for authenticated users
|
|
- HTMX handler onLogout:
|
|
- Call authService.logout
|
|
- Return HtmxResponse with HX-Redirect to configured page
|
|
- Trigger "userLoggedOut" event
|
|
|
|
**Account.scala (research Pattern 4):**
|
|
- Extend SummerComponent trait
|
|
- ComponentDetails: name "Account", description "Registration, login, profile"
|
|
- Property schema:
|
|
- redirect: Dropdown, page after login/register
|
|
- paramCode: String, activation code URL param name, default "code"
|
|
- requirePassword: Checkbox, require current password for profile update
|
|
- HTMX handlers:
|
|
- onSignin: Parse login/password from form, validate length, call authService.authenticate,
|
|
handle errors (InvalidCredentials, AccountBanned, NotActivated) with generic message,
|
|
record IP, return HX-Redirect on success
|
|
- onRegister: Check allowRegistration setting, check throttle, parse form data,
|
|
call userService.register, send activation email if mode is User,
|
|
auto-login if auto-activated, return HX-Redirect on success
|
|
- onActivate: Get code from URL param, call userService.activate, auto-login, redirect
|
|
|
|
**Templates (Pebble):**
|
|
- session/default.peb: Empty by default (component injects user context only)
|
|
- account/login.peb:
|
|
- Form with hx-post to onSignin handler
|
|
- Email input, password input, remember checkbox (if settings allow)
|
|
- Error display area with hx-swap="innerHTML"
|
|
- CSRF token hidden field
|
|
- account/register.peb:
|
|
- Form with hx-post to onRegister handler
|
|
- Email, password, password_confirmation, name, surname inputs
|
|
- Error display area
|
|
- CSRF token hidden field
|
|
- account/success.peb:
|
|
- Success message partial for HTMX responses
|
|
</action>
|
|
<verify>
|
|
./mill summercms.compile succeeds
|
|
Templates have valid Pebble syntax (no unclosed tags)
|
|
Components registered in Plugin.scala boot method
|
|
</verify>
|
|
<done>
|
|
Session component controls page access with security modes.
|
|
Account component handles login and registration via HTMX.
|
|
Templates render forms with CSRF protection and error handling.
|
|
User plugin fully functional for registration and authentication flows.
|
|
</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
After all tasks complete:
|
|
1. User plugin compiles: `./mill summercms.compile`
|
|
2. Migration valid: Check SQL syntax for frontend_users, user_groups, users_groups, user_throttle
|
|
3. Component registration: Plugin.scala registers Session and Account components
|
|
4. Service wiring: All ZLayers compose without missing dependencies
|
|
5. Template syntax: Pebble templates parse without errors
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Frontend visitor can submit registration form (model, service, component exist)
|
|
- Registration creates FrontendUser with hashed password
|
|
- Login authenticates user and sets JWT cookie
|
|
- Session component restricts page access based on security mode
|
|
- Logout clears session and rotates persist code
|
|
- Throttle service tracks failed login attempts
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/10-core-plugins/10-01-SUMMARY.md`
|
|
</output>
|