plutonium 0.50.0 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +85 -102
  3. data/.claude/skills/plutonium-app/SKILL.md +572 -0
  4. data/.claude/skills/plutonium-auth/SKILL.md +163 -300
  5. data/.claude/skills/plutonium-behavior/SKILL.md +838 -0
  6. data/.claude/skills/plutonium-resource/SKILL.md +1176 -0
  7. data/.claude/skills/plutonium-tenancy/SKILL.md +655 -0
  8. data/.claude/skills/plutonium-testing/SKILL.md +6 -5
  9. data/.claude/skills/plutonium-ui/SKILL.md +900 -0
  10. data/CHANGELOG.md +27 -2
  11. data/Rakefile +2 -1
  12. data/app/assets/plutonium.css +1 -11
  13. data/app/assets/plutonium.js +1009 -1214
  14. data/app/assets/plutonium.js.map +3 -3
  15. data/app/assets/plutonium.min.js +52 -51
  16. data/app/assets/plutonium.min.js.map +3 -3
  17. data/docs/.vitepress/config.ts +37 -27
  18. data/docs/getting-started/index.md +22 -29
  19. data/docs/getting-started/installation.md +37 -80
  20. data/docs/getting-started/tutorial/index.md +4 -5
  21. data/docs/guides/adding-resources.md +66 -377
  22. data/docs/guides/authentication.md +94 -463
  23. data/docs/guides/authorization.md +124 -370
  24. data/docs/guides/creating-packages.md +94 -296
  25. data/docs/guides/custom-actions.md +121 -441
  26. data/docs/guides/index.md +22 -42
  27. data/docs/guides/multi-tenancy.md +116 -187
  28. data/docs/guides/nested-resources.md +103 -431
  29. data/docs/guides/search-filtering.md +123 -240
  30. data/docs/guides/testing.md +5 -4
  31. data/docs/guides/theming.md +157 -407
  32. data/docs/guides/troubleshooting.md +5 -3
  33. data/docs/guides/user-invites.md +106 -425
  34. data/docs/guides/user-profile.md +76 -243
  35. data/docs/index.md +1 -1
  36. data/docs/reference/app/generators.md +517 -0
  37. data/docs/reference/app/index.md +158 -0
  38. data/docs/reference/app/packages.md +146 -0
  39. data/docs/reference/app/portals.md +377 -0
  40. data/docs/reference/auth/accounts.md +230 -0
  41. data/docs/reference/auth/index.md +88 -0
  42. data/docs/reference/auth/profile.md +185 -0
  43. data/docs/reference/behavior/controllers.md +395 -0
  44. data/docs/reference/behavior/index.md +22 -0
  45. data/docs/reference/behavior/interactions.md +341 -0
  46. data/docs/reference/behavior/policies.md +417 -0
  47. data/docs/reference/index.md +56 -49
  48. data/docs/reference/resource/actions.md +423 -0
  49. data/docs/reference/resource/definition.md +508 -0
  50. data/docs/reference/resource/index.md +50 -0
  51. data/docs/reference/resource/model.md +348 -0
  52. data/docs/reference/resource/query.md +305 -0
  53. data/docs/reference/tenancy/entity-scoping.md +361 -0
  54. data/docs/reference/tenancy/index.md +36 -0
  55. data/docs/reference/tenancy/invites.md +393 -0
  56. data/docs/reference/tenancy/nested-resources.md +267 -0
  57. data/docs/reference/testing/index.md +287 -0
  58. data/docs/reference/ui/assets.md +400 -0
  59. data/docs/reference/ui/components.md +165 -0
  60. data/docs/reference/ui/displays.md +104 -0
  61. data/docs/reference/ui/forms.md +284 -0
  62. data/docs/reference/ui/index.md +30 -0
  63. data/docs/reference/ui/layouts.md +106 -0
  64. data/docs/reference/ui/pages.md +189 -0
  65. data/docs/reference/ui/tables.md +117 -0
  66. data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
  67. data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
  68. data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
  69. data/gemfiles/rails_7.gemfile.lock +1 -1
  70. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  71. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  72. data/lib/generators/pu/core/update/update_generator.rb +0 -20
  73. data/lib/generators/pu/invites/install_generator.rb +1 -0
  74. data/lib/plutonium/definition/base.rb +1 -1
  75. data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
  76. data/lib/plutonium/helpers/turbo_helper.rb +11 -0
  77. data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
  78. data/lib/plutonium/resource/controller.rb +1 -0
  79. data/lib/plutonium/resource/controllers/crud_actions.rb +19 -1
  80. data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
  81. data/lib/plutonium/resource/policy.rb +7 -0
  82. data/lib/plutonium/routing/mapper_extensions.rb +15 -0
  83. data/lib/plutonium/ui/component/methods.rb +4 -0
  84. data/lib/plutonium/ui/form/base.rb +6 -2
  85. data/lib/plutonium/ui/form/components/json.rb +58 -0
  86. data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
  87. data/lib/plutonium/ui/form/components/secure_association.rb +98 -22
  88. data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
  89. data/lib/plutonium/ui/form/resource.rb +0 -4
  90. data/lib/plutonium/ui/grid/resource.rb +1 -1
  91. data/lib/plutonium/ui/layout/base.rb +1 -0
  92. data/lib/plutonium/ui/page/base.rb +0 -7
  93. data/lib/plutonium/ui/page/index.rb +4 -4
  94. data/lib/plutonium/ui/table/resource.rb +1 -1
  95. data/lib/plutonium/version.rb +1 -1
  96. data/lib/plutonium.rb +8 -0
  97. data/lib/tasks/release.rake +15 -1
  98. data/package.json +10 -10
  99. data/src/css/slim_select.css +4 -0
  100. data/src/js/controllers/slim_select_controller.js +61 -0
  101. data/src/js/turbo/turbo_actions.js +33 -0
  102. data/yarn.lock +553 -543
  103. metadata +44 -33
  104. data/.claude/skills/plutonium-assets/SKILL.md +0 -512
  105. data/.claude/skills/plutonium-controller/SKILL.md +0 -396
  106. data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
  107. data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
  108. data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
  109. data/.claude/skills/plutonium-forms/SKILL.md +0 -465
  110. data/.claude/skills/plutonium-installation/SKILL.md +0 -331
  111. data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
  112. data/.claude/skills/plutonium-invites/SKILL.md +0 -408
  113. data/.claude/skills/plutonium-model/SKILL.md +0 -440
  114. data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
  115. data/.claude/skills/plutonium-package/SKILL.md +0 -198
  116. data/.claude/skills/plutonium-policy/SKILL.md +0 -456
  117. data/.claude/skills/plutonium-portal/SKILL.md +0 -410
  118. data/.claude/skills/plutonium-views/SKILL.md +0 -651
  119. data/docs/reference/assets/index.md +0 -496
  120. data/docs/reference/controller/index.md +0 -412
  121. data/docs/reference/definition/actions.md +0 -462
  122. data/docs/reference/definition/fields.md +0 -383
  123. data/docs/reference/definition/index.md +0 -326
  124. data/docs/reference/definition/query.md +0 -351
  125. data/docs/reference/generators/index.md +0 -648
  126. data/docs/reference/interaction/index.md +0 -449
  127. data/docs/reference/model/features.md +0 -248
  128. data/docs/reference/model/index.md +0 -218
  129. data/docs/reference/policy/index.md +0 -456
  130. data/docs/reference/portal/index.md +0 -379
  131. data/docs/reference/views/forms.md +0 -411
  132. data/docs/reference/views/index.md +0 -544
