plutonium 0.50.0 → 0.52.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 (201) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +85 -102
  3. data/.claude/skills/plutonium-app/SKILL.md +574 -0
  4. data/.claude/skills/plutonium-auth/SKILL.md +167 -302
  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 +674 -0
  8. data/.claude/skills/plutonium-testing/SKILL.md +9 -6
  9. data/.claude/skills/plutonium-ui/SKILL.md +900 -0
  10. data/CHANGELOG.md +44 -2
  11. data/Rakefile +2 -1
  12. data/app/assets/plutonium.css +1 -11
  13. data/app/assets/plutonium.js +1010 -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 +38 -29
  18. data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
  19. data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
  20. data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
  21. data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
  22. data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
  23. data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
  24. data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
  25. data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
  26. data/docs/.vitepress/theme/custom.css +144 -0
  27. data/docs/.vitepress/theme/index.ts +58 -1
  28. data/docs/getting-started/index.md +33 -57
  29. data/docs/getting-started/installation.md +37 -80
  30. data/docs/getting-started/tutorial/02-first-resource.md +17 -8
  31. data/docs/getting-started/tutorial/03-authentication.md +31 -23
  32. data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
  33. data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
  34. data/docs/getting-started/tutorial/07-author-portal.md +8 -0
  35. data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
  36. data/docs/getting-started/tutorial/index.md +4 -5
  37. data/docs/guides/adding-resources.md +66 -377
  38. data/docs/guides/authentication.md +98 -462
  39. data/docs/guides/authorization.md +124 -370
  40. data/docs/guides/creating-packages.md +93 -298
  41. data/docs/guides/custom-actions.md +126 -441
  42. data/docs/guides/customizing-ui.md +258 -0
  43. data/docs/guides/index.md +49 -52
  44. data/docs/guides/multi-tenancy.md +123 -186
  45. data/docs/guides/nested-resources.md +137 -396
  46. data/docs/guides/search-filtering.md +127 -238
  47. data/docs/guides/testing.md +10 -5
  48. data/docs/guides/theming.md +168 -405
  49. data/docs/guides/troubleshooting.md +5 -3
  50. data/docs/guides/user-invites.md +112 -425
  51. data/docs/guides/user-profile.md +82 -241
  52. data/docs/index.md +10 -219
  53. data/docs/public/asciinema/home-scaffold.cast +305 -0
  54. data/docs/public/images/guides/custom-actions-bulk.png +0 -0
  55. data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
  56. data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
  57. data/docs/public/images/guides/nested-inputs.png +0 -0
  58. data/docs/public/images/guides/nested-resources-tab.png +0 -0
  59. data/docs/public/images/guides/search-filtering-index.png +0 -0
  60. data/docs/public/images/guides/search-filtering-panel.png +0 -0
  61. data/docs/public/images/guides/theming-after.png +0 -0
  62. data/docs/public/images/guides/theming-before.png +0 -0
  63. data/docs/public/images/guides/user-invites-landing.png +0 -0
  64. data/docs/public/images/guides/user-profile-edit.png +0 -0
  65. data/docs/public/images/guides/user-profile-show.png +0 -0
  66. data/docs/public/images/home-index.png +0 -0
  67. data/docs/public/images/home-new.png +0 -0
  68. data/docs/public/images/home-show.png +0 -0
  69. data/docs/public/images/tutorial/02-empty-index.png +0 -0
  70. data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
  71. data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
  72. data/docs/public/images/tutorial/02-new-form.png +0 -0
  73. data/docs/public/images/tutorial/03-create-account.png +0 -0
  74. data/docs/public/images/tutorial/03-login.png +0 -0
  75. data/docs/public/images/tutorial/04-admin-index.png +0 -0
  76. data/docs/public/images/tutorial/05-actions-menu.png +0 -0
  77. data/docs/public/images/tutorial/05-row-actions.png +0 -0
  78. data/docs/public/images/tutorial/06-comments-tab.png +0 -0
  79. data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
  80. data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
  81. data/docs/public/images/tutorial/07-author-portal.png +0 -0
  82. data/docs/public/images/tutorial/08-customized-index.png +0 -0
  83. data/docs/reference/app/generators.md +517 -0
  84. data/docs/reference/app/index.md +158 -0
  85. data/docs/reference/app/packages.md +146 -0
  86. data/docs/reference/app/portals.md +377 -0
  87. data/docs/reference/auth/accounts.md +229 -0
  88. data/docs/reference/auth/index.md +88 -0
  89. data/docs/reference/auth/profile.md +185 -0
  90. data/docs/reference/behavior/controllers.md +395 -0
  91. data/docs/reference/behavior/index.md +22 -0
  92. data/docs/reference/behavior/interactions.md +341 -0
  93. data/docs/reference/behavior/policies.md +417 -0
  94. data/docs/reference/index.md +67 -48
  95. data/docs/reference/resource/actions.md +423 -0
  96. data/docs/reference/resource/definition.md +508 -0
  97. data/docs/reference/resource/index.md +50 -0
  98. data/docs/reference/resource/model.md +348 -0
  99. data/docs/reference/resource/query.md +305 -0
  100. data/docs/reference/tenancy/entity-scoping.md +368 -0
  101. data/docs/reference/tenancy/index.md +36 -0
  102. data/docs/reference/tenancy/invites.md +400 -0
  103. data/docs/reference/tenancy/nested-resources.md +267 -0
  104. data/docs/reference/testing/index.md +287 -0
  105. data/docs/reference/ui/assets.md +400 -0
  106. data/docs/reference/ui/components.md +165 -0
  107. data/docs/reference/ui/displays.md +104 -0
  108. data/docs/reference/ui/forms.md +284 -0
  109. data/docs/reference/ui/index.md +30 -0
  110. data/docs/reference/ui/layouts.md +106 -0
  111. data/docs/reference/ui/pages.md +189 -0
  112. data/docs/reference/ui/tables.md +121 -0
  113. data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
  114. data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -0
  115. data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
  116. data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
  117. data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
  118. data/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -0
  119. data/gemfiles/rails_7.gemfile.lock +1 -1
  120. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  121. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  122. data/lib/generators/pu/core/assets/assets_generator.rb +10 -0
  123. data/lib/generators/pu/core/update/update_generator.rb +0 -20
  124. data/lib/generators/pu/invites/install_generator.rb +45 -0
  125. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
  126. data/lib/generators/pu/profile/conn_generator.rb +2 -2
  127. data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
  128. data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
  129. data/lib/generators/pu/rodauth/account_generator.rb +2 -1
  130. data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
  131. data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
  132. data/lib/generators/pu/rodauth/views_generator.rb +0 -2
  133. data/lib/generators/pu/saas/membership/USAGE +4 -1
  134. data/lib/generators/pu/saas/setup_generator.rb +16 -4
  135. data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
  136. data/lib/plutonium/definition/base.rb +1 -1
  137. data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
  138. data/lib/plutonium/helpers/turbo_helper.rb +30 -0
  139. data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
  140. data/lib/plutonium/resource/controller.rb +1 -0
  141. data/lib/plutonium/resource/controllers/crud_actions.rb +23 -5
  142. data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
  143. data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
  144. data/lib/plutonium/resource/policy.rb +7 -0
  145. data/lib/plutonium/routing/mapper_extensions.rb +15 -0
  146. data/lib/plutonium/ui/component/methods.rb +5 -0
  147. data/lib/plutonium/ui/form/base.rb +23 -3
  148. data/lib/plutonium/ui/form/components/json.rb +58 -0
  149. data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
  150. data/lib/plutonium/ui/form/components/secure_association.rb +103 -22
  151. data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
  152. data/lib/plutonium/ui/form/interaction.rb +1 -1
  153. data/lib/plutonium/ui/form/resource.rb +0 -4
  154. data/lib/plutonium/ui/form/theme.rb +1 -1
  155. data/lib/plutonium/ui/grid/resource.rb +1 -1
  156. data/lib/plutonium/ui/layout/base.rb +1 -0
  157. data/lib/plutonium/ui/page/base.rb +0 -7
  158. data/lib/plutonium/ui/page/edit.rb +1 -1
  159. data/lib/plutonium/ui/page/index.rb +4 -4
  160. data/lib/plutonium/ui/page/new.rb +1 -1
  161. data/lib/plutonium/ui/table/components/filter_form.rb +12 -4
  162. data/lib/plutonium/ui/table/resource.rb +1 -1
  163. data/lib/plutonium/version.rb +1 -1
  164. data/lib/plutonium.rb +8 -0
  165. data/lib/tasks/release.rake +15 -1
  166. data/package.json +13 -10
  167. data/src/css/slim_select.css +4 -0
  168. data/src/js/controllers/form_controller.js +5 -4
  169. data/src/js/controllers/slim_select_controller.js +61 -0
  170. data/src/js/turbo/turbo_actions.js +33 -0
  171. data/yarn.lock +661 -544
  172. metadata +86 -33
  173. data/.claude/skills/plutonium-assets/SKILL.md +0 -512
  174. data/.claude/skills/plutonium-controller/SKILL.md +0 -396
  175. data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
  176. data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
  177. data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
  178. data/.claude/skills/plutonium-forms/SKILL.md +0 -465
  179. data/.claude/skills/plutonium-installation/SKILL.md +0 -331
  180. data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
  181. data/.claude/skills/plutonium-invites/SKILL.md +0 -408
  182. data/.claude/skills/plutonium-model/SKILL.md +0 -440
  183. data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
  184. data/.claude/skills/plutonium-package/SKILL.md +0 -198
  185. data/.claude/skills/plutonium-policy/SKILL.md +0 -456
  186. data/.claude/skills/plutonium-portal/SKILL.md +0 -410
  187. data/.claude/skills/plutonium-views/SKILL.md +0 -651
  188. data/docs/reference/assets/index.md +0 -496
  189. data/docs/reference/controller/index.md +0 -412
  190. data/docs/reference/definition/actions.md +0 -462
  191. data/docs/reference/definition/fields.md +0 -383
  192. data/docs/reference/definition/index.md +0 -326
  193. data/docs/reference/definition/query.md +0 -351
  194. data/docs/reference/generators/index.md +0 -648
  195. data/docs/reference/interaction/index.md +0 -449
  196. data/docs/reference/model/features.md +0 -248
  197. data/docs/reference/model/index.md +0 -218
  198. data/docs/reference/policy/index.md +0 -456
  199. data/docs/reference/portal/index.md +0 -379
  200. data/docs/reference/views/forms.md +0 -411
  201. data/docs/reference/views/index.md +0 -544
