rodauth-rails 0.11.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|