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.
- checksums.yaml +4 -4
- data/.claude/skills/plutonium/SKILL.md +85 -102
- data/.claude/skills/plutonium-app/SKILL.md +572 -0
- data/.claude/skills/plutonium-auth/SKILL.md +163 -300
- data/.claude/skills/plutonium-behavior/SKILL.md +838 -0
- data/.claude/skills/plutonium-resource/SKILL.md +1176 -0
- data/.claude/skills/plutonium-tenancy/SKILL.md +655 -0
- data/.claude/skills/plutonium-testing/SKILL.md +6 -5
- data/.claude/skills/plutonium-ui/SKILL.md +900 -0
- data/CHANGELOG.md +27 -2
- data/Rakefile +2 -1
- data/app/assets/plutonium.css +1 -11
- data/app/assets/plutonium.js +1009 -1214
- data/app/assets/plutonium.js.map +3 -3
- data/app/assets/plutonium.min.js +52 -51
- data/app/assets/plutonium.min.js.map +3 -3
- data/docs/.vitepress/config.ts +37 -27
- data/docs/getting-started/index.md +22 -29
- data/docs/getting-started/installation.md +37 -80
- data/docs/getting-started/tutorial/index.md +4 -5
- data/docs/guides/adding-resources.md +66 -377
- data/docs/guides/authentication.md +94 -463
- data/docs/guides/authorization.md +124 -370
- data/docs/guides/creating-packages.md +94 -296
- data/docs/guides/custom-actions.md +121 -441
- data/docs/guides/index.md +22 -42
- data/docs/guides/multi-tenancy.md +116 -187
- data/docs/guides/nested-resources.md +103 -431
- data/docs/guides/search-filtering.md +123 -240
- data/docs/guides/testing.md +5 -4
- data/docs/guides/theming.md +157 -407
- data/docs/guides/troubleshooting.md +5 -3
- data/docs/guides/user-invites.md +106 -425
- data/docs/guides/user-profile.md +76 -243
- data/docs/index.md +1 -1
- data/docs/reference/app/generators.md +517 -0
- data/docs/reference/app/index.md +158 -0
- data/docs/reference/app/packages.md +146 -0
- data/docs/reference/app/portals.md +377 -0
- data/docs/reference/auth/accounts.md +230 -0
- data/docs/reference/auth/index.md +88 -0
- data/docs/reference/auth/profile.md +185 -0
- data/docs/reference/behavior/controllers.md +395 -0
- data/docs/reference/behavior/index.md +22 -0
- data/docs/reference/behavior/interactions.md +341 -0
- data/docs/reference/behavior/policies.md +417 -0
- data/docs/reference/index.md +56 -49
- data/docs/reference/resource/actions.md +423 -0
- data/docs/reference/resource/definition.md +508 -0
- data/docs/reference/resource/index.md +50 -0
- data/docs/reference/resource/model.md +348 -0
- data/docs/reference/resource/query.md +305 -0
- data/docs/reference/tenancy/entity-scoping.md +361 -0
- data/docs/reference/tenancy/index.md +36 -0
- data/docs/reference/tenancy/invites.md +393 -0
- data/docs/reference/tenancy/nested-resources.md +267 -0
- data/docs/reference/testing/index.md +287 -0
- data/docs/reference/ui/assets.md +400 -0
- data/docs/reference/ui/components.md +165 -0
- data/docs/reference/ui/displays.md +104 -0
- data/docs/reference/ui/forms.md +284 -0
- data/docs/reference/ui/index.md +30 -0
- data/docs/reference/ui/layouts.md +106 -0
- data/docs/reference/ui/pages.md +189 -0
- data/docs/reference/ui/tables.md +117 -0
- data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
- data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
- data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/core/update/update_generator.rb +0 -20
- data/lib/generators/pu/invites/install_generator.rb +1 -0
- data/lib/plutonium/definition/base.rb +1 -1
- data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
- data/lib/plutonium/helpers/turbo_helper.rb +11 -0
- data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
- data/lib/plutonium/resource/controller.rb +1 -0
- data/lib/plutonium/resource/controllers/crud_actions.rb +19 -1
- data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
- data/lib/plutonium/resource/policy.rb +7 -0
- data/lib/plutonium/routing/mapper_extensions.rb +15 -0
- data/lib/plutonium/ui/component/methods.rb +4 -0
- data/lib/plutonium/ui/form/base.rb +6 -2
- data/lib/plutonium/ui/form/components/json.rb +58 -0
- data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
- data/lib/plutonium/ui/form/components/secure_association.rb +98 -22
- data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
- data/lib/plutonium/ui/form/resource.rb +0 -4
- data/lib/plutonium/ui/grid/resource.rb +1 -1
- data/lib/plutonium/ui/layout/base.rb +1 -0
- data/lib/plutonium/ui/page/base.rb +0 -7
- data/lib/plutonium/ui/page/index.rb +4 -4
- data/lib/plutonium/ui/table/resource.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium.rb +8 -0
- data/lib/tasks/release.rake +15 -1
- data/package.json +10 -10
- data/src/css/slim_select.css +4 -0
- data/src/js/controllers/slim_select_controller.js +61 -0
- data/src/js/turbo/turbo_actions.js +33 -0
- data/yarn.lock +553 -543
- metadata +44 -33
- data/.claude/skills/plutonium-assets/SKILL.md +0 -512
- data/.claude/skills/plutonium-controller/SKILL.md +0 -396
- data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
- data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
- data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
- data/.claude/skills/plutonium-forms/SKILL.md +0 -465
- data/.claude/skills/plutonium-installation/SKILL.md +0 -331
- data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
- data/.claude/skills/plutonium-invites/SKILL.md +0 -408
- data/.claude/skills/plutonium-model/SKILL.md +0 -440
- data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
- data/.claude/skills/plutonium-package/SKILL.md +0 -198
- data/.claude/skills/plutonium-policy/SKILL.md +0 -456
- data/.claude/skills/plutonium-portal/SKILL.md +0 -410
- data/.claude/skills/plutonium-views/SKILL.md +0 -651
- data/docs/reference/assets/index.md +0 -496
- data/docs/reference/controller/index.md +0 -412
- data/docs/reference/definition/actions.md +0 -462
- data/docs/reference/definition/fields.md +0 -383
- data/docs/reference/definition/index.md +0 -326
- data/docs/reference/definition/query.md +0 -351
- data/docs/reference/generators/index.md +0 -648
- data/docs/reference/interaction/index.md +0 -449
- data/docs/reference/model/features.md +0 -248
- data/docs/reference/model/index.md +0 -218
- data/docs/reference/policy/index.md +0 -456
- data/docs/reference/portal/index.md +0 -379
- data/docs/reference/views/forms.md +0 -411
- data/docs/reference/views/index.md +0 -544
|
@@ -1,107 +1,77 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-auth
|
|
3
|
-
description: Use BEFORE
|
|
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
|
|
6
|
+
# Plutonium Auth — Rodauth + Profile
|
|
7
7
|
|
|
8
|
-
|
|
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
|
-
|
|
10
|
+
For multi-tenant invitations and membership, see [[plutonium-tenancy]] › Invites. For portal-side wiring, see [[plutonium-app]] › Portal Engines.
|
|
16
11
|
|
|
17
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Install
|
|
31
25
|
|
|
32
26
|
```bash
|
|
33
27
|
rails generate pu:rodauth:install
|
|
34
28
|
```
|
|
35
29
|
|
|
36
|
-
|
|
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
|
-
|
|
55
|
-
rails generate pu:saas:setup --user Customer --entity Organization
|
|
56
|
-
```
|
|
32
|
+
---
|
|
57
33
|
|
|
58
34
|
## Account types
|
|
59
35
|
|
|
60
|
-
|
|
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` |
|
|
71
|
-
| `--kitchen_sink` |
|
|
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
|
|
75
|
-
| `--api_only` |
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
|
80
|
-
|
|
81
|
-
| `--login` | ✓ |
|
|
82
|
-
| `--
|
|
83
|
-
| `--
|
|
84
|
-
| `--
|
|
85
|
-
| `--
|
|
86
|
-
| `--
|
|
87
|
-
| `--
|
|
88
|
-
| `--
|
|
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` | |
|
|
66
|
+
| `--audit_logging` | | Log auth events |
|
|
96
67
|
| `--close_account` | | Allow account deletion |
|
|
97
|
-
| `--email_auth` | | Passwordless login
|
|
98
|
-
| `--sms_codes` | | SMS
|
|
99
|
-
| `--jwt` | | JWT
|
|
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
|
|
72
|
+
### Admin account — `pu:rodauth:admin`
|
|
103
73
|
|
|
104
|
-
|
|
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
|
|
117
|
-
| `--extra_attributes` | | Additional model attributes (e.g
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
152
|
-
rails
|
|
153
|
-
rails
|
|
154
|
-
rails
|
|
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
|
|
165
|
-
| `--skip-entity` |
|
|
166
|
-
| `--skip-membership` |
|
|
167
|
-
| `--user-attributes` | |
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
171
|
+
`Plutonium::Auth::Rodauth(:name)` exposes `current_user`, `logout_url`, and `rodauth` in the controller.
|
|
214
172
|
|
|
215
|
-
|
|
173
|
+
For portal wiring (`AdminPortal::Concerns::Controller`), see [[plutonium-app]] › Portal controller concern.
|
|
216
174
|
|
|
217
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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
|
|
189
|
+
### Custom create-account validation + hook
|
|
232
190
|
|
|
233
191
|
```ruby
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
|
201
|
+
### Password requirements
|
|
246
202
|
|
|
247
203
|
```ruby
|
|
248
|
-
|
|
249
|
-
password_minimum_length 12
|
|
204
|
+
password_minimum_length 12
|
|
250
205
|
|
|
251
|
-
|
|
252
|
-
|
|
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-
|
|
211
|
+
### Multi-phase login (password on a separate page)
|
|
258
212
|
|
|
259
213
|
```ruby
|
|
260
|
-
|
|
261
|
-
use_multi_phase_login? true
|
|
262
|
-
end
|
|
214
|
+
use_multi_phase_login? true
|
|
263
215
|
```
|
|
264
216
|
|
|
265
|
-
### Prevent
|
|
217
|
+
### Prevent public signup (admin pattern)
|
|
266
218
|
|
|
267
219
|
```ruby
|
|
268
|
-
|
|
269
|
-
|
|
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
|
|
289
|
-
|
|
290
|
-
## Portal integration
|
|
241
|
+
Override templates in `app/views/rodauth/<account>_mailer/`.
|
|
291
242
|
|
|
292
|
-
|
|
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
|
-
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Profile resource
|
|
338
263
|
|
|
339
|
-
|
|
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
|
-
|
|
346
|
-
|
|
270
|
+
--dest=competition \
|
|
271
|
+
--portal=competition_portal
|
|
347
272
|
```
|
|
348
273
|
|
|
349
|
-
### Step-by-step
|
|
274
|
+
### Step-by-step
|
|
350
275
|
|
|
351
276
|
```bash
|
|
352
|
-
rails generate pu:profile:install
|
|
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
|
-
|
|
280
|
+
rails db:migrate
|
|
361
281
|
|
|
362
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
382
|
-
db/migrate/xxx_create_user_profiles.rb
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
332
|
+
To customize the show page (e.g. wrap, reorder), override `ShowPage#render_after_content` (see [[plutonium-ui]] › Page hooks).
|
|
420
333
|
|
|
421
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
474
|
-
- **`owner` is always prepended**
|
|
475
|
-
- **Profile association is always `:profile
|
|
476
|
-
- **`pu:saas:setup`
|
|
477
|
-
- **Profile requires
|
|
478
|
-
- **Users need a profile row.** Add an `after_create` callback or
|
|
479
|
-
|
|
480
|
-
|
|
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
|
-
-
|
|
507
|
-
-
|
|
508
|
-
-
|
|
509
|
-
-
|
|
510
|
-
-
|
|
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
|