@@ -1,107 +1,77 @@
1
1
  ---
2
2
  name: plutonium-auth
3
- description: Use BEFORE configuring Rodauth, account types, login flows, or building a profile / account settings page. Also when including Plutonium::Auth::Rodauth in a controller. Covers authentication and user profile pages.
3
+ description: Use BEFORE installing Rodauth, configuring account types, building login/password flows, or wiring a profile / account-settings page. Covers the full auth surface — Rodauth installation, accounts, admin accounts, SaaS setup, profile resource, security section.
4
4
  ---
5
5
 
6
- # Plutonium Authentication (Rodauth + Profile)
6
+ # Plutonium Auth Rodauth + Profile
7
7
 
8
- ## 🚨 Critical (read first)
9
- - **Use the generators.** `pu:rodauth:install`, `pu:rodauth:account`, `pu:rodauth:admin`, `pu:saas:setup`, `pu:profile:install`, `pu:profile:conn` — never hand-write Rodauth plugin files, account models, or profile resources.
10
- - **Role index 0 is the most privileged** (`owner`, `super_admin`). Invite interactions default new invitees to index 1. `pu:saas:setup` always prepends `owner` — don't include it in `--roles`.
11
- - **`pu:saas:setup` is a meta-generator** that also runs `pu:saas:portal`, `pu:profile:setup`, `pu:saas:welcome`, and `pu:invites:install`. Don't re-run them manually.
12
- - **Profile association is always `:profile`** regardless of the model class — `current_user.profile`, `build_profile`, etc.
13
- - **Related skills:** `plutonium-installation` (initial setup), `plutonium-portal` (portal auth), `plutonium-invites` (multi-tenant invitations), `plutonium-entity-scoping` (tenant scoping).
8
+ Plutonium integrates [Rodauth](http://rodauth.jeremyevans.net/) via [rodauth-rails](https://github.com/janko/rodauth-rails). This skill covers installing Rodauth, generating account types (basic / admin / SaaS), wiring auth into controllers and portals, and the profile / account-settings resource.
14
9
 
15
- Plutonium integrates with [Rodauth](http://rodauth.jeremyevans.net/) via [rodauth-rails](https://github.com/janko/rodauth-rails) for authentication. This skill covers the full auth surface: installing Rodauth, configuring account types, building login/password flows, and adding a user profile / account settings page.
10
+ For multi-tenant invitations and membership, see [[plutonium-tenancy]] Invites. For portal-side wiring, see [[plutonium-app]] Portal Engines.
16
11
 
17
- ## Contents
18
- - [Rodauth setup](#rodauth-setup)
19
- - [Account types](#account-types)
20
- - [Connecting auth to controllers](#connecting-auth-to-controllers)
21
- - [Customization](#customization)
22
- - [Email configuration](#email-configuration)
23
- - [Portal integration](#portal-integration)
24
- - [API authentication](#api-authentication)
25
- - [Profile page](#profile-page)
26
- - [Gotchas](#gotchas)
12
+ ## 🚨 Critical (read first)
27
13
 
28
- ## Rodauth setup
14
+ - **Use the generators.** `pu:rodauth:install`, `pu:rodauth:account`, `pu:rodauth:admin`, `pu:saas:setup`, `pu:profile:install`, `pu:profile:conn`. Never hand-write Rodauth plugin files, account models, or profile resources.
15
+ - **Role index 0 is the most privileged** (`owner`, `super_admin`). Invite interactions default new invitees to **index 1**.
16
+ - **`pu:saas:setup --roles=...` always prepends `owner` as index 0.** Don't include `owner` in the option.
17
+ - **`pu:saas:setup` is a meta-generator.** It also runs `pu:saas:portal`, `pu:profile:setup`, `pu:saas:welcome`, and `pu:invites:install`. Don't re-run those manually.
18
+ - **Profile association is always `:profile`** regardless of the model class — `current_user.profile`, `build_profile`, `params.require(:profile)`.
19
+ - **Profile needs `pu:profile:conn` to be visible** — without it, the singular `/profile` route and `profile_url` helper don't exist.
20
+ - **Every user needs a profile row.** Add an `after_create` callback or `find_or_create_by` — otherwise `current_user.profile` is nil.
29
21
 
30
- ### Step 1: Install Rodauth Base
22
+ ---
23
+
24
+ ## Install
31
25
 
32
26
  ```bash
33
27
  rails generate pu:rodauth:install
34
28
  ```
35
29
 
36
- This installs:
37
- - Required gems (`rodauth-rails`, `bcrypt`, `sequel-activerecord_connection`)
38
- - `app/rodauth/rodauth_app.rb` - Main Roda app
39
- - `app/rodauth/rodauth_plugin.rb` - Base plugin
40
- - `app/controllers/rodauth_controller.rb` - Base controller
41
- - `config/initializers/rodauth.rb` - Configuration
42
- - `app/views/layouts/rodauth.html.erb` - Auth layout
43
- - PostgreSQL extension migration (if using PostgreSQL)
44
-
45
- ### Step 2: Create Account Type
46
-
47
- ```bash
48
- # Basic user account
49
- rails generate pu:rodauth:account user
50
-
51
- # Admin with 2FA and security features
52
- rails generate pu:rodauth:admin admin
30
+ Installs gems (`rodauth-rails`, `bcrypt`, `sequel-activerecord_connection`), the Roda app at `app/rodauth/rodauth_app.rb`, base plugin and controller, initializer, layout, and a PostgreSQL extension migration if applicable.
53
31
 
54
- # SaaS user with entity/organization (multi-tenant)
55
- rails generate pu:saas:setup --user Customer --entity Organization
56
- ```
32
+ ---
57
33
 
58
34
  ## Account types
59
35
 
60
- ### Basic Account (`pu:rodauth:account`)
36
+ Pick one (or several — apps can have multiple account types side-by-side).
37
+
38
+ ### Basic account — `pu:rodauth:account`
61
39
 
62
40
  ```bash
63
41
  rails generate pu:rodauth:account user [options]
64
42
  ```
65
43
 
66
- **Options:**
67
-
68
44
  | Option | Description |
69
- |--------|-------------|
70
- | `--defaults` | Enable default features (login, logout, remember, password reset) |
71
- | `--kitchen_sink` | Enable ALL available features |
45
+ |---|---|
46
+ | `--defaults` | Enables login, logout, remember, password reset |
47
+ | `--kitchen_sink` | Enables ALL features |
72
48
  | `--primary` | Mark as primary account (no URL prefix) |
73
49
  | `--no-mails` | Skip mailer setup |
74
- | `--argon2` | Use Argon2 instead of bcrypt for password hashing |
75
- | `--api_only` | Configure for JSON API only (no sessions) |
76
-
77
- **Feature Options:**
78
-
79
- | Option | Default | Description |
80
- |--------|---------|-------------|
81
- | `--login` | ✓ | Login functionality |
82
- | `--logout` | ✓ | Logout functionality |
83
- | `--remember` | ✓ | "Remember me" cookies |
84
- | `--create_account` | ✓ | User registration |
85
- | `--verify_account` | | Email verification |
86
- | `--reset_password` | | Password reset via email |
87
- | `--change_password` | | Change password |
88
- | `--change_login` | | Change email |
89
- | `--verify_login_change` | ✓ | Verify email change |
90
- | `--otp` | | TOTP two-factor auth |
91
- | `--webauthn` | | WebAuthn/passkeys |
92
- | `--recovery_codes` | | Recovery codes for 2FA |
93
- | `--lockout` | | Account lockout after failed attempts |
50
+ | `--argon2` | Use Argon2 instead of bcrypt |
51
+ | `--api_only` | JSON API only (no sessions) |
52
+
53
+ ### Feature flags
54
+
55
+ | Flag | Default | Purpose |
56
+ |---|---|---|
57
+ | `--login`, `--logout`, `--remember` | ✓ | Basic auth |
58
+ | `--create_account`, `--verify_account` | ✓ | Registration + email verification |
59
+ | `--reset_password`, `--change_password` | ✓ | Password lifecycle |
60
+ | `--change_login`, `--verify_login_change` | ✓ | Email change |
61
+ | `--otp` | | TOTP 2FA |
62
+ | `--webauthn` | | WebAuthn / passkeys |
63
+ | `--recovery_codes` | | 2FA backup codes |
64
+ | `--lockout` | | Lock after failed attempts |
94
65
  | `--active_sessions` | | Track active sessions |
95
- | `--audit_logging` | | Audit authentication events |
66
+ | `--audit_logging` | | Log auth events |
96
67
  | `--close_account` | | Allow account deletion |
97
- | `--email_auth` | | Passwordless login via email |
98
- | `--sms_codes` | | SMS-based 2FA |
99
- | `--jwt` | | JWT token authentication |
100
- | `--jwt_refresh` | | JWT refresh tokens |
68
+ | `--email_auth` | | Passwordless email login |
69
+ | `--sms_codes` | | SMS 2FA |
70
+ | `--jwt`, `--jwt_refresh` | | JWT for API auth |
101
71
 
102
- ### Admin Account (`pu:rodauth:admin`)
72
+ ### Admin account `pu:rodauth:admin`
103
73
 
104
- Creates a secure admin account with multi-phase login, TOTP 2FA (required), recovery codes, account lockout, active session tracking, audit logging, role-based access control, invite interaction, and no public signup.
74
+ Pre-configured secure admin with multi-phase login, required TOTP, recovery codes, lockout, active session tracking, audit logging, role-based access, invite interaction, and **no public signup**.
105
75
 
106
76
  ```bash
107
77
  rails generate pu:rodauth:admin admin
@@ -109,80 +79,67 @@ rails generate pu:rodauth:admin admin --roles=super_admin,admin,viewer
109
79
  rails generate pu:rodauth:admin admin --extra-attributes=name:string,department:string
110
80
  ```
111
81
 
112
- **Options:**
113
-
114
82
  | Option | Default | Description |
115
- |--------|---------|-------------|
116
- | `--roles` | super_admin,admin | Comma-separated roles for admin accounts |
117
- | `--extra_attributes` | | Additional model attributes (e.g., name:string) |
83
+ |---|---|---|
84
+ | `--roles` | `super_admin,admin` | Comma-separated roles (positional enum) |
85
+ | `--extra_attributes` | | Additional model attributes (e.g. `name:string`) |
118
86
 
119
- **Role ordering convention:** Roles are stored as a positional enum — **index 0 is the most privileged** (`super_admin`, `owner`, etc.). The generated invite interaction defaults new invitees to `roles[1]`, so the order in `--roles=` matters.
87
+ **Role-ordering convention:** index 0 is the most privileged. Generated invite interaction defaults new invitees to `roles[1]` the order in `--roles=` matters.
120
88
 
121
89
  ```ruby
122
- # app/models/admin.rb
123
90
  enum :role, super_admin: 0, admin: 1
124
91
  ```
125
92
 
126
- ```ruby
127
- # app/interactions/admin/invite_interaction.rb
128
- class Admin::InviteInteraction < Plutonium::Interaction::Base
129
- attribute :email, :string
130
- attribute :role, default: :admin
131
- # ...
132
- end
133
- ```
93
+ Rake task for direct admin creation:
134
94
 
135
- Rake task for direct creation:
136
95
  ```bash
137
96
  rails rodauth_admin:create[admin@example.com,password123]
138
97
  ```
139
98
 
140
- ### SaaS Setup (`pu:saas:setup`)
99
+ ### SaaS setup `pu:saas:setup` (meta-generator)
100
+
101
+ Creates the User + Entity + Membership trio AND runs:
102
+
103
+ - `pu:saas:portal` → a full `{Entity}Portal` scoped to the entity
104
+ - `pu:profile:setup` → profile model + association
105
+ - `pu:saas:welcome` → onboarding / select-entity flow
106
+ - `pu:invites:install` → the invites package (see [[plutonium-tenancy]])
141
107
 
142
- > **This is a meta-generator.** In addition to creating the user + entity + membership, `pu:saas:setup` also runs:
143
- > - `pu:saas:portal` → a full `{Entity}Portal` scoped to the entity
144
- > - `pu:profile:setup` → a `Profile` model and association on the user
145
- > - `pu:saas:welcome` → the onboarding / select-entity flow
146
- > - `pu:invites:install` → the entire invites package
147
- >
148
- > Don't generate another entity portal after running this. Pass `--force` if re-running.
108
+ Don't generate another entity portal after this. Pass `--force` to re-run.
149
109
 
150
110
  ```bash
151
- rails generate pu:saas:setup --user Customer --entity Organization
152
- rails generate pu:saas:setup --user Customer --entity Organization --roles=member,admin,owner
153
- rails generate pu:saas:setup --user Customer --entity Organization --no-allow-signup
154
- rails generate pu:saas:setup --user Customer --entity Organization --user-attributes=name:string --entity-attributes=slug:string
111
+ rails g pu:saas:setup --user Customer --entity Organization
112
+ rails g pu:saas:setup --user Customer --entity Organization --roles=member,admin
113
+ rails g pu:saas:setup --user Customer --entity Organization --no-allow-signup
114
+ rails g pu:saas:setup --user Customer --entity Organization \
115
+ --user-attributes=name:string --entity-attributes=slug:string
155
116
  ```
156
117
 
157
- **Options:**
158
-
159
118
  | Option | Default | Description |
160
- |--------|---------|-------------|
119
+ |---|---|---|
161
120
  | `--user=NAME` | (required) | User account model name |
162
121
  | `--entity=NAME` | (required) | Entity model name |
163
- | `--allow-signup` | true | Allow public registration |
164
- | `--roles` | admin,member | Additional membership roles. **`owner` is always prepended as index 0** — don't include it. |
165
- | `--skip-entity` | false | Skip entity model generation |
166
- | `--skip-membership` | false | Skip membership model generation |
167
- | `--user-attributes` | | Additional user model attributes |
168
- | `--entity-attributes` | | Additional entity model attributes |
169
- | `--membership-attributes` | | Additional membership model attributes |
122
+ | `--allow-signup` | `true` | Allow public registration |
123
+ | `--roles` | `admin,member` | Additional roles. **`owner` always prepended as index 0** |
124
+ | `--skip-entity` | | Skip entity model generation |
125
+ | `--skip-membership` | | Skip membership model generation |
126
+ | `--user-attributes`, `--entity-attributes`, `--membership-attributes` | | Extra model fields |
127
+
128
+ Individual generators (rarely needed): `pu:saas:user`, `pu:saas:entity`, `pu:saas:membership`.
170
129
 
171
- Individual generators: `pu:saas:user`, `pu:saas:entity`, `pu:saas:membership`.
130
+ Generated user + membership:
172
131
 
173
132
  ```ruby
174
- # app/models/customer.rb
175
133
  class Customer < ApplicationRecord
176
134
  include Rodauth::Rails.model(:customer)
177
135
  has_many :organization_customers, dependent: :destroy
178
136
  has_many :organizations, through: :organization_customers
179
137
  end
180
138
 
181
- # app/models/organization_customer.rb
182
139
  class OrganizationCustomer < ApplicationRecord
183
140
  belongs_to :organization
184
141
  belongs_to :customer
185
- enum :role, member: 0, owner: 1
142
+ enum :role, owner: 0, admin: 1, member: 2
186
143
 
187
144
  validates :customer, uniqueness: {
188
145
  scope: :organization_id,
@@ -191,17 +148,18 @@ class OrganizationCustomer < ApplicationRecord
191
148
  end
192
149
  ```
193
150
 
194
- ## Connecting auth to controllers
151
+ ---
152
+
153
+ ## Wiring auth into controllers
195
154
 
196
155
  ```ruby
197
- # app/controllers/resource_controller.rb
198
156
  class ResourceController < PlutoniumController
199
157
  include Plutonium::Resource::Controller
200
158
  include Plutonium::Auth::Rodauth(:user)
201
159
  end
202
160
  ```
203
161
 
204
- Multiple account types:
162
+ Multiple account types — include the matching `:name`:
205
163
 
206
164
  ```ruby
207
165
  class AdminController < PlutoniumController
@@ -210,72 +168,67 @@ class AdminController < PlutoniumController
210
168
  end
211
169
  ```
212
170
 
213
- Including `Plutonium::Auth::Rodauth(:name)` adds `current_user`, `logout_url`, and `rodauth`.
171
+ `Plutonium::Auth::Rodauth(:name)` exposes `current_user`, `logout_url`, and `rodauth` in the controller.
214
172
 
215
- ## Customization
173
+ For portal wiring (`AdminPortal::Concerns::Controller`), see [[plutonium-app]] › Portal controller concern.
216
174
 
217
- ### Custom Login Redirect
175
+ ---
176
+
177
+ ## Common customizations
178
+
179
+ All inside the Rodauth `configure do ... end` block in `app/rodauth/<name>_rodauth_plugin.rb`.
180
+
181
+ ### Custom login redirect
218
182
 
219
183
  ```ruby
220
- configure do
221
- login_redirect do
222
- if rails_account.admin?
223
- "/admin"
224
- else
225
- "/dashboard"
226
- end
227
- end
184
+ login_redirect do
185
+ rails_account.admin? ? "/admin" : "/dashboard"
228
186
  end
229
187
  ```
230
188
 
231
- ### Custom Validation
189
+ ### Custom create-account validation + hook
232
190
 
233
191
  ```ruby
234
- configure do
235
- before_create_account do
236
- throw_error_status(422, "name", "must be present") if param("name").empty?
237
- end
238
-
239
- after_create_account do
240
- Profile.create!(account_id: account_id, name: param("name"))
241
- end
192
+ before_create_account do
193
+ throw_error_status(422, "name", "must be present") if param("name").empty?
194
+ end
195
+
196
+ after_create_account do
197
+ Profile.create!(account_id: account_id, name: param("name"))
242
198
  end
243
199
  ```
244
200
 
245
- ### Password Requirements
201
+ ### Password requirements
246
202
 
247
203
  ```ruby
248
- configure do
249
- password_minimum_length 12
204
+ password_minimum_length 12
250
205
 
251
- password_meets_requirements? do |password|
252
- super(password) && password.match?(/\d/) && password.match?(/[^a-zA-Z\d]/)
253
- end
206
+ password_meets_requirements? do |password|
207
+ super(password) && password.match?(/\d/) && password.match?(/[^a-zA-Z\d]/)
254
208
  end
255
209
  ```
256
210
 
257
- ### Multi-Phase Login
211
+ ### Multi-phase login (password on a separate page)
258
212
 
259
213
  ```ruby
260
- configure do
261
- use_multi_phase_login? true
262
- end
214
+ use_multi_phase_login? true
263
215
  ```
264
216
 
265
- ### Prevent Public Signup
217
+ ### Prevent public signup (admin pattern)
266
218
 
267
219
  ```ruby
268
- configure do
269
- before_create_account_route do
270
- request.halt unless internal_request?
271
- end
220
+ before_create_account_route do
221
+ request.halt unless internal_request?
272
222
  end
273
223
  ```
274
224
 
225
+ ---
226
+
275
227
  ## Email configuration
276
228
 
229
+ Standard ActionMailer in `config/environments/production.rb`:
230
+
277
231
  ```ruby
278
- # config/environments/production.rb
279
232
  config.action_mailer.delivery_method = :smtp
280
233
  config.action_mailer.smtp_settings = {
281
234
  address: "smtp.example.com",
@@ -285,39 +238,9 @@ config.action_mailer.smtp_settings = {
285
238
  }
286
239
  ```
287
240
 
288
- Override templates in `app/views/rodauth/user_mailer/`.
289
-
290
- ## Portal integration
241
+ Override templates in `app/views/rodauth/<account>_mailer/`.
291
242
 
292
- ```bash
293
- rails generate pu:pkg:portal admin
294
- # Select "Rodauth account" → "admin"
295
- ```
296
-
297
- Manual:
298
-
299
- ```ruby
300
- module AdminPortal
301
- class Engine < Rails::Engine
302
- include Plutonium::Portal::Engine
303
-
304
- config.before_initialize do
305
- config.to_prepare do
306
- AdminPortal::ResourceController.class_eval do
307
- include Plutonium::Auth::Rodauth(:admin)
308
- before_action :require_authenticated
309
-
310
- private
311
-
312
- def require_authenticated
313
- redirect_to rodauth.login_path unless current_user
314
- end
315
- end
316
- end
317
- end
318
- end
319
- end
320
- ```
243
+ ---
321
244
 
322
245
  ## API authentication
323
246
 
@@ -334,40 +257,37 @@ GET /api/posts
334
257
  Authorization: Bearer <access_token>
335
258
  ```
336
259
 
337
- ## Profile page
260
+ ---
261
+
262
+ ## Profile resource
338
263
 
339
- Plutonium provides a Profile resource generator for managing Rodauth account settings. Users can view/edit their profile, access Rodauth security features (change password, 2FA, etc.), and manage account settings in one place.
264
+ Manages Rodauth account settings: view/edit personal fields plus links to Rodauth security features (change password, 2FA, etc.).
340
265
 
341
- ### Quick setup
266
+ ### Quick setup (with extra fields)
342
267
 
343
268
  ```bash
344
269
  rails g pu:profile:setup date_of_birth:date bio:text \
345
- --dest=competition \
346
- --portal=competition_portal
270
+ --dest=competition \
271
+ --portal=competition_portal
347
272
  ```
348
273
 
349
- ### Step-by-step install
274
+ ### Step-by-step
350
275
 
351
276
  ```bash
352
- rails generate pu:profile:install --dest=main_app
353
- ```
354
-
355
- | Option | Default | Description |
356
- |--------|---------|-------------|
357
- | `--dest=DESTINATION` | (prompts) | Target package or main_app |
358
- | `--user-model=NAME` | User | Rodauth user model name |
277
+ rails generate pu:profile:install bio:text avatar:attachment 'timezone:string?' \
278
+ --dest=customer
359
279
 
360
- With fields:
280
+ rails db:migrate
361
281
 
362
- ```bash
363
- rails g pu:profile:install \
364
- bio:text \
365
- avatar:attachment \
366
- 'timezone:string?' \
367
- --dest=customer
282
+ rails generate pu:profile:conn --dest=customer_portal
368
283
  ```
369
284
 
370
- Custom name:
285
+ | Option | Default | Description |
286
+ |---|---|---|
287
+ | `--dest=DEST` | (prompts) | Target package or `main_app` |
288
+ | `--user-model=NAME` | `User` | Rodauth user model |
289
+
290
+ Custom resource name (first positional argument):
371
291
 
372
292
  ```bash
373
293
  rails g pu:profile:install AccountSettings bio:text --dest=main_app
@@ -375,39 +295,32 @@ rails g pu:profile:install AccountSettings bio:text --dest=main_app
375
295
 
376
296
  ### What gets created
377
297
 
378
- The generator creates a standard Plutonium resource. **By default the model is named `{UserModel}Profile`** (e.g. `UserProfile`, `StaffUserProfile`) — derived from `--user-model`. Pass an explicit name as the first positional argument to override.
298
+ By default the model is `{UserModel}Profile` `UserProfile`, `StaffUserProfile`, etc. — derived from `--user-model`.
379
299
 
380
300
  ```
381
- app/models/[package/]user_profile.rb # {UserModel}Profile model
382
- db/migrate/xxx_create_user_profiles.rb # Migration
301
+ app/models/[package/]user_profile.rb
302
+ db/migrate/xxx_create_user_profiles.rb
383
303
  app/controllers/[package/]user_profiles_controller.rb
384
304
  app/policies/[package/]user_profile_policy.rb
385
305
  app/definitions/[package/]user_profile_definition.rb
386
306
  ```
387
307
 
388
- And modifies:
389
- - **User model**: Adds `has_one :profile, class_name: "{UserModel}Profile", dependent: :destroy`
390
- > The association is **always named `:profile`** regardless of the class, so `current_user.profile` / `build_profile` / `params.require(:profile)` work uniformly.
391
- - **Definition**: Injects custom ShowPage with SecuritySection
392
-
393
- ### The SecuritySection component
308
+ The generator modifies the user model:
394
309
 
395
310
  ```ruby
396
- class ProfileDefinition < Plutonium::Resource::Definition
397
- class ShowPage < ShowPage
398
- private
399
-
400
- def render_after_content
401
- render Plutonium::Profile::SecuritySection.new
402
- end
403
- end
404
- end
311
+ has_one :profile, class_name: "UserProfile", dependent: :destroy
405
312
  ```
406
313
 
407
- Dynamically checks enabled Rodauth features and displays links for:
314
+ 🚨 The association is **always `:profile`**, regardless of class — `current_user.profile`, `build_profile`, `params.require(:profile)` always work.
315
+
316
+ The generated definition injects a custom `ShowPage` that renders the `SecuritySection` component.
317
+
318
+ ### The `SecuritySection` component
319
+
320
+ Dynamically lists Rodauth security links based on which features are enabled:
408
321
 
409
322
  | Feature | Label |
410
- |---------|-------|
323
+ |---|---|
411
324
  | `change_password` | Change Password |
412
325
  | `change_login` | Change Email |
413
326
  | `otp` | Two-Factor Authentication |
@@ -416,96 +329,46 @@ Dynamically checks enabled Rodauth features and displays links for:
416
329
  | `active_sessions` | Active Sessions |
417
330
  | `close_account` | Close Account |
418
331
 
419
- ### After generation
332
+ To customize the show page (e.g. wrap, reorder), override `ShowPage#render_after_content` (see [[plutonium-ui]] › Page hooks).
420
333
 
421
- 1. `rails db:migrate`
422
- 2. Connect to portal: `rails g pu:profile:conn --dest=customer_portal` — registers as singular resource (`/profile`) and enables `profile_url` helper.
423
- 3. Ensure users have a profile row:
334
+ ### Required: every user gets a profile
424
335
 
425
336
  ```ruby
426
337
  class User < ApplicationRecord
427
338
  after_create :create_profile!
428
339
 
429
340
  private
430
-
431
- def create_profile!
432
- create_profile
433
- end
341
+ def create_profile! = create_profile
434
342
  end
435
343
  ```
436
344
 
437
- ### Customizing the profile definition
345
+ Without this, `current_user.profile` is `nil` and the profile route errors. For existing users at migration time, run a one-off `User.find_each(&:create_profile)`.
346
+
347
+ ### Linking to the profile
438
348
 
439
349
  ```ruby
440
- class ProfileDefinition < Plutonium::Resource::Definition
441
- form do |f|
442
- f.field :bio
443
- f.field :avatar
444
- f.field :website
445
- end
446
-
447
- display do |d|
448
- d.field :bio
449
- d.field :avatar
450
- d.field :website
451
- end
452
-
453
- class ShowPage < ShowPage
454
- private
455
-
456
- def render_after_content
457
- render Plutonium::Profile::SecuritySection.new
458
- end
459
- end
460
- end
350
+ link_to("Profile", profile_url) if respond_to?(:profile_url)
461
351
  ```
462
352
 
463
- ### Profile link in header
353
+ `profile_url` only exists when the profile resource is connected via `pu:profile:conn` (which registers it as a singular resource — see [[plutonium-app]] › Routes).
464
354
 
465
- ```ruby
466
- if respond_to?(:profile_url)
467
- link_to "Profile", profile_url
468
- end
469
- ```
355
+ ---
470
356
 
471
357
  ## Gotchas
472
358
 
473
- - **Role index 0 is the most privileged.** For admin/saas roles, index 0 is owner/super_admin. Invite interactions default new invitees to index 1.
474
- - **`owner` is always prepended** in `pu:saas:setup --roles`. Don't include it manually.
475
- - **Profile association is always `:profile`**, even if the model class is `StaffUserProfile`.
476
- - **`pu:saas:setup` is a meta-generator** — it also runs `pu:saas:portal`, `pu:profile:setup`, `pu:saas:welcome`, and `pu:invites:install`. Don't rerun them separately.
477
- - **Profile requires a connected portal** — without `pu:profile:conn`, `profile_url` is missing and the user menu link won't render.
478
- - **Users need a profile row.** Add an `after_create` callback or use `find_or_create`.
479
-
480
- ## Feature reference
481
-
482
- | Feature | Description |
483
- |---------|-------------|
484
- | `login` | Basic login/logout |
485
- | `create_account` | User registration |
486
- | `verify_account` | Email verification |
487
- | `reset_password` | Password reset via email |
488
- | `change_password` | Change password when logged in |
489
- | `change_login` | Change email address |
490
- | `verify_login_change` | Verify email change |
491
- | `remember` | "Remember me" functionality |
492
- | `otp` | TOTP two-factor authentication |
493
- | `sms_codes` | SMS-based 2FA |
494
- | `recovery_codes` | Backup codes for 2FA |
495
- | `webauthn` | WebAuthn/passkey authentication |
496
- | `lockout` | Lock account after failed attempts |
497
- | `active_sessions` | Track/manage active sessions |
498
- | `audit_logging` | Log authentication events |
499
- | `email_auth` | Passwordless email login |
500
- | `jwt` | JWT token authentication |
501
- | `jwt_refresh` | JWT refresh tokens |
502
- | `close_account` | Allow account deletion |
359
+ - **Role index 0 is the most privileged.** For admin/SaaS roles, index 0 is `owner`/`super_admin`. Generated invite interactions default invitees to index 1.
360
+ - **`owner` is always prepended** by `pu:saas:setup --roles`. Don't include it manually.
361
+ - **Profile association is always `:profile`** even when the class is `StaffUserProfile`.
362
+ - **`pu:saas:setup` runs four other generators** — don't re-run portal, profile, welcome, or invites separately.
363
+ - **Profile requires `pu:profile:conn`** without it, no route, no `profile_url`, no menu link.
364
+ - **Users need a profile row.** Add an `after_create` callback (or `find_or_create_by`) `current_user.profile` is otherwise nil.
365
+
366
+ ---
503
367
 
504
368
  ## Related skills
505
369
 
506
- - `plutonium-installation` - Initial Plutonium setup
507
- - `plutonium-portal` - Portal configuration
508
- - `plutonium-policy` - Authorization after authentication
509
- - `plutonium-invites` - User invitation system for multi-tenant apps
510
- - `plutonium-definition` - Customizing the profile definition
511
- - `plutonium-views` - Custom pages and components
370
+ - [[plutonium-app]] initial install, portal wiring, mounting auth-constrained routes
371
+ - [[plutonium-tenancy]] — invites + memberships for multi-tenant onboarding
372
+ - [[plutonium-behavior]] — policies (auth runs first, policy checks the authenticated user)
373
+ - [[plutonium-resource]] customizing the profile definition (fields, inputs, displays)
374
+ - [[plutonium-ui]] overriding the profile's `ShowPage`, theming the security section