--- 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\"" --- 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 @/home/jin/.claude/get-shit-done/workflows/execute-plan.md @/home/jin/.claude/get-shit-done/templates/summary.md @.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) Task 1: User plugin models and database schema 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 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 ./mill summercms.compile succeeds with new models SQL migration syntax valid (no syntax errors in IDE) FrontendUser, UserGroup, UserThrottle, UserSettings models exist with Quill mappings. Migration creates all required tables with proper indexes. Task 2: Authentication and user services plugins/golem15/user/repositories/FrontendUserRepository.scala plugins/golem15/user/services/FrontendUserService.scala plugins/golem15/user/services/FrontendAuthService.scala plugins/golem15/user/services/ThrottleService.scala 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 ./mill summercms.compile succeeds All service traits have Live implementations ZLayers compose correctly (no missing dependencies) FrontendUserRepository provides data access. FrontendUserService handles registration with Argon2id password hashing. FrontendAuthService manages JWT sessions with cookie helpers. ThrottleService prevents brute force attacks. Task 3: Session and Account components with templates 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 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 ./mill summercms.compile succeeds Templates have valid Pebble syntax (no unclosed tags) Components registered in Plugin.scala boot method 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. 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 - 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 After completion, create `.planning/phases/10-core-plugins/10-01-SUMMARY.md`