@@ -1,559 +1,195 @@
1
1
  # Authentication
2
2
 
3
- This guide covers setting up user authentication with Rodauth.
3
+ Add Rodauth-based authentication to your Plutonium app.
4
4
 
5
- ## Overview
5
+ ## Goal
6
6
 
7
- Plutonium uses [Rodauth](http://rodauth.jeremyevans.net/) via [rodauth-rails](https://github.com/janko/rodauth-rails) for authentication, providing:
8
- - User registration and login
9
- - Password reset
10
- - Email verification
11
- - Multi-factor authentication (OTP, WebAuthn, SMS)
12
- - Session management
13
- - Account lockout
7
+ Authenticated users can sign up, log in, change passwords, and reset forgotten passwords. Pages in protected portals are gated.
14
8
 
15
- ## Installation
16
-
17
- ### New Applications
18
-
19
- The Plutonium template installs Rodauth automatically:
9
+ ## Quick path — basic user auth
20
10
 
21
11
  ```bash
22
- rails new myapp -a propshaft -j esbuild -c tailwind \
23
- -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
24
- ```
12
+ # 1. Install Rodauth
13
+ rails generate pu:rodauth:install
25
14
 
26
- ### Existing Applications
15
+ # 2. Create a user account type
16
+ rails generate pu:rodauth:account user
27
17
 
28
- ```bash
29
- rails g pu:rodauth:install
18
+ # 3. Run migrations
30
19
  rails db:migrate
20
+
21
+ # 4. Wire auth into a portal
22
+ # (when you run `pu:pkg:portal admin --auth=user`, this happens automatically)
31
23
  ```
32
24
 
33
- This installs:
34
- - Required gems (`rodauth-rails`, `bcrypt`, `sequel-activerecord_connection`)
35
- - `app/rodauth/rodauth_app.rb` - Main Roda app
36
- - `app/rodauth/rodauth_plugin.rb` - Base plugin
37
- - `app/controllers/rodauth_controller.rb` - Base controller
38
- - `config/initializers/rodauth.rb` - Configuration
25
+ If you generated the portal with `--auth=user`, the engine is already mounted with the `Rodauth::Rails.authenticate(:user)` constraint — open `packages/admin_portal/config/routes.rb` to see it. The wiring looks like:
39
26
 
40
- ## Creating Account Types
27
+ ```ruby
28
+ # packages/admin_portal/config/routes.rb (generated)
29
+ Rails.application.routes.draw do
30
+ constraints Rodauth::Rails.authenticate(:user) do
31
+ mount AdminPortal::Engine, at: "/admin"
32
+ end
33
+ end
34
+ ```
41
35
 
42
- ### Basic User Account
36
+ If you generated the portal as `--public` and need to switch it to authenticated later, re-run with `--auth=user --force` (or edit the constraint into the routes file by hand).
43
37
 
44
- ```bash
45
- rails g pu:rodauth:account user
46
- rails db:migrate
47
- ```
38
+ For accounts with more features, options, and admin patterns: see [Reference › Auth › Accounts](/reference/auth/accounts).
48
39
 
49
- **Default features** (enabled with `--defaults`, which is on by default):
50
- - `login`, `logout`, `remember`
51
- - `create_account`, `verify_account`, `verify_account_grace_period`
52
- - `reset_password`, `reset_password_notify`
53
- - `change_login`, `verify_login_change`
54
- - `change_password`, `change_password_notify`
55
- - `case_insensitive_login`, `internal_request`
40
+ ## Common variations
56
41
 
57
- ### Admin Account
42
+ ### Multi-factor auth (TOTP)
58
43
 
59
44
  ```bash
60
- rails g pu:rodauth:admin admin
61
- rails db:migrate
45
+ rails generate pu:rodauth:account user --otp --recovery_codes
62
46
  ```
63
47
 
64
- Includes all base features plus:
65
- - Multi-phase login (email first, then password)
66
- - TOTP two-factor authentication (required)
67
- - Recovery codes
68
- - Account lockout
69
- - Active sessions tracking
70
- - Audit logging
71
- - **No public signup** - accounts created via rake task
48
+ Then enable in the user-facing security section (see [User profile](./user-profile)).
72
49
 
73
- ### SaaS Account (Multi-tenant)
50
+ ### Hardened admin account
51
+
52
+ For an admin role with 2FA, lockout, audit logging, and no public signup, use the dedicated `pu:rodauth:admin` generator (a preset of `pu:rodauth:account` with hardened defaults):
74
53
 
75
54
  ```bash
76
- rails g pu:saas:setup --user Customer --entity Organization
77
- rails g pu:saas:setup --user Customer --entity Organization --roles=member,admin,owner
78
- rails g pu:saas:setup --user Customer --entity Organization --no-allow-signup
79
- rails db:migrate
55
+ rails generate pu:rodauth:admin admin
80
56
  ```
81
57
 
82
- Creates a complete multi-tenant setup:
83
- - Customer account model with Rodauth authentication
84
- - Organization entity model with unique name
85
- - OrganizationCustomer membership join model with role enum
58
+ Create the first admin with the rake task generated alongside the account:
86
59
 
87
- You can also run individual generators:
88
60
  ```bash
89
- rails g pu:saas:user Customer # Just the user account
90
- rails g pu:saas:entity Organization # Just the entity model
91
- rails g pu:saas:membership --user Customer --entity Organization # Just the membership
61
+ EMAIL=admin@example.com rails rodauth:admin
62
+ # (run without EMAIL to prompt)
92
63
  ```
93
64
 
94
- ## Generator Options
65
+ The task creates the account and triggers a verification email; the admin sets their own password through that flow. No password is passed on the command line.
95
66
 
96
- ### Feature Options
67
+ ### Multi-tenant SaaS — user + entity + membership in one shot
97
68
 
98
69
  ```bash
99
- # Enable all supported features
100
- rails g pu:rodauth:account user --kitchen_sink
101
-
102
- # Disable default features (explicit selection only)
103
- rails g pu:rodauth:account user --no-defaults
104
-
105
- # Enable specific features
106
- rails g pu:rodauth:account user --otp --recovery_codes --lockout
107
-
108
- # Skip email setup
109
- rails g pu:rodauth:account user --no-mails
70
+ rails generate pu:saas:setup --user Customer --entity Organization
71
+ ```
110
72
 
111
- # API-only mode (JWT, no sessions)
112
- rails g pu:rodauth:account user --api_only --jwt --jwt_refresh
73
+ ⚠️ This 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. See [Reference › Auth › Accounts › SaaS setup](/reference/auth/accounts#saas-setup-pu-saas-setup).
113
74
 
114
- # Use Argon2 instead of bcrypt
115
- rails g pu:rodauth:account user --argon2
75
+ ### API-only (JWT)
116
76
 
117
- # Mark as primary account (no URL prefix)
118
- rails g pu:rodauth:account user --primary
77
+ ```bash
78
+ rails generate pu:rodauth:account api_user --api_only --jwt --jwt_refresh
119
79
  ```
120
80
 
121
- ### Available Features
122
-
123
- | Feature | Description |
124
- |---------|-------------|
125
- | `login` | Basic login/logout |
126
- | `create_account` | User registration |
127
- | `verify_account` | Email verification |
128
- | `reset_password` | Password reset via email |
129
- | `change_password` | Change password when logged in |
130
- | `change_login` | Change email address |
131
- | `verify_login_change` | Verify email change |
132
- | `remember` | "Remember me" functionality |
133
- | `otp` | TOTP two-factor authentication |
134
- | `sms_codes` | SMS-based 2FA |
135
- | `recovery_codes` | Backup codes for 2FA |
136
- | `webauthn` | WebAuthn/passkey authentication |
137
- | `lockout` | Lock account after failed attempts |
138
- | `active_sessions` | Track/manage active sessions |
139
- | `audit_logging` | Log authentication events |
140
- | `email_auth` | Passwordless email login |
141
- | `jwt` | JWT token authentication |
142
- | `jwt_refresh` | JWT refresh tokens |
143
- | `close_account` | Allow account deletion |
144
-
145
- ## Generated Files
146
-
147
81
  ```
148
- app/
149
- ├── controllers/rodauth/
150
- │ └── user_controller.rb # Account-specific controller
151
- ├── mailers/rodauth/
152
- │ └── user_mailer.rb # Account-specific mailer
153
- ├── models/
154
- │ └── user.rb # Account model
155
- ├── rodauth/
156
- │ ├── rodauth_app.rb # Main Roda app
157
- │ ├── rodauth_plugin.rb # Base plugin
158
- │ └── user_rodauth_plugin.rb # Account-specific config
159
- ├── policies/
160
- │ └── user_policy.rb # Account policy
161
- ├── definitions/
162
- │ └── user_definition.rb # Account definition
163
- └── views/rodauth/
164
- └── user_mailer/ # Email templates
165
- db/migrate/
166
- └── xxx_create_users.rb # Account table migration
82
+ POST /api_users/login
83
+ {"login": "user@example.com", "password": "secret"}
84
+ # {"access_token": "...", "refresh_token": "..."}
167
85
  ```
168
86
 
169
- ## Connecting Auth to Controllers
87
+ ## Connecting a portal to an account type
170
88
 
171
- Include the auth module in your controller to require authentication:
89
+ If you create the portal with `--auth=`, it's wired automatically:
172
90
 
173
- ```ruby
174
- # app/controllers/resource_controller.rb
175
- class ResourceController < PlutoniumController
176
- include Plutonium::Resource::Controller
177
- include Plutonium::Auth::Rodauth(:user)
178
- end
91
+ ```bash
92
+ rails generate pu:pkg:portal customer --auth=user
179
93
  ```
180
94
 
181
- This provides:
182
-
183
- | Method | Description |
184
- |--------|-------------|
185
- | `current_user` | The authenticated account |
186
- | `logout_url` | URL to logout |
187
- | `rodauth` | Access to Rodauth instance |
188
-
189
- ### Portal Configuration
190
-
191
- For portals, include the auth module in the controller concern:
95
+ Manually, edit the portal's controller concern:
192
96
 
193
97
  ```ruby
194
- # packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
195
- module AdminPortal
196
- module Concerns
197
- module Controller
198
- extend ActiveSupport::Concern
199
- include Plutonium::Portal::Controller
200
- include Plutonium::Auth::Rodauth(:admin)
201
- end
202
- end
98
+ # packages/customer_portal/app/controllers/customer_portal/concerns/controller.rb
99
+ module CustomerPortal::Concerns::Controller
100
+ extend ActiveSupport::Concern
101
+ include Plutonium::Portal::Controller
102
+ include Plutonium::Auth::Rodauth(:user)
203
103
  end
204
104
  ```
205
105
 
206
- ## Accessing the Current User
106
+ Multiple account types different portals use different Rodauth instances:
207
107
 
208
108
  ```ruby
209
- # In controllers
210
- def index
211
- @user_posts = current_user.posts
212
- end
109
+ # Admin portal
110
+ include Plutonium::Auth::Rodauth(:admin)
213
111
 
214
- # In views (helper method)
215
- <% if current_user.present? %>
216
- Welcome, <%= current_user.email %>
217
- <% end %>
112
+ # Customer portal
113
+ include Plutonium::Auth::Rodauth(:user)
218
114
  ```
219
115
 
220
- ## Rodauth Plugin Configuration
221
-
222
- The generated plugin file contains configuration options:
223
-
224
- ```ruby
225
- # app/rodauth/user_rodauth_plugin.rb
226
- class UserRodauthPlugin < RodauthPlugin
227
- configure do
228
- # Features enabled for this account
229
- enable :login, :logout, :remember, :create_account, ...
230
-
231
- # URL prefix (non-primary accounts)
232
- prefix "/users"
233
-
234
- # Store password in column (not separate table)
235
- account_password_hash_column :password_hash
116
+ See [Reference App › Portals](/reference/app/portals#controller-concern-auth).
236
117
 
237
- # Controller for views
238
- rails_controller { Rodauth::UserController }
118
+ ## Customizing the auth flow
239
119
 
240
- # Model
241
- rails_account_model { User }
120
+ All inside `app/rodauth/<name>_rodauth_plugin.rb`, in the `configure do` block:
242
121
 
243
- # Redirects
244
- login_redirect "/"
245
- logout_redirect "/"
246
-
247
- # Session configuration
248
- session_key "_user_session"
249
- remember_cookie_key "_user_remember"
250
- end
251
- end
252
- ```
253
-
254
- ### Custom Login Redirect
122
+ ### Custom login redirect
255
123
 
256
124
  ```ruby
257
- configure do
258
- login_redirect { "/dashboard" }
259
-
260
- # Or dynamically
261
- login_redirect do
262
- if rails_account.admin?
263
- "/admin"
264
- else
265
- "/dashboard"
266
- end
267
- end
125
+ login_redirect do
126
+ rails_account.admin? ? "/admin" : "/dashboard"
268
127
  end
269
128
  ```
270
129
 
271
- ### Password Requirements
130
+ ### After-create hook (e.g. create a profile)
272
131
 
273
132
  ```ruby
274
- configure do
275
- # Minimum length (default: 8)
276
- password_minimum_length 12
277
-
278
- # Custom complexity
279
- password_meets_requirements? do |password|
280
- super(password) && password.match?(/\d/) && password.match?(/[^a-zA-Z\d]/)
281
- end
133
+ after_create_account do
134
+ Profile.create!(account_id: account_id, name: param("name"))
282
135
  end
283
136
  ```
284
137
 
285
- ### Multi-Phase Login
138
+ ### Password requirements
286
139
 
287
140
  ```ruby
288
- configure do
289
- # Ask for email first, then password
290
- use_multi_phase_login? true
141
+ password_minimum_length 12
142
+
143
+ password_meets_requirements? do |password|
144
+ super(password) && password.match?(/\d/) && password.match?(/[^a-zA-Z\d]/)
291
145
  end
292
146
  ```
293
147
 
294
- ### Prevent Public Signup
148
+ ### Prevent public signup
295
149
 
296
150
  ```ruby
297
- configure do
298
- before_create_account_route do
299
- request.halt unless internal_request?
300
- end
151
+ before_create_account_route do
152
+ request.halt unless internal_request?
301
153
  end
302
154
  ```
303
155
 
304
- ## Email Configuration
156
+ Full customization surface: [Reference › Auth › Accounts › Common customizations](/reference/auth/accounts#common-customizations).
305
157
 
306
- Emails are sent via Action Mailer.
307
-
308
- ### Development
309
-
310
- ```ruby
311
- # Gemfile
312
- gem "letter_opener", group: :development
313
-
314
- # config/environments/development.rb
315
- config.action_mailer.delivery_method = :letter_opener
316
- config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
317
- ```
318
-
319
- ### Production
158
+ ## Email setup (production)
320
159
 
321
160
  ```ruby
322
161
  # config/environments/production.rb
323
162
  config.action_mailer.delivery_method = :smtp
324
163
  config.action_mailer.smtp_settings = {
325
- address: ENV['SMTP_HOST'],
326
- port: ENV['SMTP_PORT'],
327
- user_name: ENV['SMTP_USER'],
328
- password: ENV['SMTP_PASSWORD']
164
+ address: "smtp.example.com",
165
+ port: 587,
166
+ user_name: ENV["SMTP_USER"],
167
+ password: ENV["SMTP_PASSWORD"]
329
168
  }
330
169
  ```
331
170
 
332
- ### Custom Email Templates
333
-
334
- Override templates in `app/views/rodauth/user_mailer/`:
335
-
336
- ```erb
337
- <%# app/views/rodauth/user_mailer/reset_password.text.erb %>
338
- Hi <%= @account.email %>,
339
-
340
- Someone requested a password reset for your account.
341
-
342
- Reset your password: <%= @reset_password_url %>
343
-
344
- If you didn't request this, ignore this email.
345
- ```
346
-
347
- ## Customizing Views
348
-
349
- Generate views to customize:
350
-
351
- ```bash
352
- # Generate views for specific features
353
- rails g pu:rodauth:views user --features login create_account reset_password
354
-
355
- # Generate all views
356
- rails g pu:rodauth:views user --all
357
- ```
358
-
359
- Views are copied to `app/views/rodauth/user/` and can be customized as standard ERB templates.
360
-
361
- ## Multiple Account Types
362
-
363
- ### Different Portals, Different Accounts
364
-
365
- ```ruby
366
- # packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
367
- module AdminPortal
368
- module Concerns
369
- module Controller
370
- extend ActiveSupport::Concern
371
- include Plutonium::Portal::Controller
372
- include Plutonium::Auth::Rodauth(:admin)
373
- end
374
- end
375
- end
376
-
377
- # packages/customer_portal/app/controllers/customer_portal/concerns/controller.rb
378
- module CustomerPortal
379
- module Concerns
380
- module Controller
381
- extend ActiveSupport::Concern
382
- include Plutonium::Portal::Controller
383
- include Plutonium::Auth::Rodauth(:customer)
384
- end
385
- end
386
- end
387
- ```
388
-
389
- ### Shared Account Type
390
-
391
- Multiple portals can share an account type:
392
-
393
- ```ruby
394
- # Both portals include the same auth module
395
- include Plutonium::Auth::Rodauth(:user)
396
- ```
397
-
398
- ## Public Portals
399
-
400
- For portals that don't require authentication, use `Plutonium::Auth::Public`:
401
-
402
- ```ruby
403
- # packages/public_portal/app/controllers/public_portal/concerns/controller.rb
404
- module PublicPortal
405
- module Concerns
406
- module Controller
407
- extend ActiveSupport::Concern
408
- include Plutonium::Portal::Controller
409
- include Plutonium::Auth::Public
410
- end
411
- end
412
- end
413
- ```
414
-
415
- This provides a `current_user` method that returns `"Guest"`.
416
-
417
- ## Two-Factor Authentication
418
-
419
- ### Enable During Generation
420
-
421
- ```bash
422
- rails g pu:rodauth:account user --otp --recovery_codes
423
- ```
424
-
425
- ### Add to Existing Account
426
-
427
- ```ruby
428
- # app/rodauth/user_rodauth_plugin.rb
429
- configure do
430
- enable :otp, :recovery_codes
431
-
432
- # Require 2FA
433
- two_factor_auth_required? true
434
- end
435
- ```
436
-
437
- Note: The `pu:rodauth:admin` generator automatically enables OTP and recovery codes.
438
-
439
- ## Creating Accounts
440
-
441
- ### Admin Accounts
442
-
443
- Admin accounts are created via rake task (web registration is disabled):
444
-
445
- ```bash
446
- # Interactive prompt for email
447
- rails rodauth:admin
448
-
449
- # With EMAIL environment variable
450
- EMAIL=admin@example.com rails rodauth:admin
451
- ```
452
-
453
- The task name matches the account name (e.g., `rails rodauth:admin` for an account named `admin`).
454
-
455
- ### Programmatic Account Creation
456
-
457
- For accounts with self-registration enabled, use internal requests:
458
-
459
- ```ruby
460
- # Create account via internal request
461
- RodauthApp.rodauth(:user).create_account(
462
- login: "user@example.com",
463
- password: "secure_password"
464
- )
465
- ```
466
-
467
- In seeds:
468
-
469
- ```ruby
470
- # db/seeds.rb
471
- RodauthApp.rodauth(:user).create_account(
472
- login: "user@example.com",
473
- password: "password123"
474
- )
475
- ```
476
-
477
- ## API Authentication
478
-
479
- For JSON API authentication:
480
-
481
- ```bash
482
- rails g pu:rodauth:account api_user --api_only --jwt --jwt_refresh
483
- ```
484
-
485
- This enables:
486
- - JWT token authentication
487
- - Refresh tokens
488
- - No session/cookie handling
489
-
490
- ### Using JWT
491
-
492
- ```bash
493
- # Login
494
- curl -X POST http://localhost:3000/api_users/login \
495
- -H "Content-Type: application/json" \
496
- -d '{"login": "user@example.com", "password": "secret"}'
497
-
498
- # Response includes tokens
499
- {"access_token": "...", "refresh_token": "..."}
500
-
501
- # Authenticated requests
502
- curl http://localhost:3000/api/posts \
503
- -H "Authorization: Bearer <access_token>"
504
- ```
505
-
506
- ## Troubleshooting
507
-
508
- ### Routes Not Working
171
+ Override mailer templates in `app/views/rodauth/<account>_mailer/`.
509
172
 
510
- Restart the server after installing Rodauth:
511
-
512
- ```bash
513
- bin/rails restart
514
- ```
515
-
516
- ### Emails Not Sending
517
-
518
- Check Action Mailer configuration:
173
+ ## Accessing the current user
519
174
 
520
175
  ```ruby
521
- # Verify mailer config
522
- Rails.application.config.action_mailer.delivery_method
523
- Rails.application.config.action_mailer.default_url_options
524
- ```
525
-
526
- Use letter_opener in development to view emails in browser.
527
-
528
- ### Session Issues
176
+ # Controllers / views
177
+ current_user
529
178
 
530
- Clear session cookies in the browser, or for active_sessions feature:
531
-
532
- ```ruby
533
- # In rails runner
534
- User.find_by(email: "user@example.com").active_session_keys.delete_all
179
+ # Policies
180
+ user
535
181
  ```
536
182
 
537
- ### Migration Issues
183
+ ## Common issues
538
184
 
539
- Ensure all migrations have run:
540
-
541
- ```bash
542
- rails db:migrate:status
543
- rails db:migrate
544
- ```
545
-
546
- ### Account Not Verified
547
-
548
- For development, you can manually verify accounts:
549
-
550
- ```ruby
551
- # In rails runner
552
- user = User.find_by(email: "user@example.com")
553
- user.update!(status: 2) # 2 = verified
554
- ```
185
+ - **"You need to set up Rodauth"** — run `pu:rodauth:install` first.
186
+ - **Portal redirects to login even though you're authenticated** — the portal mount constraint references a different Rodauth account than the portal's controller concern uses. Match them up.
187
+ - **Email confirmation never arrives in development** — Plutonium sets ActionMailer to `:test` by default. Check `tmp/letter_opener/` or your mail interceptor. In production, configure SMTP (see above).
555
188
 
556
189
  ## Related
557
190
 
558
- - [Authorization](./authorization)
559
- - [Multi-tenancy](./multi-tenancy)
191
+ - [Reference › Auth](/reference/auth/) — full auth surface
192
+ - [Authorization](./authorization) — controlling who can do what AFTER login
193
+ - [Multi-tenancy](./multi-tenancy) — entity scoping for SaaS apps
194
+ - [User invites](./user-invites) — invitation-based onboarding
195
+ - [User profile](./user-profile) — account-settings page