plutonium 0.33.1 → 0.34.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/# Plutonium: The pre-alpha demo.md +4 -2
- data/.claude/skills/assets/SKILL.md +416 -0
- data/.claude/skills/connect-resource/SKILL.md +112 -0
- data/.claude/skills/controller/SKILL.md +302 -0
- data/.claude/skills/create-resource/SKILL.md +240 -0
- data/.claude/skills/definition/SKILL.md +218 -0
- data/.claude/skills/definition-actions/SKILL.md +386 -0
- data/.claude/skills/definition-fields/SKILL.md +474 -0
- data/.claude/skills/definition-query/SKILL.md +334 -0
- data/.claude/skills/forms/SKILL.md +439 -0
- data/.claude/skills/installation/SKILL.md +300 -0
- data/.claude/skills/interaction/SKILL.md +382 -0
- data/.claude/skills/model/SKILL.md +267 -0
- data/.claude/skills/model-features/SKILL.md +286 -0
- data/.claude/skills/nested-resources/SKILL.md +274 -0
- data/.claude/skills/package/SKILL.md +191 -0
- data/.claude/skills/policy/SKILL.md +352 -0
- data/.claude/skills/portal/SKILL.md +400 -0
- data/.claude/skills/resource/SKILL.md +281 -0
- data/.claude/skills/rodauth/SKILL.md +452 -0
- data/.claude/skills/views/SKILL.md +563 -0
- data/Appraisals +46 -4
- data/CHANGELOG.md +32 -1
- data/app/assets/plutonium.css +2 -2
- data/config/brakeman.ignore +239 -0
- data/config/initializers/action_policy.rb +1 -1
- data/docs/.vitepress/config.ts +132 -47
- data/docs/concepts/architecture.md +226 -0
- data/docs/concepts/auto-detection.md +254 -0
- data/docs/concepts/index.md +61 -0
- data/docs/concepts/packages-portals.md +304 -0
- data/docs/concepts/resources.md +224 -0
- data/docs/cookbook/blog.md +412 -0
- data/docs/cookbook/index.md +289 -0
- data/docs/cookbook/saas.md +481 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +146 -0
- data/docs/getting-started/tutorial/01-setup.md +118 -0
- data/docs/getting-started/tutorial/02-first-resource.md +180 -0
- data/docs/getting-started/tutorial/03-authentication.md +246 -0
- data/docs/getting-started/tutorial/04-authorization.md +170 -0
- data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
- data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
- data/docs/getting-started/tutorial/index.md +64 -0
- data/docs/guides/adding-resources.md +420 -0
- data/docs/guides/authentication.md +551 -0
- data/docs/guides/authorization.md +468 -0
- data/docs/guides/creating-packages.md +380 -0
- data/docs/guides/custom-actions.md +523 -0
- data/docs/guides/index.md +45 -0
- data/docs/guides/multi-tenancy.md +302 -0
- data/docs/guides/nested-resources.md +411 -0
- data/docs/guides/search-filtering.md +266 -0
- data/docs/guides/theming.md +321 -0
- data/docs/index.md +67 -26
- data/docs/public/CLAUDE.md +64 -21
- data/docs/reference/assets/index.md +496 -0
- data/docs/reference/controller/index.md +363 -0
- data/docs/reference/definition/actions.md +400 -0
- data/docs/reference/definition/fields.md +350 -0
- data/docs/reference/definition/index.md +252 -0
- data/docs/reference/definition/query.md +342 -0
- data/docs/reference/generators/index.md +469 -0
- data/docs/reference/index.md +49 -0
- data/docs/reference/interaction/index.md +445 -0
- data/docs/reference/model/features.md +248 -0
- data/docs/reference/model/index.md +219 -0
- data/docs/reference/policy/index.md +385 -0
- data/docs/reference/portal/index.md +382 -0
- data/docs/reference/views/forms.md +396 -0
- data/docs/reference/views/index.md +479 -0
- data/gemfiles/rails_7.gemfile +9 -2
- data/gemfiles/rails_7.gemfile.lock +146 -111
- data/gemfiles/rails_8.0.gemfile +20 -0
- data/gemfiles/rails_8.0.gemfile.lock +417 -0
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/gemfiles/rails_8.1.gemfile.lock +419 -0
- data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
- data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
- data/lib/generators/pu/pkg/portal/USAGE +65 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
- data/lib/generators/pu/res/conn/USAGE +71 -0
- data/lib/generators/pu/res/model/USAGE +106 -110
- data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
- data/lib/generators/pu/res/scaffold/USAGE +85 -0
- data/lib/generators/pu/rodauth/install_generator.rb +2 -6
- data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
- data/lib/generators/pu/skills/sync/USAGE +14 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
- data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/interaction/base.rb +1 -0
- data/lib/plutonium/package/engine.rb +2 -2
- data/lib/plutonium/query/adhoc_block.rb +6 -2
- data/lib/plutonium/query/model_scope.rb +1 -1
- data/lib/plutonium/railtie.rb +4 -0
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
- data/lib/plutonium/resource/query_object.rb +38 -8
- data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +19 -4
- data/package.json +1 -1
- metadata +76 -39
- data/brakeman.ignore +0 -28
- data/docs/api-examples.md +0 -49
- data/docs/guide/claude-code-guide.md +0 -74
- data/docs/guide/deep-dive/authorization.md +0 -189
- data/docs/guide/deep-dive/multitenancy.md +0 -256
- data/docs/guide/deep-dive/resources.md +0 -390
- data/docs/guide/getting-started/01-installation.md +0 -165
- data/docs/guide/index.md +0 -28
- data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
- data/docs/guide/introduction/02-core-concepts.md +0 -440
- data/docs/guide/tutorial/01-project-setup.md +0 -75
- data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
- data/docs/guide/tutorial/03-defining-resources.md +0 -90
- data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
- data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
- data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
- data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
- data/docs/markdown-examples.md +0 -85
- data/docs/modules/action.md +0 -244
- data/docs/modules/authentication.md +0 -236
- data/docs/modules/configuration.md +0 -599
- data/docs/modules/controller.md +0 -443
- data/docs/modules/core.md +0 -316
- data/docs/modules/definition.md +0 -1308
- data/docs/modules/display.md +0 -759
- data/docs/modules/form.md +0 -495
- data/docs/modules/generator.md +0 -400
- data/docs/modules/index.md +0 -167
- data/docs/modules/interaction.md +0 -642
- data/docs/modules/package.md +0 -151
- data/docs/modules/policy.md +0 -176
- data/docs/modules/portal.md +0 -710
- data/docs/modules/query.md +0 -297
- data/docs/modules/resource_record.md +0 -618
- data/docs/modules/routing.md +0 -690
- data/docs/modules/table.md +0 -301
- data/docs/modules/ui.md +0 -631
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Chapter 1: Project Setup
|
|
2
|
+
|
|
3
|
+
In this chapter, you'll create a new Plutonium application and explore its structure.
|
|
4
|
+
|
|
5
|
+
## Creating the Application
|
|
6
|
+
|
|
7
|
+
Open your terminal and run:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
rails new blog -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This creates a new Rails application with Plutonium pre-configured.
|
|
14
|
+
|
|
15
|
+
When prompted, accept the defaults or customize as needed. The template will:
|
|
16
|
+
- Install the Plutonium gem
|
|
17
|
+
- Configure TailwindCSS 4
|
|
18
|
+
- Set up Rodauth for authentication
|
|
19
|
+
- Create the database schema
|
|
20
|
+
|
|
21
|
+
## Starting the Server
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cd blog
|
|
25
|
+
bin/dev
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Visit `http://localhost:3000`. You should see a welcome page.
|
|
29
|
+
|
|
30
|
+
## Project Structure
|
|
31
|
+
|
|
32
|
+
Let's explore what was created:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
blog/
|
|
36
|
+
├── app/
|
|
37
|
+
│ ├── controllers/ # Standard Rails controllers
|
|
38
|
+
│ ├── definitions/ # Plutonium definitions (how resources render)
|
|
39
|
+
│ ├── interactions/ # Business logic classes
|
|
40
|
+
│ ├── models/ # Standard Rails models
|
|
41
|
+
│ ├── policies/ # Authorization policies
|
|
42
|
+
│ └── views/ # Phlex view components
|
|
43
|
+
├── packages/ # Feature packages and portals
|
|
44
|
+
│ └── .keep
|
|
45
|
+
├── config/
|
|
46
|
+
│ └── initializers/
|
|
47
|
+
│ └── plutonium.rb # Plutonium configuration
|
|
48
|
+
└── ...
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Key Directories
|
|
52
|
+
|
|
53
|
+
**`app/definitions/`** - Contains Definition classes that control how resources are displayed. Fields, forms, tables, and actions are configured here.
|
|
54
|
+
|
|
55
|
+
**`app/policies/`** - Contains Policy classes that control authorization. Who can do what with each resource.
|
|
56
|
+
|
|
57
|
+
**`app/interactions/`** - Contains Interaction classes for complex business logic. Used for custom actions beyond simple CRUD.
|
|
58
|
+
|
|
59
|
+
**`packages/`** - Contains Feature Packages (business logic modules) and Portal Packages (web interfaces).
|
|
60
|
+
|
|
61
|
+
## Understanding Packages
|
|
62
|
+
|
|
63
|
+
Plutonium uses two types of packages:
|
|
64
|
+
|
|
65
|
+
### Feature Packages
|
|
66
|
+
|
|
67
|
+
Feature packages contain your business logic - models, definitions, policies, interactions, and controllers. They're Rails engines that can be shared across multiple portals.
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
packages/
|
|
71
|
+
└── blogging/ # Feature package
|
|
72
|
+
├── app/
|
|
73
|
+
│ ├── controllers/blogging/
|
|
74
|
+
│ ├── definitions/blogging/
|
|
75
|
+
│ ├── interactions/blogging/
|
|
76
|
+
│ ├── models/blogging/
|
|
77
|
+
│ ├── policies/blogging/
|
|
78
|
+
│ └── views/blogging/
|
|
79
|
+
└── lib/
|
|
80
|
+
└── engine.rb
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Portal Packages
|
|
84
|
+
|
|
85
|
+
Portal packages are web interfaces that expose resources to users. Each portal can have its own authentication, authorization rules, and UI customizations.
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
packages/
|
|
89
|
+
└── admin_portal/ # Portal package
|
|
90
|
+
├── app/
|
|
91
|
+
│ ├── controllers/admin_portal/
|
|
92
|
+
│ └── views/admin_portal/
|
|
93
|
+
└── lib/admin_portal/
|
|
94
|
+
└── engine.rb
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Configuration
|
|
98
|
+
|
|
99
|
+
Open `config/initializers/plutonium.rb`:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
Plutonium.configure do |config|
|
|
103
|
+
config.load_defaults 1.0
|
|
104
|
+
|
|
105
|
+
# Configure plutonium above.
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The defaults work well out of the box. Available options include:
|
|
110
|
+
- `config.development` - Enable development mode
|
|
111
|
+
- `config.cache_discovery` - Cache resource discovery (auto-configured per environment)
|
|
112
|
+
- `config.assets.logo` - Custom logo path
|
|
113
|
+
|
|
114
|
+
## What's Next
|
|
115
|
+
|
|
116
|
+
In the next chapter, we'll create our first resource - the Post model with its definition and policy.
|
|
117
|
+
|
|
118
|
+
[Continue to Chapter 2: Creating Your First Resource →](./02-first-resource)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Chapter 2: Creating Your First Resource
|
|
2
|
+
|
|
3
|
+
In this chapter, you'll create the Post resource - the core of our blog application.
|
|
4
|
+
|
|
5
|
+
## What is a Resource?
|
|
6
|
+
|
|
7
|
+
In Plutonium, a **resource** is a complete unit consisting of:
|
|
8
|
+
- **Model** - The data structure (ActiveRecord)
|
|
9
|
+
- **Definition** - How the resource renders (fields, actions, UI)
|
|
10
|
+
- **Policy** - Who can do what (authorization)
|
|
11
|
+
- **Controller** - HTTP handling (auto-generated or custom)
|
|
12
|
+
|
|
13
|
+
## Generating a Feature Package
|
|
14
|
+
|
|
15
|
+
First, let's create a feature package to hold our blogging logic:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
rails generate pu:pkg:package blogging
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This creates:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
packages/blogging/
|
|
25
|
+
├── app/
|
|
26
|
+
│ ├── controllers/blogging/
|
|
27
|
+
│ ├── definitions/blogging/
|
|
28
|
+
│ ├── interactions/blogging/
|
|
29
|
+
│ ├── models/blogging/
|
|
30
|
+
│ ├── policies/blogging/
|
|
31
|
+
│ └── views/blogging/
|
|
32
|
+
└── lib/
|
|
33
|
+
└── engine.rb
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Generating the Post Resource
|
|
37
|
+
|
|
38
|
+
Now generate the Post resource inside the blogging package:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
rails generate pu:res:scaffold Post title:string body:text 'published:boolean?' --dest=blogging
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This creates:
|
|
45
|
+
|
|
46
|
+
### Model (`packages/blogging/app/models/blogging/post.rb`)
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
class Blogging::Post < Blogging::ResourceRecord
|
|
50
|
+
# Add validations, associations, and business logic here
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Definition (`packages/blogging/app/definitions/blogging/post_definition.rb`)
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
class Blogging::PostDefinition < Blogging::ResourceDefinition
|
|
58
|
+
# Customize field rendering, forms, and UI here
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Policy (`packages/blogging/app/policies/blogging/post_policy.rb`)
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
|
66
|
+
def create?
|
|
67
|
+
true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def read?
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def permitted_attributes_for_create
|
|
75
|
+
[:title, :body, :published]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def permitted_attributes_for_read
|
|
79
|
+
[:title, :body, :published]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def permitted_associations
|
|
83
|
+
%i[]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Migration
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
class CreateBloggingPosts < ActiveRecord::Migration[8.0]
|
|
92
|
+
def change
|
|
93
|
+
create_table :blogging_posts do |t|
|
|
94
|
+
t.string :title, null: false
|
|
95
|
+
t.text :body, null: false
|
|
96
|
+
t.boolean :published
|
|
97
|
+
|
|
98
|
+
t.timestamps
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Running the Migration
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
rails db:migrate
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Creating a Portal
|
|
111
|
+
|
|
112
|
+
Resources need a portal to be accessible via the web. Let's create an admin portal:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
rails generate pu:pkg:portal admin
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
This creates the AdminPortal package with authentication configured.
|
|
119
|
+
|
|
120
|
+
## Connecting the Resource
|
|
121
|
+
|
|
122
|
+
Connect the Post resource to the admin portal:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
rails generate pu:res:conn Blogging::Post --dest=admin_portal
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This:
|
|
129
|
+
1. Adds routes for Post in the admin portal
|
|
130
|
+
2. Creates a portal-specific controller
|
|
131
|
+
3. Registers the resource with the portal
|
|
132
|
+
|
|
133
|
+
## Starting the Server
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
bin/dev
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Visit `http://localhost:3000/admin/blogging/posts`. You should see:
|
|
140
|
+
- An empty posts table
|
|
141
|
+
- A "New Post" button
|
|
142
|
+
- Search and filter options
|
|
143
|
+
|
|
144
|
+
Try creating a post. The form is automatically generated from your model's attributes.
|
|
145
|
+
|
|
146
|
+
## Understanding Auto-Detection
|
|
147
|
+
|
|
148
|
+
Notice that we wrote almost no configuration code. Plutonium auto-detected:
|
|
149
|
+
|
|
150
|
+
- **Fields** from the model's columns
|
|
151
|
+
- **Validations** from the model's validators
|
|
152
|
+
- **Associations** from belongs_to/has_many
|
|
153
|
+
- **Form inputs** based on column types
|
|
154
|
+
- **Table columns** from the policy's permitted attributes
|
|
155
|
+
|
|
156
|
+
This is Plutonium's core philosophy: **convention over configuration**. You only write code when you need to change the defaults.
|
|
157
|
+
|
|
158
|
+
## Customizing Table Columns
|
|
159
|
+
|
|
160
|
+
By default, Plutonium shows all permitted attributes in the table. To customize which columns appear, use the policy's `permitted_attributes_for_index` method:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
# packages/blogging/app/policies/blogging/post_policy.rb
|
|
164
|
+
module Blogging
|
|
165
|
+
class PostPolicy < Blogging::ResourcePolicy
|
|
166
|
+
# Control which columns appear in the index table
|
|
167
|
+
def permitted_attributes_for_index
|
|
168
|
+
[:title, :published, :created_at]
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Refresh the page. The table now shows only the columns you specified.
|
|
175
|
+
|
|
176
|
+
## What's Next
|
|
177
|
+
|
|
178
|
+
Our resource is working, but anyone can access it. In the next chapter, we'll add authentication with Rodauth.
|
|
179
|
+
|
|
180
|
+
[Continue to Chapter 3: Setting Up Authentication →](./03-authentication)
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Chapter 3: Setting Up Authentication
|
|
2
|
+
|
|
3
|
+
In this chapter, you'll configure Rodauth for user authentication.
|
|
4
|
+
|
|
5
|
+
## What is Rodauth?
|
|
6
|
+
|
|
7
|
+
Rodauth is a Ruby authentication framework that Plutonium uses for:
|
|
8
|
+
- User registration and login
|
|
9
|
+
- Password reset and email verification
|
|
10
|
+
- Multi-factor authentication
|
|
11
|
+
- Session management
|
|
12
|
+
|
|
13
|
+
Plutonium integrates Rodauth seamlessly with its portal system.
|
|
14
|
+
|
|
15
|
+
## Setting Up Authentication
|
|
16
|
+
|
|
17
|
+
If you used the Plutonium template, Rodauth is already installed. If not:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
rails generate pu:rodauth:install
|
|
21
|
+
rails db:migrate
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Creating an Account Type
|
|
25
|
+
|
|
26
|
+
Plutonium supports multiple account types. For admin accounts that cannot self-register, use the `admin` generator:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
rails generate pu:rodauth:admin admin
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This creates:
|
|
33
|
+
|
|
34
|
+
### Account Model (`app/models/admin.rb`)
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
class Admin < ResourceRecord
|
|
38
|
+
include Rodauth::Rails.model(:admin)
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Rodauth Plugin (`app/rodauth/admin_rodauth_plugin.rb`)
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
class AdminRodauthPlugin < RodauthPlugin
|
|
46
|
+
configure do
|
|
47
|
+
# Multi-phase login, 2FA, lockout, audit logging enabled
|
|
48
|
+
enable :login, :remember, :logout, ...
|
|
49
|
+
|
|
50
|
+
# No public signup - accounts created via rake task
|
|
51
|
+
before_create_account_route do
|
|
52
|
+
request.halt unless internal_request?
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The generator also creates migrations for the account table and authentication features.
|
|
59
|
+
|
|
60
|
+
## Configuring the Portal
|
|
61
|
+
|
|
62
|
+
Authentication is configured in the portal's controller concern. Update it to use Rodauth:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
# packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
|
|
66
|
+
module AdminPortal
|
|
67
|
+
module Concerns
|
|
68
|
+
module Controller
|
|
69
|
+
extend ActiveSupport::Concern
|
|
70
|
+
include Plutonium::Portal::Controller
|
|
71
|
+
include Plutonium::Auth::Rodauth(:admin)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This provides `current_user` and authentication helpers throughout the portal.
|
|
78
|
+
|
|
79
|
+
## Running Migrations
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
rails db:migrate
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Testing Authentication
|
|
86
|
+
|
|
87
|
+
Restart your server:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
bin/dev
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Visit `http://localhost:3000/admin/blogging/posts`. You'll be redirected to the login page.
|
|
94
|
+
|
|
95
|
+
### Creating an Admin Account
|
|
96
|
+
|
|
97
|
+
Admin accounts are created via rake task (web registration is disabled for security):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
rails rodauth:admin
|
|
101
|
+
# Or with EMAIL environment variable:
|
|
102
|
+
EMAIL=admin@example.com rails rodauth:admin
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The task will prompt for an email if not provided and send a verification email with password setup instructions.
|
|
106
|
+
|
|
107
|
+
Now log in with the account you created.
|
|
108
|
+
|
|
109
|
+
## Customizing the Login Page
|
|
110
|
+
|
|
111
|
+
The login page uses Plutonium's default theme. To customize it:
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
# packages/admin_portal/app/views/admin_portal/auth/login.rb
|
|
115
|
+
module AdminPortal
|
|
116
|
+
module Auth
|
|
117
|
+
class Login < Plutonium::Auth::View::Login
|
|
118
|
+
def page_title
|
|
119
|
+
"Admin Login"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def welcome_message
|
|
123
|
+
"Sign in to manage your blog"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Email Configuration
|
|
131
|
+
|
|
132
|
+
For password reset and email verification, configure Action Mailer:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
# config/environments/development.rb
|
|
136
|
+
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
|
|
137
|
+
config.action_mailer.delivery_method = :letter_opener # Or :smtp for real emails
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Creating the User Account Type
|
|
141
|
+
|
|
142
|
+
Our blog needs users (authors) who can create posts. Let's create a User account type that allows self-registration:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
rails generate pu:rodauth:account user
|
|
146
|
+
rails db:migrate
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
This creates a `User` model similar to `Admin`, but with public registration enabled.
|
|
150
|
+
|
|
151
|
+
### Linking Posts to Users
|
|
152
|
+
|
|
153
|
+
Now we can add the author relationship to our Post model. Generate a migration:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
rails generate migration AddUserToBloggingPosts user:belongs_to
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Update the migration to add the foreign key:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
class AddUserToBloggingPosts < ActiveRecord::Migration[8.0]
|
|
163
|
+
def change
|
|
164
|
+
add_reference :blogging_posts, :user, null: false, foreign_key: true
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Run the migration:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
rails db:migrate
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Update the Post model to include the association:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
# packages/blogging/app/models/blogging/post.rb
|
|
179
|
+
class Blogging::Post < Blogging::ResourceRecord
|
|
180
|
+
belongs_to :user
|
|
181
|
+
# ... existing code
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Also update the Post policy to include user in permitted attributes:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
# packages/blogging/app/policies/blogging/post_policy.rb
|
|
189
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
|
190
|
+
def permitted_attributes_for_create
|
|
191
|
+
[:title, :body, :published, :user_id]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def permitted_attributes_for_read
|
|
195
|
+
[:title, :body, :published, :user, :created_at]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def permitted_associations
|
|
199
|
+
%i[user]
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Now posts are linked to their authors, which we'll use for authorization in the next chapter.
|
|
205
|
+
|
|
206
|
+
## Multiple Account Types
|
|
207
|
+
|
|
208
|
+
You can have different account types for different portals:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Create an author account type (if using a different name than "user")
|
|
212
|
+
rails generate pu:rodauth:account author
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Then configure each portal's controller concern:
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
# packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
|
|
219
|
+
include Plutonium::Auth::Rodauth(:admin)
|
|
220
|
+
|
|
221
|
+
# packages/author_portal/app/controllers/author_portal/concerns/controller.rb
|
|
222
|
+
include Plutonium::Auth::Rodauth(:author)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Session Configuration
|
|
226
|
+
|
|
227
|
+
Rodauth sessions can be configured in the Rodauth plugin:
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
# app/rodauth/admin_rodauth_plugin.rb
|
|
231
|
+
class AdminRodauthPlugin < RodauthPlugin
|
|
232
|
+
configure do
|
|
233
|
+
# Session expires after 30 days
|
|
234
|
+
session_expiration_seconds 30.days.to_i
|
|
235
|
+
|
|
236
|
+
# Require re-authentication for sensitive actions
|
|
237
|
+
password_grace_period 3600
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## What's Next
|
|
243
|
+
|
|
244
|
+
Users can now log in, but anyone can do anything. In the next chapter, we'll implement authorization policies to control access.
|
|
245
|
+
|
|
246
|
+
[Continue to Chapter 4: Implementing Authorization →](./04-authorization)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Chapter 4: Implementing Authorization
|
|
2
|
+
|
|
3
|
+
In this chapter, you'll implement authorization policies to control who can do what.
|
|
4
|
+
|
|
5
|
+
## Understanding Policies
|
|
6
|
+
|
|
7
|
+
Plutonium uses policy classes to control authorization at three levels:
|
|
8
|
+
|
|
9
|
+
1. **Action Permissions** - Can the user perform this action? (create, read, update, delete)
|
|
10
|
+
2. **Attribute Permissions** - Which attributes can the user see/modify?
|
|
11
|
+
3. **Scope Permissions** - Which records can the user access?
|
|
12
|
+
|
|
13
|
+
## The Policy Class
|
|
14
|
+
|
|
15
|
+
Open the post policy:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
# packages/blogging/app/policies/blogging/post_policy.rb
|
|
19
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
|
20
|
+
# By default, all actions are permitted
|
|
21
|
+
# Let's add some restrictions
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Action Permissions
|
|
26
|
+
|
|
27
|
+
Let's implement basic CRUD permissions:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
|
31
|
+
# Anyone can view published posts
|
|
32
|
+
def read?
|
|
33
|
+
record.published? || owner?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Only the owner can edit
|
|
37
|
+
def update?
|
|
38
|
+
owner?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Only the owner can delete
|
|
42
|
+
def destroy?
|
|
43
|
+
owner?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Anyone authenticated can create
|
|
47
|
+
def create?
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def owner?
|
|
54
|
+
record.user_id == user.id
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Understanding the Policy Context
|
|
60
|
+
|
|
61
|
+
Inside a policy, you have access to:
|
|
62
|
+
|
|
63
|
+
| Accessor | Description |
|
|
64
|
+
|----------|-------------|
|
|
65
|
+
| `user` | The current authenticated user |
|
|
66
|
+
| `record` | The resource being authorized |
|
|
67
|
+
| `entity_scope` | Parent record for scoping (e.g., Organization in multi-tenant apps) |
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
def some_permission?
|
|
71
|
+
user # => Current user (from authentication)
|
|
72
|
+
record # => The Post instance being checked
|
|
73
|
+
entity_scope # => Parent record for multi-tenancy (or nil)
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Attribute Permissions
|
|
78
|
+
|
|
79
|
+
Control which fields users can view and modify:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
|
83
|
+
# Attributes allowed in forms (create/update)
|
|
84
|
+
def permitted_attributes_for_create
|
|
85
|
+
[:title, :body, :user_id]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def permitted_attributes_for_update
|
|
89
|
+
if owner?
|
|
90
|
+
[:title, :body, :published]
|
|
91
|
+
else
|
|
92
|
+
[] # Non-owners can't edit
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Attributes visible in views
|
|
97
|
+
def permitted_attributes_for_read
|
|
98
|
+
if owner? || record.published?
|
|
99
|
+
[:title, :body, :published, :created_at, :user]
|
|
100
|
+
else
|
|
101
|
+
[:title] # Limited view for unpublished posts
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Scope Permissions
|
|
108
|
+
|
|
109
|
+
Control which records appear in listings:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
|
113
|
+
# Called when listing posts
|
|
114
|
+
def relation_scope(relation)
|
|
115
|
+
if admin?
|
|
116
|
+
relation # Admins see everything
|
|
117
|
+
else
|
|
118
|
+
relation.where(published: true).or(relation.where(user_id: user.id))
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def admin?
|
|
125
|
+
user.respond_to?(:admin?) && user.admin?
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Portal-Specific Policies
|
|
131
|
+
|
|
132
|
+
Different portals can have different policies. Create a portal-specific policy:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
# packages/admin_portal/app/policies/admin_portal/blogging/post_policy.rb
|
|
136
|
+
class AdminPortal::Blogging::PostPolicy < ::Blogging::PostPolicy
|
|
137
|
+
# Admins can do everything
|
|
138
|
+
def read?
|
|
139
|
+
true
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def update?
|
|
143
|
+
true
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def destroy?
|
|
147
|
+
true
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def relation_scope(relation)
|
|
151
|
+
relation # No scope restrictions for admins
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Plutonium automatically uses the portal-specific policy when available.
|
|
157
|
+
|
|
158
|
+
## Learn More
|
|
159
|
+
|
|
160
|
+
Plutonium policies extend [ActionPolicy](https://actionpolicy.evilmartians.io/). See the ActionPolicy documentation for advanced features like:
|
|
161
|
+
|
|
162
|
+
- [Aliases](https://actionpolicy.evilmartians.io/#/aliases) - Group actions under common rules
|
|
163
|
+
- [Pre-checks](https://actionpolicy.evilmartians.io/#/pre_checks) - Skip checks for certain users (e.g., admins)
|
|
164
|
+
- [Caching](https://actionpolicy.evilmartians.io/#/caching) - Cache authorization results
|
|
165
|
+
|
|
166
|
+
## What's Next
|
|
167
|
+
|
|
168
|
+
We have CRUD working with proper authorization. In the next chapter, we'll add a custom "Publish" action using Interactions.
|
|
169
|
+
|
|
170
|
+
[Continue to Chapter 5: Adding Custom Actions →](./05-custom-actions)
|