rodauth-rails 0.11.0 → 0.15.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/CHANGELOG.md +40 -0
- data/README.md +316 -218
- data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +8 -4
- data/lib/generators/rodauth/templates/app/models/account.rb +1 -0
- data/lib/generators/rodauth/templates/app/views/rodauth/_email_auth_request_form.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_field_error.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_global_logout_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_confirm_field.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_display.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_field.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_form.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_form_footer.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_form_header.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_hidden_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_new_password_field.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_otp_auth_code_field.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_password_confirm_field.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_password_field.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_recovery_code_field.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_recovery_codes_form.html.erb +4 -4
- data/lib/generators/rodauth/templates/app/views/rodauth/_sms_code_field.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_sms_phone_field.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_submit.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/add_recovery_codes.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/change_login.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/change_password.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/close_account.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/confirm_password.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/create_account.html.erb +4 -4
- data/lib/generators/rodauth/templates/app/views/rodauth/email_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/logout.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/multi_phase_login.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/otp_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/otp_disable.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/otp_setup.html.erb +9 -9
- data/lib/generators/rodauth/templates/app/views/rodauth/recovery_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/remember.html.erb +5 -5
- data/lib/generators/rodauth/templates/app/views/rodauth/reset_password.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/reset_password_request.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_confirm.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_disable.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_request.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_setup.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_disable.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_manage.html.erb +6 -6
- data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account_request.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/verify_account.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/verify_account_resend.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/verify_login_change.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_auth.html.erb +7 -7
- data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_remove.html.erb +6 -6
- data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_setup.html.erb +7 -7
- data/lib/generators/rodauth/views_generator.rb +26 -4
- data/lib/rodauth/rails.rb +32 -13
- data/lib/rodauth/rails/auth.rb +9 -12
- data/lib/rodauth/rails/feature.rb +19 -230
- data/lib/rodauth/rails/feature/base.rb +62 -0
- data/lib/rodauth/rails/feature/callbacks.rb +65 -0
- data/lib/rodauth/rails/feature/csrf.rb +65 -0
- data/lib/rodauth/rails/feature/email.rb +30 -0
- data/lib/rodauth/rails/feature/instrumentation.rb +71 -0
- data/lib/rodauth/rails/feature/internal_request.rb +50 -0
- data/lib/rodauth/rails/feature/render.rb +48 -0
- data/lib/rodauth/rails/model.rb +101 -0
- data/lib/rodauth/rails/model/associations.rb +195 -0
- data/lib/rodauth/rails/railtie.rb +0 -5
- data/lib/rodauth/rails/tasks.rake +5 -5
- data/lib/rodauth/rails/version.rb +1 -1
- data/rodauth-rails.gemspec +4 -1
- metadata +56 -6
- data/lib/rodauth/rails/log_subscriber.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '097625662e9cefbf7484ea775c9b1930968fbbc3a1b8ad24df9e229e2194e301'
|
4
|
+
data.tar.gz: fbbfe9dd849646e859aecbf3c600168fc0b65255f7b2bf933a2e42591f8b0b79
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 762d0c1725dcd0017cdd6722894e546dfdf246e245af688a8bc99f177e43765fe8bd7a79639e45d3146ca5bedadce9f34389f61bf3a6957b406cbc664cf93829
|
7
|
+
data.tar.gz: 8c6624c70668356b8434dde9bd237cced89f23d4d241b770989f1af68ee687b7186f305ada409885d619b9974e7d2d47b6fddc9faf6935653cab805ee8709167
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,43 @@
|
|
1
|
+
## 0.15.0 (2021-07-29)
|
2
|
+
|
3
|
+
* Add `Rodauth::Rails::Model` mixin that defines password attribute and associations on the model (@janko)
|
4
|
+
|
5
|
+
* Add support for the new internal_request feature (@janko)
|
6
|
+
|
7
|
+
* Implement `Rodauth::Rails.rodauth` in terms of the internal_request feature (@janko)
|
8
|
+
|
9
|
+
## 0.14.0 (2021-07-10)
|
10
|
+
|
11
|
+
* Speed up template rendering by only searching formats accepted by the request (@janko)
|
12
|
+
|
13
|
+
* Add `--name` option to `rodauth:views` generator for specifying different rodauth configuration (@janko)
|
14
|
+
|
15
|
+
* Infer correct template path from configured controller in `rodauth:views` generator (@janko)
|
16
|
+
|
17
|
+
* Raise `ArgumentError` if undefined rodauth configuration is passed to `Rodauth::Rails.app` (@janko)
|
18
|
+
|
19
|
+
* Make `#rails_controller` method on the rodauth instance public (@janko)
|
20
|
+
|
21
|
+
* Remove `--directory` option from `rodauth:views` generator (@janko)
|
22
|
+
|
23
|
+
* Remove `#features` and `#routes` writer and `#configuration` reader from `Rodauth::Rails::Auth` (@janko)
|
24
|
+
|
25
|
+
## 0.13.0 (2021-06-10)
|
26
|
+
|
27
|
+
* Add `:query`, `:form`, `:session`, `:account`, and `:env` options to `Rodauth::Rails.rodauth` (@janko)
|
28
|
+
|
29
|
+
## 0.12.0 (2021-05-15)
|
30
|
+
|
31
|
+
* Include total view render time in logs for Rodauth requests (@janko)
|
32
|
+
|
33
|
+
* Instrument redirects (@janko)
|
34
|
+
|
35
|
+
* Instrument Rodauth requests on `action_controller` namespace (@janko)
|
36
|
+
|
37
|
+
* Update templates for Boostrap 5 compatibility (@janko)
|
38
|
+
|
39
|
+
* Log request parameters for Rodauth requests (@janko)
|
40
|
+
|
1
41
|
## 0.11.0 (2021-05-06)
|
2
42
|
|
3
43
|
* Add controller-like logging for requests to Rodauth endpoints (@janko)
|
data/README.md
CHANGED
@@ -41,27 +41,15 @@ Active Record's database connection][sequel-activerecord_connection].
|
|
41
41
|
|
42
42
|
## Upgrading
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
Starting from version 0.7.0, rodauth-rails now correctly detects Rails
|
47
|
-
application's `secret_key_base` when setting default `hmac_secret`, including
|
48
|
-
when it's set via credentials or `$SECRET_KEY_BASE` environment variable. This
|
49
|
-
means that your authentication will now be more secure by default, and Rodauth
|
50
|
-
features that require `hmac_secret` should now work automatically as well.
|
51
|
-
|
52
|
-
However, if you've already been using rodauth-rails in production, where the
|
53
|
-
`secret_key_base` is set via credentials or environment variable and `hmac_secret`
|
54
|
-
was not explicitly set, the fact that your authentication will now start using
|
55
|
-
HMACs has backwards compatibility considerations. See the [Rodauth
|
56
|
-
documentation][hmac] for instructions on how to safely transition, or just set
|
57
|
-
`hmac_secret nil` in your Rodauth configuration.
|
44
|
+
For instructions on upgrading from previous rodauth-rails versions, see
|
45
|
+
[UPGRADING.md](/UPGRADING.md).
|
58
46
|
|
59
47
|
## Installation
|
60
48
|
|
61
49
|
Add the gem to your Gemfile:
|
62
50
|
|
63
51
|
```rb
|
64
|
-
gem "rodauth-rails", "~> 0.
|
52
|
+
gem "rodauth-rails", "~> 0.15"
|
65
53
|
|
66
54
|
# gem "jwt", require: false # for JWT feature
|
67
55
|
# gem "rotp", require: false # for OTP feature
|
@@ -86,132 +74,22 @@ $ rails generate rodauth:install --jwt # token authentication via the "Authoriza
|
|
86
74
|
$ bundle add jwt
|
87
75
|
```
|
88
76
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
* Rodauth initializer at `config/initializers/rodauth.rb`
|
93
|
-
* Sequel initializer at `config/initializers/sequel.rb` for ActiveRecord integration
|
94
|
-
* Rodauth app at `app/lib/rodauth_app.rb`
|
95
|
-
* Rodauth controller at `app/controllers/rodauth_controller.rb`
|
96
|
-
* Account model at `app/models/account.rb`
|
97
|
-
* Rodauth mailer at `app/mailers/rodauth_mailer.rb` with views
|
98
|
-
|
99
|
-
### Migration
|
100
|
-
|
101
|
-
The migration file creates tables required by Rodauth. You're encouraged to
|
102
|
-
review the migration, and modify it to only create tables for features you
|
103
|
-
intend to use.
|
104
|
-
|
105
|
-
```rb
|
106
|
-
# db/migrate/*_create_rodauth.rb
|
107
|
-
class CreateRodauth < ActiveRecord::Migration
|
108
|
-
def change
|
109
|
-
create_table :accounts do |t| ... end
|
110
|
-
create_table :account_password_hashes do |t| ... end
|
111
|
-
create_table :account_password_reset_keys do |t| ... end
|
112
|
-
create_table :account_verification_keys do |t| ... end
|
113
|
-
create_table :account_login_change_keys do |t| ... end
|
114
|
-
create_table :account_remember_keys do |t| ... end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
```
|
77
|
+
This generator will create a Rodauth app with common authentication features
|
78
|
+
enabled, a database migration with tables required by those features, a mailer
|
79
|
+
with default templates, and a few other files.
|
118
80
|
|
119
|
-
|
81
|
+
Feel free to remove any features you don't need, along with their corresponding
|
82
|
+
tables. Afterwards, run the migration:
|
120
83
|
|
121
|
-
```
|
84
|
+
```sh
|
122
85
|
$ rails db:migrate
|
123
86
|
```
|
124
87
|
|
125
|
-
### Rodauth initializer
|
126
|
-
|
127
|
-
The Rodauth initializer assigns the constant for your Rodauth app, which will
|
128
|
-
be called by the Rack middleware that's added in front of your Rails router.
|
129
|
-
|
130
|
-
```rb
|
131
|
-
# config/initializers/rodauth.rb
|
132
|
-
Rodauth::Rails.configure do |config|
|
133
|
-
config.app = "RodauthApp"
|
134
|
-
end
|
135
|
-
```
|
136
|
-
|
137
|
-
### Sequel initializer
|
138
|
-
|
139
|
-
Rodauth uses [Sequel] for database interaction. If you're using ActiveRecord,
|
140
|
-
an additional initializer will be created which configures Sequel to use the
|
141
|
-
ActiveRecord connection.
|
142
|
-
|
143
|
-
```rb
|
144
|
-
# config/initializers/sequel.rb
|
145
|
-
require "sequel/core"
|
146
|
-
|
147
|
-
# initialize Sequel and have it reuse Active Record's database connection
|
148
|
-
DB = Sequel.connect("postgresql://", extensions: :activerecord_connection)
|
149
|
-
```
|
150
|
-
|
151
|
-
### Rodauth app
|
152
|
-
|
153
|
-
Your Rodauth app is created in the `app/lib/` directory, and comes with a
|
154
|
-
default set of authentication features enabled, as well as extensive examples
|
155
|
-
on ways you can configure authentication behaviour.
|
156
|
-
|
157
|
-
```rb
|
158
|
-
# app/lib/rodauth_app.rb
|
159
|
-
class RodauthApp < Rodauth::Rails::App
|
160
|
-
configure do
|
161
|
-
# authentication configuration
|
162
|
-
end
|
163
|
-
|
164
|
-
route do |r|
|
165
|
-
# request handling
|
166
|
-
end
|
167
|
-
end
|
168
|
-
```
|
169
|
-
|
170
|
-
### Controller
|
171
|
-
|
172
|
-
Your Rodauth app will by default use `RodauthController` for view rendering,
|
173
|
-
CSRF protection, and running controller callbacks and rescue handlers around
|
174
|
-
Rodauth actions.
|
175
|
-
|
176
|
-
```rb
|
177
|
-
# app/controllers/rodauth_controller.rb
|
178
|
-
class RodauthController < ApplicationController
|
179
|
-
end
|
180
|
-
```
|
181
|
-
|
182
|
-
### Account model
|
183
|
-
|
184
|
-
Rodauth stores user accounts in the `accounts` table, so the generator will
|
185
|
-
also create an `Account` model for custom use.
|
186
|
-
|
187
|
-
```rb
|
188
|
-
# app/models/account.rb
|
189
|
-
class Account < ApplicationRecord
|
190
|
-
end
|
191
|
-
```
|
192
|
-
|
193
|
-
### Rodauth mailer
|
194
|
-
|
195
|
-
The default Rodauth app is configured to use `RodauthMailer` mailer
|
196
|
-
for sending authentication emails.
|
197
|
-
|
198
|
-
```rb
|
199
|
-
# app/mailers/rodauth_mailer.rb
|
200
|
-
class RodauthMailer < ApplicationMailer
|
201
|
-
def verify_account(recipient, email_link) ... end
|
202
|
-
def reset_password(recipient, email_link) ... end
|
203
|
-
def verify_login_change(recipient, old_login, new_login, email_link) ... end
|
204
|
-
def password_changed(recipient) ... end
|
205
|
-
# def email_auth(recipient, email_link) ... end
|
206
|
-
# def unlock_account(recipient, email_link) ... end
|
207
|
-
end
|
208
|
-
```
|
209
|
-
|
210
88
|
## Usage
|
211
89
|
|
212
90
|
### Routes
|
213
91
|
|
214
|
-
|
92
|
+
You can see the list of routes our Rodauth middleware handles:
|
215
93
|
|
216
94
|
```sh
|
217
95
|
$ rails rodauth:routes
|
@@ -233,7 +111,7 @@ Routes handled by RodauthApp:
|
|
233
111
|
/close-account rodauth.close_account_path
|
234
112
|
```
|
235
113
|
|
236
|
-
Using this information,
|
114
|
+
Using this information, you can add some basic authentication links to your
|
237
115
|
navigation header:
|
238
116
|
|
239
117
|
```erb
|
@@ -264,7 +142,7 @@ end
|
|
264
142
|
|
265
143
|
### Current account
|
266
144
|
|
267
|
-
To be able to fetch currently authenticated account,
|
145
|
+
To be able to fetch currently authenticated account, you can define a
|
268
146
|
`#current_account` method that fetches the account id from session and
|
269
147
|
retrieves the corresponding account record:
|
270
148
|
|
@@ -281,11 +159,11 @@ class ApplicationController < ActionController::Base
|
|
281
159
|
rodauth.logout
|
282
160
|
rodauth.login_required
|
283
161
|
end
|
284
|
-
helper_method :current_account # skip if inheriting from ActionController
|
162
|
+
helper_method :current_account # skip if inheriting from ActionController::API
|
285
163
|
end
|
286
164
|
```
|
287
165
|
|
288
|
-
This allows
|
166
|
+
This allows you to access the current account in controllers and views:
|
289
167
|
|
290
168
|
```erb
|
291
169
|
<p>Authenticated as: <%= current_account.email %></p>
|
@@ -293,9 +171,9 @@ This allows us to access the current account in controllers and views:
|
|
293
171
|
|
294
172
|
### Requiring authentication
|
295
173
|
|
296
|
-
|
297
|
-
redirecting the user to the login page if they're not logged in.
|
298
|
-
in
|
174
|
+
You'll likely want to require authentication for certain parts of your app,
|
175
|
+
redirecting the user to the login page if they're not logged in. You can do this
|
176
|
+
in your Rodauth app's routing block, which helps keep the authentication logic
|
299
177
|
encapsulated:
|
300
178
|
|
301
179
|
```rb
|
@@ -314,7 +192,7 @@ class RodauthApp < Rodauth::Rails::App
|
|
314
192
|
end
|
315
193
|
```
|
316
194
|
|
317
|
-
|
195
|
+
You can also require authentication at the controller layer:
|
318
196
|
|
319
197
|
```rb
|
320
198
|
# app/controllers/application_controller.rb
|
@@ -341,8 +219,8 @@ end
|
|
341
219
|
|
342
220
|
#### Routing constraints
|
343
221
|
|
344
|
-
|
345
|
-
|
222
|
+
In some cases it makes sense to require authentication at the Rails router
|
223
|
+
level. You can do this via the built-in `authenticated` routing constraint:
|
346
224
|
|
347
225
|
```rb
|
348
226
|
# config/routes.rb
|
@@ -400,11 +278,11 @@ $ rails generate rodauth:views
|
|
400
278
|
```
|
401
279
|
|
402
280
|
This will generate views for the default set of Rodauth features into the
|
403
|
-
`app/views/rodauth` directory,
|
404
|
-
|
281
|
+
`app/views/rodauth` directory, provided that `RodauthController` is set for the
|
282
|
+
main configuration.
|
405
283
|
|
406
284
|
You can pass a list of Rodauth features to the generator to create views for
|
407
|
-
these features (this will not remove any existing views):
|
285
|
+
these features (this will not remove or overwrite any existing views):
|
408
286
|
|
409
287
|
```sh
|
410
288
|
$ rails generate rodauth:views login create_account lockout otp
|
@@ -416,12 +294,10 @@ Or you can generate views for all features:
|
|
416
294
|
$ rails generate rodauth:views --all
|
417
295
|
```
|
418
296
|
|
419
|
-
|
420
|
-
case make sure to rename the Rodauth controller accordingly):
|
297
|
+
Use `--name` to generate views for a different Rodauth configuration:
|
421
298
|
|
422
299
|
```sh
|
423
|
-
|
424
|
-
$ rails generate rodauth:views --name authentication
|
300
|
+
$ rails generate rodauth:views --name admin
|
425
301
|
```
|
426
302
|
|
427
303
|
#### Layout
|
@@ -514,14 +390,48 @@ end
|
|
514
390
|
This configuration calls `#deliver_later`, which uses Active Job to deliver
|
515
391
|
emails in a background job. It's generally recommended to send emails
|
516
392
|
asynchronously for better request throughput and the ability to retry
|
517
|
-
deliveries. However, if you want to send emails synchronously, modify
|
518
|
-
configuration to call `#deliver_now` instead.
|
393
|
+
deliveries. However, if you want to send emails synchronously, you can modify
|
394
|
+
the configuration to call `#deliver_now` instead.
|
519
395
|
|
520
396
|
If you're using a background processing library without an Active Job adapter,
|
521
397
|
or a 3rd-party service for sending transactional emails, this two-phase API
|
522
398
|
might not be suitable. In this case, instead of overriding `#create_*_email`
|
523
399
|
and `#send_email`, override the `#send_*_email` methods instead, which are
|
524
|
-
required to send the email immediately.
|
400
|
+
required to send the email immediately. For example:
|
401
|
+
|
402
|
+
```rb
|
403
|
+
# app/workers/rodauth_mailer_worker.rb
|
404
|
+
class RodauthMailerWorker
|
405
|
+
include Sidekiq::Worker
|
406
|
+
|
407
|
+
def perform(name, *args)
|
408
|
+
email = RodauthMailer.public_send(name, *args)
|
409
|
+
email.deliver_now
|
410
|
+
end
|
411
|
+
end
|
412
|
+
```
|
413
|
+
```rb
|
414
|
+
# app/lib/rodauth_app.rb
|
415
|
+
class RodauthApp < Rodauth::Rails::App
|
416
|
+
configure do
|
417
|
+
# ...
|
418
|
+
# use `#send_*_email` method to be able to immediately enqueue email delivery
|
419
|
+
send_reset_password_email do
|
420
|
+
enqueue_email(:reset_password, email_to, reset_password_email_link)
|
421
|
+
end
|
422
|
+
# ...
|
423
|
+
auth_class_eval do
|
424
|
+
# custom method for enqueuing email delivery using our worker
|
425
|
+
def enqueue_email(name, *args)
|
426
|
+
db.after_commit do
|
427
|
+
RodauthMailerWorker.perform_async(name, *args)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
# ...
|
432
|
+
end
|
433
|
+
end
|
434
|
+
```
|
525
435
|
|
526
436
|
### Migrations
|
527
437
|
|
@@ -543,10 +453,134 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
|
|
543
453
|
end
|
544
454
|
```
|
545
455
|
|
456
|
+
### Model
|
457
|
+
|
458
|
+
The `Rodauth::Rails::Model` mixin can be included into the account model, which
|
459
|
+
defines a password attribute and associations for tables used by enabled
|
460
|
+
authentication features.
|
461
|
+
|
462
|
+
```rb
|
463
|
+
class Account < ApplicationRecord
|
464
|
+
include Rodauth::Rails.model # or `Rodauth::Rails.model(:admin)`
|
465
|
+
end
|
466
|
+
```
|
467
|
+
|
468
|
+
#### Password attribute
|
469
|
+
|
470
|
+
Regardless of whether you're storing the password hash in a column in the
|
471
|
+
accounts table, or in a separate table, the `#password` attribute can be used
|
472
|
+
to set or clear the password hash.
|
473
|
+
|
474
|
+
```rb
|
475
|
+
account = Account.create!(email: "user@example.com", password: "secret")
|
476
|
+
|
477
|
+
# when password hash is stored in a column on the accounts table
|
478
|
+
account.password_hash #=> "$2a$12$k/Ub1I2iomi84RacqY89Hu4.M0vK7klRnRtzorDyvOkVI.hKhkNw."
|
479
|
+
|
480
|
+
# when password hash is stored in a separate table
|
481
|
+
account.password_hash #=> #<Account::PasswordHash...> (record from `account_password_hashes` table)
|
482
|
+
account.password_hash.password_hash #=> "$2a$12$k/Ub1..." (inaccessible when using database authentication functions)
|
483
|
+
|
484
|
+
account.password = nil # clears password hash
|
485
|
+
account.password_hash #=> nil
|
486
|
+
```
|
487
|
+
|
488
|
+
Note that the password attribute doesn't come with validations, making it
|
489
|
+
unsuitable for forms. It was primarily intended to allow easily creating
|
490
|
+
accounts in development console and in tests.
|
491
|
+
|
492
|
+
#### Associations
|
493
|
+
|
494
|
+
The `Rodauth::Rails::Model` mixin defines associations for Rodauth tables
|
495
|
+
associated to the accounts table:
|
496
|
+
|
497
|
+
```rb
|
498
|
+
account.remember_key #=> #<Account::RememberKey> (record from `account_remember_keys` table)
|
499
|
+
account.active_session_keys #=> [#<Account::ActiveSessionKey>,...] (records from `account_active_session_keys` table)
|
500
|
+
```
|
501
|
+
|
502
|
+
You can also reference the associated models directly:
|
503
|
+
|
504
|
+
```rb
|
505
|
+
# model referencing the `account_authentication_audit_logs` table
|
506
|
+
Account::AuthenticationAuditLog.where(message: "login").group(:account_id)
|
507
|
+
```
|
508
|
+
|
509
|
+
The associated models define the inverse `belongs_to :account` association:
|
510
|
+
|
511
|
+
```rb
|
512
|
+
Account::ActiveSessionKey.includes(:account).map(&:account)
|
513
|
+
```
|
514
|
+
|
515
|
+
Here is an example of using associations to create a method that returns
|
516
|
+
whether the account has multifactor authentication enabled:
|
517
|
+
|
518
|
+
```rb
|
519
|
+
class Account < ApplicationRecord
|
520
|
+
include Rodauth::Rails.model
|
521
|
+
|
522
|
+
def mfa_enabled?
|
523
|
+
otp_key || (sms_code && sms_code.num_failures.nil?) || recovery_codes.any?
|
524
|
+
end
|
525
|
+
end
|
526
|
+
```
|
527
|
+
|
528
|
+
Here is another example of creating a query scope that selects accounts with
|
529
|
+
multifactor authentication enabled:
|
530
|
+
|
531
|
+
```rb
|
532
|
+
class Account < ApplicationRecord
|
533
|
+
include Rodauth::Rails.model
|
534
|
+
|
535
|
+
scope :otp_setup, -> { where(otp_key: OtpKey.all) }
|
536
|
+
scope :sms_codes_setup, -> { where(sms_code: SmsCode.where(num_failures: nil)) }
|
537
|
+
scope :recovery_codes_setup, -> { where(recovery_codes: RecoveryCode.all) }
|
538
|
+
scope :mfa_enabled, -> { merge(otp_setup.or(sms_codes_setup).or(recovery_codes_setup)) }
|
539
|
+
end
|
540
|
+
```
|
541
|
+
|
542
|
+
Below is a list of all associations defined depending on the features loaded:
|
543
|
+
|
544
|
+
| Feature | Association | Type | Model | Table (default) |
|
545
|
+
| :------ | :---------- | :--- | :---- | :---- |
|
546
|
+
| account_expiration | `:activity_time` | `has_one` | `ActivityTime` | `account_activity_times` |
|
547
|
+
| active_sessions | `:active_session_keys` | `has_many` | `ActiveSessionKey` | `account_active_session_keys` |
|
548
|
+
| audit_logging | `:authentication_audit_logs` | `has_many` | `AuthenticationAuditLog` | `account_authentication_audit_logs` |
|
549
|
+
| disallow_password_reuse | `:previous_password_hashes` | `has_many` | `PreviousPasswordHash` | `account_previous_password_hashes` |
|
550
|
+
| email_auth | `:email_auth_key` | `has_one` | `EmailAuthKey` | `account_email_auth_keys` |
|
551
|
+
| jwt_refresh | `:jwt_refresh_keys` | `has_many` | `JwtRefreshKey` | `account_jwt_refresh_keys` |
|
552
|
+
| lockout | `:lockout` | `has_one` | `Lockout` | `account_lockouts` |
|
553
|
+
| lockout | `:login_failure` | `has_one` | `LoginFailure` | `account_login_failures` |
|
554
|
+
| otp | `:otp_key` | `has_one` | `OtpKey` | `account_otp_keys` |
|
555
|
+
| password_expiration | `:password_change_time` | `has_one` | `PasswordChangeTime` | `account_password_change_times` |
|
556
|
+
| recovery_codes | `:recovery_codes` | `has_many` | `RecoveryCode` | `account_recovery_codes` |
|
557
|
+
| remember | `:remember_key` | `has_one` | `RememberKey` | `account_remember_keys` |
|
558
|
+
| reset_password | `:password_reset_key` | `has_one` | `PasswordResetKey` | `account_password_reset_keys` |
|
559
|
+
| single_session | `:session_key` | `has_one` | `SessionKey` | `account_session_keys` |
|
560
|
+
| sms_codes | `:sms_code` | `has_one` | `SmsCode` | `account_sms_codes` |
|
561
|
+
| verify_account | `:verification_key` | `has_one` | `VerificationKey` | `account_verification_keys` |
|
562
|
+
| verify_login_change | `:login_change_key` | `has_one` | `LoginChangeKey` | `account_login_change_keys` |
|
563
|
+
| webauthn | `:webauthn_keys` | `has_many` | `WebauthnKey` | `account_webauthn_keys` |
|
564
|
+
| webauthn | `:webauthn_user_id` | `has_one` | `WebauthnUserId` | `account_webauthn_user_ids` |
|
565
|
+
|
566
|
+
By default, all associations except for audit logs have `dependent: :destroy`
|
567
|
+
set, to allow for easy deletion of account records in the console. You can use
|
568
|
+
`:association_options` to modify global or per-association options:
|
569
|
+
|
570
|
+
```rb
|
571
|
+
# don't auto-delete associations when account model is deleted
|
572
|
+
Rodauth::Rails.model(association_options: { dependent: nil })
|
573
|
+
|
574
|
+
# require authentication audit logs to be eager loaded before retrieval
|
575
|
+
Rodauth::Rails.model(association_options: -> (name) {
|
576
|
+
{ strict_loading: true } if name == :authentication_audit_logs
|
577
|
+
})
|
578
|
+
```
|
579
|
+
|
546
580
|
### Multiple configurations
|
547
581
|
|
548
582
|
If you need to handle multiple types of accounts that require different
|
549
|
-
authentication logic, you can create
|
583
|
+
authentication logic, you can create additional configurations for them:
|
550
584
|
|
551
585
|
```rb
|
552
586
|
# app/lib/rodauth_app.rb
|
@@ -562,10 +596,6 @@ class RodauthApp < Rodauth::Rails::App
|
|
562
596
|
prefix "/admin"
|
563
597
|
session_key_prefix "admin_"
|
564
598
|
remember_cookie_key "_admin_remember" # if using remember feature
|
565
|
-
|
566
|
-
# if you want separate tables
|
567
|
-
accounts_table :admin_accounts
|
568
|
-
password_hash_table :admin_account_password_hashes
|
569
599
|
# ...
|
570
600
|
end
|
571
601
|
|
@@ -574,7 +604,7 @@ class RodauthApp < Rodauth::Rails::App
|
|
574
604
|
|
575
605
|
r.on "admin" do
|
576
606
|
r.rodauth(:admin)
|
577
|
-
|
607
|
+
break # allow routing of other /admin/* requests to continue to Rails
|
578
608
|
end
|
579
609
|
|
580
610
|
# ...
|
@@ -588,6 +618,50 @@ Then in your application you can reference the secondary Rodauth instance:
|
|
588
618
|
rodauth(:admin).login_path #=> "/admin/login"
|
589
619
|
```
|
590
620
|
|
621
|
+
You'll likely want to save the information of which account belongs to which
|
622
|
+
configuration to the database. One way would be to have a separate table that
|
623
|
+
stores account types:
|
624
|
+
|
625
|
+
```sh
|
626
|
+
$ rails generate migration create_account_types
|
627
|
+
```
|
628
|
+
```rb
|
629
|
+
# db/migrate/*_create_account_types.rb
|
630
|
+
class CreateAccountTypes < ActiveRecord::Migration
|
631
|
+
def change
|
632
|
+
create_table :account_types do |t|
|
633
|
+
t.references :account, foreign_key: { on_delete: :cascade }, null: false
|
634
|
+
t.string :type, null: false
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
```
|
639
|
+
```sh
|
640
|
+
$ rails db:migrate
|
641
|
+
```
|
642
|
+
|
643
|
+
Then an entry would be inserted after account creation, and optionally whenever
|
644
|
+
Rodauth retrieves accounts you could filter only those belonging to the current
|
645
|
+
configuration:
|
646
|
+
|
647
|
+
```rb
|
648
|
+
# app/lib/rodauth_app.rb
|
649
|
+
class RodauthApp < Rodauth::Rails::App
|
650
|
+
configure(:admin) do
|
651
|
+
# ...
|
652
|
+
after_create_account do
|
653
|
+
db[:account_types].insert(account_id: account_id, type: "admin")
|
654
|
+
end
|
655
|
+
auth_class_eval do
|
656
|
+
def account_ds(*)
|
657
|
+
super.join(:account_types, account_id: :id).where(type: "admin")
|
658
|
+
end
|
659
|
+
end
|
660
|
+
# ...
|
661
|
+
end
|
662
|
+
end
|
663
|
+
```
|
664
|
+
|
591
665
|
#### Named auth classes
|
592
666
|
|
593
667
|
A `configure` block inside `Rodauth::Rails::App` will internally create an
|
@@ -656,8 +730,8 @@ class RodauthAdmin < RodauthBase # inherit common settings
|
|
656
730
|
end
|
657
731
|
```
|
658
732
|
|
659
|
-
Another benefit is that you can define custom methods
|
660
|
-
instead of
|
733
|
+
Another benefit of explicit classes is that you can define custom methods
|
734
|
+
directly at the class level instead of inside an `auth_class_eval`:
|
661
735
|
|
662
736
|
```rb
|
663
737
|
# app/lib/rodauth_admin.rb
|
@@ -706,24 +780,54 @@ class RodauthApp < Rodauth::Rails::App
|
|
706
780
|
end
|
707
781
|
```
|
708
782
|
|
709
|
-
###
|
783
|
+
### Outside of a request
|
784
|
+
|
785
|
+
In some cases you might need to use Rodauth more programmatically. If you would
|
786
|
+
like to perform Rodauth operations outside of request context, Rodauth ships
|
787
|
+
with the [internal_request] feature just for that. The rodauth-rails gem
|
788
|
+
additionally updates the internal rack env hash with your
|
789
|
+
`config.action_mailer.default_url_options`, which is used for generating URLs.
|
710
790
|
|
711
|
-
|
712
|
-
Rodauth
|
713
|
-
|
791
|
+
If you need to access Rodauth methods not exposed as internal requests, you can
|
792
|
+
use `Rodauth::Rails.rodauth` to retrieve the Rodauth instance used by the
|
793
|
+
internal_request feature:
|
714
794
|
|
715
795
|
```rb
|
716
|
-
|
796
|
+
# app/lib/rodauth_app.rb
|
797
|
+
class RodauthApp < Rodauth::Rails::App
|
798
|
+
configure do
|
799
|
+
enable :internal_request # this is required
|
800
|
+
end
|
801
|
+
end
|
802
|
+
```
|
803
|
+
```rb
|
804
|
+
account = Account.find_by!(email: "user@example.com")
|
805
|
+
rodauth = Rodauth::Rails.rodauth(account: account)
|
806
|
+
|
807
|
+
rodauth.compute_hmac("token") #=> "TpEJTKfKwqYvIDKWsuZhkhKlhaBXtR1aodskBAflD8U"
|
808
|
+
rodauth.open_account? #=> true
|
809
|
+
rodauth.two_factor_authentication_setup? #=> true
|
810
|
+
rodauth.password_meets_requirements?("foo") #=> false
|
811
|
+
rodauth.locked_out? #=> false
|
812
|
+
```
|
813
|
+
|
814
|
+
In addition to the `:account` option, the `Rodauth::Rails.rodauth`
|
815
|
+
method accepts any options supported by the internal_request feature.
|
717
816
|
|
718
|
-
|
719
|
-
rodauth
|
720
|
-
|
721
|
-
|
722
|
-
|
817
|
+
```rb
|
818
|
+
Rodauth::Rails.rodauth(
|
819
|
+
env: { "HTTP_USER_AGENT" => "programmatic" },
|
820
|
+
session: { two_factor_auth_setup: true },
|
821
|
+
params: { "param" => "value" }
|
822
|
+
)
|
723
823
|
```
|
724
824
|
|
725
|
-
|
726
|
-
|
825
|
+
Secondary Rodauth configurations are specified by passing the configuration
|
826
|
+
name:
|
827
|
+
|
828
|
+
```rb
|
829
|
+
Rodauth::Rails.rodauth(:admin)
|
830
|
+
```
|
727
831
|
|
728
832
|
## How it works
|
729
833
|
|
@@ -834,7 +938,7 @@ class RodauthApp < Rodauth::Rails::App
|
|
834
938
|
configure do
|
835
939
|
# ...
|
836
940
|
enable :json
|
837
|
-
only_json? true # accept only JSON requests
|
941
|
+
only_json? true # accept only JSON requests (optional)
|
838
942
|
# ...
|
839
943
|
end
|
840
944
|
end
|
@@ -855,27 +959,29 @@ class RodauthApp < Rodauth::Rails::App
|
|
855
959
|
# ...
|
856
960
|
enable :jwt
|
857
961
|
jwt_secret "<YOUR_SECRET_KEY>" # store the JWT secret in a safe place
|
858
|
-
only_json? true # accept only JSON requests
|
962
|
+
only_json? true # accept only JSON requests (optional)
|
859
963
|
# ...
|
860
964
|
end
|
861
965
|
end
|
862
966
|
```
|
863
967
|
|
864
|
-
|
865
|
-
|
968
|
+
The JWT token will be returned after each request to Rodauth routes. To also
|
969
|
+
return the JWT token on requests to your app's routes, you can add the
|
970
|
+
following code to your base controller:
|
866
971
|
|
867
|
-
```sh
|
868
|
-
$ rails generate rodauth:migration jwt_refresh
|
869
|
-
$ rails db:migrate
|
870
|
-
```
|
871
972
|
```rb
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
973
|
+
class ApplicationController < ActionController::Base
|
974
|
+
# ...
|
975
|
+
after_action :set_jwt_token
|
976
|
+
|
977
|
+
private
|
978
|
+
|
979
|
+
def set_jwt_token
|
980
|
+
if rodauth.use_jwt? && rodauth.valid_jwt?
|
981
|
+
response.headers["Authorization"] = rodauth.session_jwt
|
982
|
+
end
|
878
983
|
end
|
984
|
+
# ...
|
879
985
|
end
|
880
986
|
```
|
881
987
|
|
@@ -935,7 +1041,8 @@ end
|
|
935
1041
|
<%= link_to "Login via Facebook", "/auth/facebook" %>
|
936
1042
|
```
|
937
1043
|
|
938
|
-
|
1044
|
+
Finally, let's implement the OmniAuth callback endpoint on our Rodauth
|
1045
|
+
controller:
|
939
1046
|
|
940
1047
|
```rb
|
941
1048
|
# config/routes.rb
|
@@ -988,11 +1095,8 @@ end
|
|
988
1095
|
|
989
1096
|
## Configuring
|
990
1097
|
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
The `rails` feature rodauth-rails loads is customizable as well, here is the
|
995
|
-
list of its configuration methods:
|
1098
|
+
The `rails` feature rodauth-rails loads provides the following configuration
|
1099
|
+
methods:
|
996
1100
|
|
997
1101
|
| Name | Description |
|
998
1102
|
| :---- | :---------- |
|
@@ -1019,12 +1123,16 @@ Rodauth::Rails.configure do |config|
|
|
1019
1123
|
end
|
1020
1124
|
```
|
1021
1125
|
|
1126
|
+
For the list of configuration methods provided by Rodauth, see the [feature
|
1127
|
+
documentation].
|
1128
|
+
|
1022
1129
|
## Custom extensions
|
1023
1130
|
|
1024
1131
|
When developing custom extensions for Rodauth inside your Rails project, it's
|
1025
|
-
better to use plain modules
|
1026
|
-
feature design doesn't yet
|
1027
|
-
|
1132
|
+
probably better to use plain modules, at least in the beginning, as Rodauth
|
1133
|
+
feature design doesn't yet work well with Zeitwerk reloading.
|
1134
|
+
|
1135
|
+
Here is an example of an LDAP authentication extension that uses the
|
1028
1136
|
[simple_ldap_authenticator] gem.
|
1029
1137
|
|
1030
1138
|
```rb
|
@@ -1181,29 +1289,6 @@ Rails.application.configure do |config|
|
|
1181
1289
|
end
|
1182
1290
|
```
|
1183
1291
|
|
1184
|
-
If you need to create an account record with a password directly, you can do it
|
1185
|
-
as follows:
|
1186
|
-
|
1187
|
-
```rb
|
1188
|
-
# app/models/account.rb
|
1189
|
-
class Account < ApplicationRecord
|
1190
|
-
has_one :password_hash, foreign_key: :id
|
1191
|
-
end
|
1192
|
-
```
|
1193
|
-
```rb
|
1194
|
-
# app/models/account/password_hash.rb
|
1195
|
-
class Account::PasswordHash < ApplicationRecord
|
1196
|
-
belongs_to :account, foreign_key: :id
|
1197
|
-
end
|
1198
|
-
```
|
1199
|
-
```rb
|
1200
|
-
require "bcrypt"
|
1201
|
-
|
1202
|
-
account = Account.create!(email: "user@example.com", status: "verified")
|
1203
|
-
password_hash = BCrypt::Password.create("secret", cost: BCrypt::Engine::MIN_COST)
|
1204
|
-
account.create_password_hash!(id: account.id, password_hash: password_hash)
|
1205
|
-
```
|
1206
|
-
|
1207
1292
|
## Rodauth defaults
|
1208
1293
|
|
1209
1294
|
rodauth-rails changes some of the default Rodauth settings for easier setup:
|
@@ -1284,6 +1369,18 @@ configure do
|
|
1284
1369
|
end
|
1285
1370
|
```
|
1286
1371
|
|
1372
|
+
### Deadline values
|
1373
|
+
|
1374
|
+
To simplify changes to the database schema, rodauth-rails configures Rodauth
|
1375
|
+
to set deadline values for various features in Ruby, instead of relying on
|
1376
|
+
the database to set default column values.
|
1377
|
+
|
1378
|
+
You can easily change this back:
|
1379
|
+
|
1380
|
+
```rb
|
1381
|
+
set_deadline_values? false
|
1382
|
+
```
|
1383
|
+
|
1287
1384
|
## License
|
1288
1385
|
|
1289
1386
|
The gem is available as open source under the terms of the [MIT
|
@@ -1325,3 +1422,4 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
|
|
1325
1422
|
[single_session]: http://rodauth.jeremyevans.net/rdoc/files/doc/single_session_rdoc.html
|
1326
1423
|
[account_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html
|
1327
1424
|
[simple_ldap_authenticator]: https://github.com/jeremyevans/simple_ldap_authenticator
|
1425
|
+
[internal_request]: http://rodauth.jeremyevans.net/rdoc/files/doc/internal_request_rdoc.html
|