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.
- checksums.yaml +4 -4
- data/.claude/skills/plutonium/SKILL.md +85 -102
- data/.claude/skills/plutonium-app/SKILL.md +574 -0
- data/.claude/skills/plutonium-auth/SKILL.md +167 -302
- 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 +674 -0
- data/.claude/skills/plutonium-testing/SKILL.md +9 -6
- data/.claude/skills/plutonium-ui/SKILL.md +900 -0
- data/CHANGELOG.md +44 -2
- data/Rakefile +2 -1
- data/app/assets/plutonium.css +1 -11
- data/app/assets/plutonium.js +1010 -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 +38 -29
- data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
- data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
- data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
- data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
- data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
- data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
- data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
- data/docs/.vitepress/theme/custom.css +144 -0
- data/docs/.vitepress/theme/index.ts +58 -1
- data/docs/getting-started/index.md +33 -57
- data/docs/getting-started/installation.md +37 -80
- data/docs/getting-started/tutorial/02-first-resource.md +17 -8
- data/docs/getting-started/tutorial/03-authentication.md +31 -23
- data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
- data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
- data/docs/getting-started/tutorial/07-author-portal.md +8 -0
- data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
- data/docs/getting-started/tutorial/index.md +4 -5
- data/docs/guides/adding-resources.md +66 -377
- data/docs/guides/authentication.md +98 -462
- data/docs/guides/authorization.md +124 -370
- data/docs/guides/creating-packages.md +93 -298
- data/docs/guides/custom-actions.md +126 -441
- data/docs/guides/customizing-ui.md +258 -0
- data/docs/guides/index.md +49 -52
- data/docs/guides/multi-tenancy.md +123 -186
- data/docs/guides/nested-resources.md +137 -396
- data/docs/guides/search-filtering.md +127 -238
- data/docs/guides/testing.md +10 -5
- data/docs/guides/theming.md +168 -405
- data/docs/guides/troubleshooting.md +5 -3
- data/docs/guides/user-invites.md +112 -425
- data/docs/guides/user-profile.md +82 -241
- data/docs/index.md +10 -219
- data/docs/public/asciinema/home-scaffold.cast +305 -0
- data/docs/public/images/guides/custom-actions-bulk.png +0 -0
- data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
- data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
- data/docs/public/images/guides/nested-inputs.png +0 -0
- data/docs/public/images/guides/nested-resources-tab.png +0 -0
- data/docs/public/images/guides/search-filtering-index.png +0 -0
- data/docs/public/images/guides/search-filtering-panel.png +0 -0
- data/docs/public/images/guides/theming-after.png +0 -0
- data/docs/public/images/guides/theming-before.png +0 -0
- data/docs/public/images/guides/user-invites-landing.png +0 -0
- data/docs/public/images/guides/user-profile-edit.png +0 -0
- data/docs/public/images/guides/user-profile-show.png +0 -0
- data/docs/public/images/home-index.png +0 -0
- data/docs/public/images/home-new.png +0 -0
- data/docs/public/images/home-show.png +0 -0
- data/docs/public/images/tutorial/02-empty-index.png +0 -0
- data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
- data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
- data/docs/public/images/tutorial/02-new-form.png +0 -0
- data/docs/public/images/tutorial/03-create-account.png +0 -0
- data/docs/public/images/tutorial/03-login.png +0 -0
- data/docs/public/images/tutorial/04-admin-index.png +0 -0
- data/docs/public/images/tutorial/05-actions-menu.png +0 -0
- data/docs/public/images/tutorial/05-row-actions.png +0 -0
- data/docs/public/images/tutorial/06-comments-tab.png +0 -0
- data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
- data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
- data/docs/public/images/tutorial/07-author-portal.png +0 -0
- data/docs/public/images/tutorial/08-customized-index.png +0 -0
- 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 +229 -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 +67 -48
- 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 +368 -0
- data/docs/reference/tenancy/index.md +36 -0
- data/docs/reference/tenancy/invites.md +400 -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 +121 -0
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -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/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -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/assets/assets_generator.rb +10 -0
- data/lib/generators/pu/core/update/update_generator.rb +0 -20
- data/lib/generators/pu/invites/install_generator.rb +45 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
- data/lib/generators/pu/profile/conn_generator.rb +2 -2
- data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
- data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
- data/lib/generators/pu/rodauth/account_generator.rb +2 -1
- data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
- data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
- data/lib/generators/pu/rodauth/views_generator.rb +0 -2
- data/lib/generators/pu/saas/membership/USAGE +4 -1
- data/lib/generators/pu/saas/setup_generator.rb +16 -4
- data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
- 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 +30 -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 +23 -5
- data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
- 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 +5 -0
- data/lib/plutonium/ui/form/base.rb +23 -3
- 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 +103 -22
- data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
- data/lib/plutonium/ui/form/interaction.rb +1 -1
- data/lib/plutonium/ui/form/resource.rb +0 -4
- data/lib/plutonium/ui/form/theme.rb +1 -1
- 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/edit.rb +1 -1
- data/lib/plutonium/ui/page/index.rb +4 -4
- data/lib/plutonium/ui/page/new.rb +1 -1
- data/lib/plutonium/ui/table/components/filter_form.rb +12 -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 +13 -10
- data/src/css/slim_select.css +4 -0
- data/src/js/controllers/form_controller.js +5 -4
- data/src/js/controllers/slim_select_controller.js +61 -0
- data/src/js/turbo/turbo_actions.js +33 -0
- data/yarn.lock +661 -544
- metadata +86 -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,559 +1,195 @@
|
|
|
1
1
|
# Authentication
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Add Rodauth-based authentication to your Plutonium app.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Goal
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
##
|
|
16
|
-
|
|
17
|
-
### New Applications
|
|
18
|
-
|
|
19
|
-
The Plutonium template installs Rodauth automatically:
|
|
9
|
+
## Quick path — basic user auth
|
|
20
10
|
|
|
21
11
|
```bash
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
```
|
|
12
|
+
# 1. Install Rodauth
|
|
13
|
+
rails generate pu:rodauth:install
|
|
25
14
|
|
|
26
|
-
|
|
15
|
+
# 2. Create a user account type
|
|
16
|
+
rails generate pu:rodauth:account user
|
|
27
17
|
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
42
|
+
### Multi-factor auth (TOTP)
|
|
58
43
|
|
|
59
44
|
```bash
|
|
60
|
-
rails
|
|
61
|
-
rails db:migrate
|
|
45
|
+
rails generate pu:rodauth:account user --otp --recovery_codes
|
|
62
46
|
```
|
|
63
47
|
|
|
64
|
-
|
|
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
|
-
###
|
|
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
|
|
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
|
-
|
|
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
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
67
|
+
### Multi-tenant SaaS — user + entity + membership in one shot
|
|
97
68
|
|
|
98
69
|
```bash
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
rails g pu:rodauth:account user --argon2
|
|
75
|
+
### API-only (JWT)
|
|
116
76
|
|
|
117
|
-
|
|
118
|
-
rails
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
87
|
+
## Connecting a portal to an account type
|
|
170
88
|
|
|
171
|
-
|
|
89
|
+
If you create the portal with `--auth=`, it's wired automatically:
|
|
172
90
|
|
|
173
|
-
```
|
|
174
|
-
|
|
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
|
-
|
|
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/
|
|
195
|
-
module
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
106
|
+
Multiple account types — different portals use different Rodauth instances:
|
|
207
107
|
|
|
208
108
|
```ruby
|
|
209
|
-
#
|
|
210
|
-
|
|
211
|
-
@user_posts = current_user.posts
|
|
212
|
-
end
|
|
109
|
+
# Admin portal
|
|
110
|
+
include Plutonium::Auth::Rodauth(:admin)
|
|
213
111
|
|
|
214
|
-
#
|
|
215
|
-
|
|
216
|
-
Welcome, <%= current_user.email %>
|
|
217
|
-
<% end %>
|
|
112
|
+
# Customer portal
|
|
113
|
+
include Plutonium::Auth::Rodauth(:user)
|
|
218
114
|
```
|
|
219
115
|
|
|
220
|
-
|
|
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
|
-
|
|
238
|
-
rails_controller { Rodauth::UserController }
|
|
118
|
+
## Customizing the auth flow
|
|
239
119
|
|
|
240
|
-
|
|
241
|
-
rails_account_model { User }
|
|
120
|
+
All inside `app/rodauth/<name>_rodauth_plugin.rb`, in the `configure do` block:
|
|
242
121
|
|
|
243
|
-
|
|
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
|
-
|
|
258
|
-
|
|
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
|
-
###
|
|
130
|
+
### After-create hook (e.g. create a profile)
|
|
272
131
|
|
|
273
132
|
```ruby
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
###
|
|
138
|
+
### Password requirements
|
|
286
139
|
|
|
287
140
|
```ruby
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
|
148
|
+
### Prevent public signup
|
|
295
149
|
|
|
296
150
|
```ruby
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
156
|
+
Full customization surface: [Reference › Auth › Accounts › Common customizations](/reference/auth/accounts#common-customizations).
|
|
305
157
|
|
|
306
|
-
|
|
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:
|
|
326
|
-
port:
|
|
327
|
-
user_name: ENV[
|
|
328
|
-
password: ENV[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
522
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
+
## Common issues
|
|
538
184
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
- [
|
|
559
|
-
- [
|
|
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
|