rodauth-rails 0.5.0 → 0.8.1
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 +38 -0
- data/README.md +364 -88
- data/lib/generators/rodauth/install_generator.rb +14 -5
- data/lib/generators/rodauth/templates/app/controllers/rodauth_controller.rb +2 -1
- data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +14 -20
- data/lib/rodauth/rails.rb +33 -4
- data/lib/rodauth/rails/app.rb +12 -2
- data/lib/rodauth/rails/app/flash.rb +1 -1
- data/lib/rodauth/rails/app/middleware.rb +26 -0
- data/lib/rodauth/rails/feature.rb +105 -30
- data/lib/rodauth/rails/tasks.rake +7 -11
- data/lib/rodauth/rails/version.rb +1 -1
- data/rodauth-rails.gemspec +3 -1
- metadata +19 -5
- data/lib/rodauth/features/rails.rb +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3f1fd01ddd20052bd15e3c822ff44ca4a6f5d425dd18892ecffa34146364437
|
4
|
+
data.tar.gz: 8907b8616edf882d21ebff69b461cc12dae737f8ec34bdaf5c2d58ac5cc9b632
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 917915fc72c29b668716d3234f2f85e8c92406ea14a63e82284750a5536ec016acb8714064888732eed9fb1443cb803c11e0764b23a76de9de64a9fda11d577f
|
7
|
+
data.tar.gz: f30214be433882a3be04fb988fcd1d4860c50c8cb8beced94f8b7529f45904c8a8c1eb12486e39f890ccce39f1321a02f1e92fce2cc07ca24f1a86509ddd4a59
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,41 @@
|
|
1
|
+
## 0.8.1 (2021-01-04)
|
2
|
+
|
3
|
+
* Fix blank email body when `json: true` and `ActionController::API` descendant are used (@janko)
|
4
|
+
|
5
|
+
* Make view and email rendering work when there are multiple configurations and one is `json: :only` (@janko)
|
6
|
+
|
7
|
+
* Don't attempt to protect against forgery when `ActionController::API` descendant is used (@janko)
|
8
|
+
|
9
|
+
* Mark content of rodauth built-in partials as HTML-safe (@janko)
|
10
|
+
|
11
|
+
## 0.8.0 (2021-01-03)
|
12
|
+
|
13
|
+
* Add `--api` option to `rodauth:install` generator for choosing JSON-only configuration (@janko)
|
14
|
+
|
15
|
+
* Don't blow up when a Rodauth request is made using an unsupported HTTP verb (@janko)
|
16
|
+
|
17
|
+
## 0.7.0 (2020-11-27)
|
18
|
+
|
19
|
+
* Add `#rails_controller_eval` method for running code in context of a controller instance (@janko)
|
20
|
+
|
21
|
+
* Detect `secret_key_base` from credentials and `$SECRET_KEY_BASE` environment variable (@janko)
|
22
|
+
|
23
|
+
## 0.6.1 (2020-11-25)
|
24
|
+
|
25
|
+
* Generate the Rodauth controller for API-only Rails apps as well (@janko)
|
26
|
+
|
27
|
+
* Fix remember cookie deadline not extending in remember feature (@janko)
|
28
|
+
|
29
|
+
## 0.6.0 (2020-11-22)
|
30
|
+
|
31
|
+
* Add `Rodauth::Rails.rodauth` method for retrieving Rodauth instance outside of request context (@janko)
|
32
|
+
|
33
|
+
* Add default Action Dispatch response headers in Rodauth responses (@janko)
|
34
|
+
|
35
|
+
* Run controller rescue handlers around Rodauth actions (@janko)
|
36
|
+
|
37
|
+
* Run controller action callbacks around Rodauth actions (@janko)
|
38
|
+
|
1
39
|
## 0.5.0 (2020-11-16)
|
2
40
|
|
3
41
|
* Support more Active Record adapters in `rodauth:install` generator (@janko)
|
data/README.md
CHANGED
@@ -4,16 +4,57 @@ Provides Rails integration for the [Rodauth] authentication framework.
|
|
4
4
|
|
5
5
|
## Resources
|
6
6
|
|
7
|
+
Useful links:
|
8
|
+
|
7
9
|
* [Rodauth documentation](http://rodauth.jeremyevans.net/documentation.html)
|
8
|
-
* [rodauth-rails wiki](https://github.com/janko/rodauth-rails/wiki)
|
9
10
|
* [Rails demo](https://github.com/janko/rodauth-demo-rails)
|
10
11
|
|
12
|
+
Articles:
|
13
|
+
|
14
|
+
* [Rodauth: A Refreshing Authentication Solution for Ruby](https://janko.io/rodauth-a-refreshing-authentication-solution-for-ruby/)
|
15
|
+
* [Adding Authentication in Rails with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
|
16
|
+
* [Adding Multifactor Authentication in Rails with Rodauth](https://janko.io/adding-multifactor-authentication-in-rails-with-rodauth/)
|
17
|
+
|
18
|
+
## Why Rodauth?
|
19
|
+
|
20
|
+
There are already several popular authentication solutions for Rails (Devise,
|
21
|
+
Sorcery, Clearance, Authlogic), so why would you choose Rodauth? Well, because
|
22
|
+
it has many advantages over the mentioned alternatives:
|
23
|
+
|
24
|
+
* multifactor authentication ([TOTP][otp], [SMS codes][sms_codes], [recovery codes][recovery_codes], [WebAuthn][webauthn])
|
25
|
+
* standardized [JSON API support][jwt] (for every feature)
|
26
|
+
* enterprise security features ([password complexity][password_complexity], [disallow password reuse][disallow_password_reuse], [password expiration][password_expiration], [session expiration][session_expiration], [single session][single_session], [account expiration][account_expiration])
|
27
|
+
* [email authentication][email_auth] (aka "passwordless")
|
28
|
+
* [audit logging][audit_logging] (for any action)
|
29
|
+
* ability to protect password hashes even in case of SQL injection ([more details][password protection])
|
30
|
+
* additional bruteforce protection for tokens ([more details][bruteforce tokens])
|
31
|
+
* uniform configuration DSL (any setting can be static or dynamic)
|
32
|
+
* consistent before/after hooks around everything
|
33
|
+
* dedicated object encapsulating all authentication logic
|
34
|
+
|
35
|
+
## Upgrading
|
36
|
+
|
37
|
+
### Upgrading to 0.7.0
|
38
|
+
|
39
|
+
Starting from version 0.7.0, rodauth-rails now correctly detects Rails
|
40
|
+
application's `secret_key_base` when setting default `hmac_secret`, including
|
41
|
+
when it's set via credentials or `$SECRET_KEY_BASE` environment variable. This
|
42
|
+
means that your authentication will now be more secure by default, and Rodauth
|
43
|
+
features that require `hmac_secret` should now work automatically as well.
|
44
|
+
|
45
|
+
However, if you've already been using rodauth-rails in production, where the
|
46
|
+
`secret_key_base` is set via credentials or environment variable and `hmac_secret`
|
47
|
+
was not explicitly set, the fact that your authentication will now start using
|
48
|
+
HMACs has backwards compatibility considerations. See the [Rodauth
|
49
|
+
documentation][hmac] for instructions on how to safely transition, or just set
|
50
|
+
`hmac_secret nil` in your Rodauth configuration.
|
51
|
+
|
11
52
|
## Installation
|
12
53
|
|
13
54
|
Add the gem to your Gemfile:
|
14
55
|
|
15
56
|
```rb
|
16
|
-
gem "rodauth-rails", "~> 0.
|
57
|
+
gem "rodauth-rails", "~> 0.6"
|
17
58
|
|
18
59
|
# gem "jwt", require: false # for JWT feature
|
19
60
|
# gem "rotp", require: false # for OTP feature
|
@@ -25,10 +66,17 @@ Then run `bundle install`.
|
|
25
66
|
|
26
67
|
Next, run the install generator:
|
27
68
|
|
28
|
-
```
|
69
|
+
```sh
|
29
70
|
$ rails generate rodauth:install
|
30
71
|
```
|
31
72
|
|
73
|
+
Or if you want Rodauth endpoints to be exposed via JSON API:
|
74
|
+
|
75
|
+
```sh
|
76
|
+
$ rails generate rodauth:install --api
|
77
|
+
$ bundle add jwt
|
78
|
+
```
|
79
|
+
|
32
80
|
The generator will create the following files:
|
33
81
|
|
34
82
|
* Rodauth migration at `db/migrate/*_create_rodauth.rb`
|
@@ -111,8 +159,9 @@ end
|
|
111
159
|
|
112
160
|
### Controller
|
113
161
|
|
114
|
-
Your Rodauth app will by default use `RodauthController` for view rendering
|
115
|
-
and
|
162
|
+
Your Rodauth app will by default use `RodauthController` for view rendering,
|
163
|
+
CSRF protection, and running controller callbacks and rescue handlers around
|
164
|
+
Rodauth actions.
|
116
165
|
|
117
166
|
```rb
|
118
167
|
# app/controllers/rodauth_controller.rb
|
@@ -131,9 +180,11 @@ class Account < ApplicationRecord
|
|
131
180
|
end
|
132
181
|
```
|
133
182
|
|
134
|
-
##
|
183
|
+
## Usage
|
184
|
+
|
185
|
+
### Routes
|
135
186
|
|
136
|
-
|
187
|
+
We can see the list of routes our Rodauth middleware handles:
|
137
188
|
|
138
189
|
```sh
|
139
190
|
$ rails rodauth:routes
|
@@ -155,54 +206,57 @@ Routes handled by RodauthApp:
|
|
155
206
|
/close-account rodauth.close_account_path
|
156
207
|
```
|
157
208
|
|
158
|
-
|
159
|
-
|
209
|
+
Using this information, we could add some basic authentication links to our
|
210
|
+
navigation header:
|
160
211
|
|
161
212
|
```erb
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
<% end %>
|
169
|
-
</ul>
|
213
|
+
<% if rodauth.logged_in? %>
|
214
|
+
<%= link_to "Sign out", rodauth.logout_path, method: :post %>
|
215
|
+
<% else %>
|
216
|
+
<%= link_to "Sign in", rodauth.login_path %>
|
217
|
+
<%= link_to "Sign up", rodauth.create_account_path %>
|
218
|
+
<% end %>
|
170
219
|
```
|
171
220
|
|
172
|
-
These
|
221
|
+
These routes are fully functional, feel free to visit them and interact with the
|
173
222
|
pages. The templates that ship with Rodauth aim to provide a complete
|
174
223
|
authentication experience, and the forms use [Bootstrap] markup.
|
175
224
|
|
176
|
-
|
177
|
-
|
225
|
+
### Current account
|
226
|
+
|
227
|
+
To be able to fetch currently authenticated account, let's define a
|
228
|
+
`#current_account` method that fetches the account id from session and
|
229
|
+
retrieves the corresponding account record:
|
178
230
|
|
179
231
|
```rb
|
180
232
|
# app/controllers/application_controller.rb
|
181
233
|
class ApplicationController < ActionController::Base
|
182
|
-
before_action :
|
234
|
+
before_action :current_account, if: -> { rodauth.logged_in? }
|
183
235
|
|
184
236
|
private
|
185
237
|
|
186
|
-
def
|
187
|
-
@current_account
|
238
|
+
def current_account
|
239
|
+
@current_account ||= Account.find(rodauth.session_value)
|
188
240
|
rescue ActiveRecord::RecordNotFound
|
189
241
|
rodauth.logout
|
190
242
|
rodauth.login_required
|
191
243
|
end
|
192
|
-
|
193
|
-
attr_reader :current_account
|
194
244
|
helper_method :current_account
|
195
245
|
end
|
196
246
|
```
|
247
|
+
|
248
|
+
This allows us to access the current account in controllers and views:
|
249
|
+
|
197
250
|
```erb
|
198
251
|
<p>Authenticated as: <%= current_account.email %></p>
|
199
252
|
```
|
200
253
|
|
201
254
|
### Requiring authentication
|
202
255
|
|
203
|
-
|
204
|
-
|
205
|
-
the authentication logic
|
256
|
+
We'll likely want to require authentication for certain parts of our app,
|
257
|
+
redirecting the user to the login page if they're not logged in. We can do this
|
258
|
+
in our Rodauth app's routing block, which helps keep the authentication logic
|
259
|
+
encapsulated:
|
206
260
|
|
207
261
|
```rb
|
208
262
|
# app/lib/rodauth_app.rb
|
@@ -260,9 +314,9 @@ end
|
|
260
314
|
|
261
315
|
### Views
|
262
316
|
|
263
|
-
The templates built into Rodauth are useful when getting started, but
|
264
|
-
|
265
|
-
|
317
|
+
The templates built into Rodauth are useful when getting started, but soon
|
318
|
+
you'll want to start editing the markup. You can run the following command to
|
319
|
+
copy Rodauth templates into your Rails app:
|
266
320
|
|
267
321
|
```sh
|
268
322
|
$ rails generate rodauth:views
|
@@ -286,7 +340,7 @@ $ rails generate rodauth:views --all
|
|
286
340
|
```
|
287
341
|
|
288
342
|
You can also tell the generator to create views into another directory (in this
|
289
|
-
case make sure to rename the Rodauth controller accordingly)
|
343
|
+
case make sure to rename the Rodauth controller accordingly):
|
290
344
|
|
291
345
|
```sh
|
292
346
|
# generates views into app/views/authentication
|
@@ -351,7 +405,7 @@ $ rails generate rodauth:mailer
|
|
351
405
|
```
|
352
406
|
|
353
407
|
This will create a `RodauthMailer` with the associated mailer views in
|
354
|
-
`app/views/rodauth_mailer` directory
|
408
|
+
`app/views/rodauth_mailer` directory:
|
355
409
|
|
356
410
|
```rb
|
357
411
|
# app/mailers/rodauth_mailer.rb
|
@@ -366,8 +420,8 @@ end
|
|
366
420
|
```
|
367
421
|
|
368
422
|
You can then uncomment the lines in your Rodauth configuration to have it call
|
369
|
-
your mailer. If you've enabled additional authentication features
|
370
|
-
override their `
|
423
|
+
your mailer. If you've enabled additional authentication features that send
|
424
|
+
emails, make sure to override their `create_*_email` methods as well.
|
371
425
|
|
372
426
|
```rb
|
373
427
|
# app/lib/rodauth_app.rb
|
@@ -375,43 +429,45 @@ class RodauthApp < Rodauth::Rails::App
|
|
375
429
|
# ...
|
376
430
|
configure do
|
377
431
|
# ...
|
378
|
-
|
379
|
-
|
432
|
+
create_reset_password_email do
|
433
|
+
RodauthMailer.reset_password(email_to, reset_password_email_link)
|
380
434
|
end
|
381
|
-
|
382
|
-
|
435
|
+
create_verify_account_email do
|
436
|
+
RodauthMailer.verify_account(email_to, verify_account_email_link)
|
383
437
|
end
|
384
|
-
|
385
|
-
|
438
|
+
create_verify_login_change_email do |login|
|
439
|
+
RodauthMailer.verify_login_change(login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link)
|
386
440
|
end
|
387
|
-
|
388
|
-
|
441
|
+
create_password_changed_email do
|
442
|
+
RodauthMailer.password_changed(email_to)
|
389
443
|
end
|
390
|
-
#
|
391
|
-
#
|
444
|
+
# create_email_auth_email do
|
445
|
+
# RodauthMailer.email_auth(email_to, email_auth_email_link)
|
392
446
|
# end
|
393
|
-
#
|
394
|
-
#
|
447
|
+
# create_unlock_account_email do
|
448
|
+
# RodauthMailer.unlock_account(email_to, unlock_account_email_link)
|
395
449
|
# end
|
396
|
-
|
450
|
+
send_email do |email|
|
397
451
|
# queue email delivery on the mailer after the transaction commits
|
398
|
-
|
399
|
-
db.after_commit do
|
400
|
-
RodauthMailer.public_send(type, *args).deliver_later
|
401
|
-
end
|
402
|
-
end
|
452
|
+
db.after_commit { email.deliver_later }
|
403
453
|
end
|
404
454
|
# ...
|
405
455
|
end
|
406
456
|
end
|
407
457
|
```
|
408
458
|
|
459
|
+
This approach can be used even if you're using a 3rd-party service for
|
460
|
+
transactional emails, where emails are sent via HTTP instead of SMTP. Whatever
|
461
|
+
the `create_*_email` block returns will be passed to `send_email`, so you can
|
462
|
+
be creative.
|
463
|
+
|
409
464
|
### Migrations
|
410
465
|
|
411
|
-
The install generator will
|
412
|
-
|
466
|
+
The install generator will create a migration for tables used by the Rodauth
|
467
|
+
features enabled by default. For any additional features, you can use the
|
468
|
+
migration generator to create the corresponding tables:
|
413
469
|
|
414
|
-
```
|
470
|
+
```sh
|
415
471
|
$ rails generate rodauth:migration otp sms_codes recovery_codes
|
416
472
|
```
|
417
473
|
```rb
|
@@ -425,36 +481,50 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
|
|
425
481
|
end
|
426
482
|
```
|
427
483
|
|
428
|
-
###
|
484
|
+
### Calling controller methods
|
429
485
|
|
430
|
-
|
431
|
-
|
486
|
+
When using Rodauth before/after hooks or generally overriding your Rodauth
|
487
|
+
configuration, in some cases you might want to call methods defined on your
|
488
|
+
controllers. You can do so with `rails_controller_eval`, for example:
|
432
489
|
|
433
490
|
```rb
|
434
|
-
|
491
|
+
# app/controllers/application_controller.rb
|
492
|
+
class ApplicationController < ActionController::Base
|
493
|
+
private
|
494
|
+
def setup_tracking(account_id)
|
495
|
+
# ... some implementation ...
|
496
|
+
end
|
497
|
+
end
|
435
498
|
```
|
436
|
-
|
437
|
-
The following configuration will enable the Rodauth endpoints to be accessed
|
438
|
-
via JSON requests (in addition to HTML requests):
|
439
|
-
|
440
499
|
```rb
|
441
500
|
# app/lib/rodauth_app.rb
|
442
501
|
class RodauthApp < Rodauth::Rails::App
|
443
|
-
configure
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
# ...
|
502
|
+
configure do
|
503
|
+
after_create_account do
|
504
|
+
rails_controller_eval { setup_tracking(account_id) }
|
505
|
+
end
|
448
506
|
end
|
449
507
|
end
|
450
508
|
```
|
451
509
|
|
452
|
-
|
453
|
-
|
454
|
-
|
510
|
+
### Rodauth instance
|
511
|
+
|
512
|
+
In some cases you might need to use Rodauth more programmatically, and perform
|
513
|
+
Rodauth operations outside of the request context. rodauth-rails gives you the
|
514
|
+
ability to retrieve the Rodauth instance:
|
455
515
|
|
456
|
-
|
457
|
-
|
516
|
+
```rb
|
517
|
+
rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:secondary)
|
518
|
+
|
519
|
+
rodauth.login_url #=> "https://example.com/login"
|
520
|
+
rodauth.account_from_login("user@example.com") # loads user by email
|
521
|
+
rodauth.password_match?("secret") #=> true
|
522
|
+
rodauth.setup_account_verification
|
523
|
+
rodauth.close_account
|
524
|
+
```
|
525
|
+
|
526
|
+
This Rodauth instance will be initialized with basic Rack env that allows is it
|
527
|
+
to generate URLs, using `config.action_mailer.default_url_options` options.
|
458
528
|
|
459
529
|
## How it works
|
460
530
|
|
@@ -500,19 +570,45 @@ end
|
|
500
570
|
The `Rodauth::Rails::App` class is a [Roda] subclass that provides Rails
|
501
571
|
integration for Rodauth:
|
502
572
|
|
503
|
-
* uses
|
504
|
-
* uses
|
573
|
+
* uses Action Dispatch flash instead of Roda's
|
574
|
+
* uses Action Dispatch CSRF protection instead of Roda's
|
505
575
|
* sets [HMAC] secret to Rails' secret key base
|
506
|
-
* uses
|
507
|
-
*
|
576
|
+
* uses Action Controller for rendering templates
|
577
|
+
* runs Action Controller callbacks & rescue handlers around Rodauth actions
|
578
|
+
* uses Action Mailer for sending emails
|
508
579
|
|
509
|
-
The `configure
|
580
|
+
The `configure` method wraps configuring the Rodauth plugin, forwarding
|
510
581
|
any additional [plugin options].
|
511
582
|
|
512
583
|
```rb
|
513
|
-
|
514
|
-
configure
|
515
|
-
configure(:
|
584
|
+
class RodauthApp < Rodauth::Rails::App
|
585
|
+
configure { ... } # defining default Rodauth configuration
|
586
|
+
configure(json: true) { ... } # passing options to the Rodauth plugin
|
587
|
+
configure(:secondary) { ... } # defining multiple Rodauth configurations
|
588
|
+
end
|
589
|
+
```
|
590
|
+
|
591
|
+
The `route` block is provided by Roda, and it's called on each request before
|
592
|
+
it reaches the Rails router.
|
593
|
+
|
594
|
+
```rb
|
595
|
+
class RodauthApp < Rodauth::Rails::App
|
596
|
+
route do |r|
|
597
|
+
# ... called before each request ...
|
598
|
+
end
|
599
|
+
end
|
600
|
+
```
|
601
|
+
|
602
|
+
Since `Rodauth::Rails::App` is just a Roda subclass, you can do anything you
|
603
|
+
would with a Roda app, such as loading additional Roda plugins:
|
604
|
+
|
605
|
+
```rb
|
606
|
+
class RodauthApp < Rodauth::Rails::App
|
607
|
+
plugin :request_headers # easier access to request headers
|
608
|
+
plugin :typecast_params # methods for conversion of request params
|
609
|
+
plugin :default_headers, { "Foo" => "Bar" }
|
610
|
+
# ...
|
611
|
+
end
|
516
612
|
```
|
517
613
|
|
518
614
|
### Sequel
|
@@ -528,6 +624,142 @@ connection (using the [sequel-activerecord_connection] gem).
|
|
528
624
|
This means that, from the usage perspective, Sequel can be considered just
|
529
625
|
as an implementation detail of Rodauth.
|
530
626
|
|
627
|
+
## JSON API
|
628
|
+
|
629
|
+
JSON API support in Rodauth is provided by the [JWT feature][jwt]. You'll need
|
630
|
+
to install the [JWT gem], enable JSON support and enable the JWT feature:
|
631
|
+
|
632
|
+
```sh
|
633
|
+
$ bundle add jwt
|
634
|
+
```
|
635
|
+
```rb
|
636
|
+
# app/lib/rodauth_app.rb
|
637
|
+
class RodauthApp < Rodauth::Rails::App
|
638
|
+
configure(json: :only) do
|
639
|
+
# ...
|
640
|
+
enable :jwt
|
641
|
+
# make sure to store the JWT secret below in a safe place
|
642
|
+
jwt_secret "...your secret key..."
|
643
|
+
# ...
|
644
|
+
end
|
645
|
+
end
|
646
|
+
```
|
647
|
+
|
648
|
+
With the above configuration, Rodauth routes will only be accessible via JSON
|
649
|
+
requests. If you still want to allow HTML access alongside JSON, change `json:
|
650
|
+
:only` to `json: true`.
|
651
|
+
|
652
|
+
Emails will automatically work in JSON-only mode, because `Rodauth::Rails::App`
|
653
|
+
comes with Roda's `render` plugin loaded. They are customized the same as in
|
654
|
+
the non-JSON case.
|
655
|
+
|
656
|
+
## OmniAuth
|
657
|
+
|
658
|
+
While Rodauth doesn't yet come with [OmniAuth] integration, we can build one
|
659
|
+
ourselves using the existing Rodauth API.
|
660
|
+
|
661
|
+
In order to allow the user to login via multiple external providers, let's
|
662
|
+
create an `account_identities` table that will have a many-to-one relationship
|
663
|
+
with the `accounts` table:
|
664
|
+
|
665
|
+
```sh
|
666
|
+
$ rails generate model AccountIdentity
|
667
|
+
```
|
668
|
+
```rb
|
669
|
+
# db/migrate/*_create_account_identities.rb
|
670
|
+
class CreateAccountIdentities < ActiveRecord::Migration
|
671
|
+
def change
|
672
|
+
create_table :account_identities do |t|
|
673
|
+
t.references :account, null: false, foreign_key: { on_delete: :cascade }
|
674
|
+
t.string :provider, null: false
|
675
|
+
t.string :uid, null: false
|
676
|
+
t.jsonb :info, null: false, default: {} # adjust JSON column type for your database
|
677
|
+
|
678
|
+
t.timestamps
|
679
|
+
|
680
|
+
t.index [:provider, :uid], unique: true
|
681
|
+
end
|
682
|
+
end
|
683
|
+
end
|
684
|
+
```
|
685
|
+
```rb
|
686
|
+
# app/models/account_identity.rb
|
687
|
+
class AcccountIdentity < ApplicationRecord
|
688
|
+
belongs_to :account
|
689
|
+
end
|
690
|
+
```
|
691
|
+
```rb
|
692
|
+
# app/models/account.rb
|
693
|
+
class Account < ApplicationRecord
|
694
|
+
has_many :identities, class_name: "AccountIdentity"
|
695
|
+
end
|
696
|
+
```
|
697
|
+
|
698
|
+
Let's assume we want to implement Facebook login, and have added the
|
699
|
+
corresponding OmniAuth strategy to the middleware stack, together with an
|
700
|
+
authorization link on the login form:
|
701
|
+
|
702
|
+
```rb
|
703
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
704
|
+
provider :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"],
|
705
|
+
scope: "email", callback_path: "/auth/facebook/callback"
|
706
|
+
end
|
707
|
+
```
|
708
|
+
```erb
|
709
|
+
<%= link_to "Login via Facebook", "/auth/facebook" %>
|
710
|
+
```
|
711
|
+
|
712
|
+
Let's implement the OmniAuth callback endpoint on our Rodauth controller:
|
713
|
+
|
714
|
+
```rb
|
715
|
+
# config/routes.rb
|
716
|
+
Rails.application.routes.draw do
|
717
|
+
# ...
|
718
|
+
get "/auth/:provider/callback", to: "rodauth#omniauth"
|
719
|
+
end
|
720
|
+
```
|
721
|
+
```rb
|
722
|
+
# app/controllres/rodauth_controller.rb
|
723
|
+
class RodauthController < ApplicationController
|
724
|
+
def omniauth
|
725
|
+
auth = request.env["omniauth.auth"]
|
726
|
+
|
727
|
+
# attempt to find existing identity directly
|
728
|
+
identity = AccountIdentity.find_by(provider: auth["provider"], uid: auth["uid"])
|
729
|
+
|
730
|
+
if identity
|
731
|
+
# update any external info changes
|
732
|
+
identity.update!(info: auth["info"])
|
733
|
+
# set account from identity
|
734
|
+
account = identity.account
|
735
|
+
end
|
736
|
+
|
737
|
+
# attempt to find an existing account by email
|
738
|
+
account ||= Account.find_by(email: auth["info"]["email"])
|
739
|
+
|
740
|
+
# disallow login if account is not verified
|
741
|
+
if account && account.status != rodauth.account_open_status_value
|
742
|
+
redirect_to rodauth.login_path, alert: rodauth.unverified_account_message
|
743
|
+
return
|
744
|
+
end
|
745
|
+
|
746
|
+
# create new account if it doesn't exist
|
747
|
+
unless account
|
748
|
+
account = Account.create!(email: auth["info"]["email"])
|
749
|
+
end
|
750
|
+
|
751
|
+
# create new identity if it doesn't exist
|
752
|
+
unless identity
|
753
|
+
account.identities.create!(provider: auth["provider"], uid: auth["uid"], info: auth["info"])
|
754
|
+
end
|
755
|
+
|
756
|
+
# login with Rodauth
|
757
|
+
rodauth.account_from_login(account.email)
|
758
|
+
rodauth.login("omniauth")
|
759
|
+
end
|
760
|
+
end
|
761
|
+
```
|
762
|
+
|
531
763
|
## Configuring
|
532
764
|
|
533
765
|
For the list of configuration methods provided by Rodauth, see the [feature
|
@@ -561,6 +793,37 @@ Rodauth::Rails.configure do |config|
|
|
561
793
|
end
|
562
794
|
```
|
563
795
|
|
796
|
+
## Custom extensions
|
797
|
+
|
798
|
+
When developing custom extensions for Rodauth inside your Rails project, it's
|
799
|
+
better to use plain modules (at least in the beginning), because Rodauth
|
800
|
+
feature API doesn't yet support Zeitwerk reloading well.
|
801
|
+
|
802
|
+
```rb
|
803
|
+
# app/lib/rodauth_argon2.rb
|
804
|
+
module RodauthArgon2
|
805
|
+
def password_hash(password)
|
806
|
+
Argon2::Password.create(password, t_cost: password_hash_cost, m_cost: password_hash_cost)
|
807
|
+
end
|
808
|
+
|
809
|
+
def password_hash_match?(hash, password)
|
810
|
+
Argon2::Password.verify_password(password, hash)
|
811
|
+
end
|
812
|
+
end
|
813
|
+
```
|
814
|
+
```rb
|
815
|
+
# app/lib/rodauth_app.rb
|
816
|
+
class RodauthApp < Rodauth::Rails::App
|
817
|
+
configure do
|
818
|
+
# ...
|
819
|
+
auth_class_eval do
|
820
|
+
include RodauthArgon2
|
821
|
+
end
|
822
|
+
# ...
|
823
|
+
end
|
824
|
+
end
|
825
|
+
```
|
826
|
+
|
564
827
|
## Testing
|
565
828
|
|
566
829
|
If you're writing system tests, it's generally better to go through the actual
|
@@ -633,17 +896,15 @@ Rodauth method for creating database functions:
|
|
633
896
|
|
634
897
|
```rb
|
635
898
|
# db/migrate/*_create_rodauth_database_functions.rb
|
899
|
+
require "rodauth/migrations"
|
900
|
+
|
636
901
|
class CreateRodauthDatabaseFunctions < ActiveRecord::Migration
|
637
902
|
def up
|
638
|
-
# ...
|
639
903
|
Rodauth.create_database_authentication_functions(DB)
|
640
|
-
# ...
|
641
904
|
end
|
642
905
|
|
643
906
|
def down
|
644
|
-
# ...
|
645
907
|
Rodauth.drop_database_authentication_functions(DB)
|
646
|
-
# ...
|
647
908
|
end
|
648
909
|
end
|
649
910
|
```
|
@@ -700,9 +961,7 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
|
|
700
961
|
|
701
962
|
[Rodauth]: https://github.com/jeremyevans/rodauth
|
702
963
|
[Sequel]: https://github.com/jeremyevans/sequel
|
703
|
-
[rendering views outside of controllers]: https://blog.bigbinary.com/2016/01/08/rendering-views-outside-of-controllers-in-rails-5.html
|
704
964
|
[feature documentation]: http://rodauth.jeremyevans.net/documentation.html
|
705
|
-
[JWT feature]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
|
706
965
|
[JWT gem]: https://github.com/jwt/ruby-jwt
|
707
966
|
[Bootstrap]: https://getbootstrap.com/
|
708
967
|
[Roda]: http://roda.jeremyevans.net/
|
@@ -711,3 +970,20 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
|
|
711
970
|
[Rodauth migration]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-Creating+tables
|
712
971
|
[sequel-activerecord_connection]: https://github.com/janko/sequel-activerecord_connection
|
713
972
|
[plugin options]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-Plugin+Options
|
973
|
+
[hmac]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-HMAC
|
974
|
+
[OmniAuth]: https://github.com/omniauth/omniauth
|
975
|
+
[otp]: http://rodauth.jeremyevans.net/rdoc/files/doc/otp_rdoc.html
|
976
|
+
[sms_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/sms_codes_rdoc.html
|
977
|
+
[recovery_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/recovery_codes_rdoc.html
|
978
|
+
[webauthn]: http://rodauth.jeremyevans.net/rdoc/files/doc/webauthn_rdoc.html
|
979
|
+
[jwt]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
|
980
|
+
[email_auth]: http://rodauth.jeremyevans.net/rdoc/files/doc/email_auth_rdoc.html
|
981
|
+
[audit_logging]: http://rodauth.jeremyevans.net/rdoc/files/doc/audit_logging_rdoc.html
|
982
|
+
[password protection]: https://github.com/jeremyevans/rodauth#label-Password+Hash+Access+Via+Database+Functions
|
983
|
+
[bruteforce tokens]: https://github.com/jeremyevans/rodauth#label-Tokens
|
984
|
+
[password_complexity]: http://rodauth.jeremyevans.net/rdoc/files/doc/password_complexity_rdoc.html
|
985
|
+
[disallow_password_reuse]: http://rodauth.jeremyevans.net/rdoc/files/doc/disallow_password_reuse_rdoc.html
|
986
|
+
[password_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/password_expiration_rdoc.html
|
987
|
+
[session_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/session_expiration_rdoc.html
|
988
|
+
[single_session]: http://rodauth.jeremyevans.net/rdoc/files/doc/single_session_rdoc.html
|
989
|
+
[account_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html
|
@@ -13,6 +13,15 @@ module Rodauth
|
|
13
13
|
source_root "#{__dir__}/templates"
|
14
14
|
namespace "rodauth:install"
|
15
15
|
|
16
|
+
# The :api option is a Rails-recognized option that always
|
17
|
+
# defaults to false, so we make it use our provided default
|
18
|
+
# value instead.
|
19
|
+
def self.default_value_for_option(name, options)
|
20
|
+
name == :api ? options[:default] : super
|
21
|
+
end
|
22
|
+
|
23
|
+
class_option :api, type: :boolean, desc: "Generate JSON-only configuration"
|
24
|
+
|
16
25
|
def create_rodauth_migration
|
17
26
|
return unless defined?(ActiveRecord::Base)
|
18
27
|
|
@@ -35,8 +44,6 @@ module Rodauth
|
|
35
44
|
end
|
36
45
|
|
37
46
|
def create_rodauth_controller
|
38
|
-
return if api_only?
|
39
|
-
|
40
47
|
template "app/controllers/rodauth_controller.rb"
|
41
48
|
end
|
42
49
|
|
@@ -77,9 +84,11 @@ module Rodauth
|
|
77
84
|
end
|
78
85
|
|
79
86
|
def api_only?
|
80
|
-
|
81
|
-
|
82
|
-
::Rails.
|
87
|
+
if options.key?(:api)
|
88
|
+
options[:api]
|
89
|
+
elsif ::Rails.gem_version >= Gem::Version.new("5.0")
|
90
|
+
::Rails.application.config.api_only
|
91
|
+
end
|
83
92
|
end
|
84
93
|
|
85
94
|
def migration_features
|
@@ -15,11 +15,9 @@ class RodauthApp < Rodauth::Rails::App
|
|
15
15
|
# Defaults to Rails `secret_key_base`, but you can use your own secret key.
|
16
16
|
# hmac_secret "<%= SecureRandom.hex(64) %>"
|
17
17
|
|
18
|
-
<% unless api_only? -%>
|
19
18
|
# Specify the controller used for view rendering and CSRF verification.
|
20
19
|
rails_controller { RodauthController }
|
21
20
|
|
22
|
-
<% end -%>
|
23
21
|
# Store account status in a text column.
|
24
22
|
account_status_column :status
|
25
23
|
account_unverified_status_value "unverified"
|
@@ -59,31 +57,27 @@ class RodauthApp < Rodauth::Rails::App
|
|
59
57
|
|
60
58
|
# ==> Emails
|
61
59
|
# Uncomment the lines below once you've imported mailer views.
|
62
|
-
#
|
63
|
-
#
|
60
|
+
# create_reset_password_email do
|
61
|
+
# RodauthMailer.reset_password(email_to, reset_password_email_link)
|
64
62
|
# end
|
65
|
-
#
|
66
|
-
#
|
63
|
+
# create_verify_account_email do
|
64
|
+
# RodauthMailer.verify_account(email_to, verify_account_email_link)
|
67
65
|
# end
|
68
|
-
#
|
69
|
-
#
|
66
|
+
# create_verify_login_change_email do |login|
|
67
|
+
# RodauthMailer.verify_login_change(login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link)
|
70
68
|
# end
|
71
|
-
#
|
72
|
-
#
|
69
|
+
# create_password_changed_email do
|
70
|
+
# RodauthMailer.password_changed(email_to)
|
73
71
|
# end
|
74
|
-
# #
|
75
|
-
# #
|
72
|
+
# # create_email_auth_email do
|
73
|
+
# # RodauthMailer.email_auth(email_to, email_auth_email_link)
|
76
74
|
# # end
|
77
|
-
# #
|
78
|
-
# #
|
75
|
+
# # create_unlock_account_email do
|
76
|
+
# # RodauthMailer.unlock_account(email_to, unlock_account_email_link)
|
79
77
|
# # end
|
80
|
-
#
|
78
|
+
# send_email do |email|
|
81
79
|
# # queue email delivery on the mailer after the transaction commits
|
82
|
-
#
|
83
|
-
# db.after_commit do
|
84
|
-
# RodauthMailer.public_send(type, *args).deliver_later
|
85
|
-
# end
|
86
|
-
# end
|
80
|
+
# db.after_commit { email.deliver_later }
|
87
81
|
# end
|
88
82
|
|
89
83
|
# In the meantime you can tweak settings for emails created by Rodauth
|
data/lib/rodauth/rails.rb
CHANGED
@@ -9,14 +9,43 @@ module Rodauth
|
|
9
9
|
# This allows the developer to avoid loading Rodauth at boot time.
|
10
10
|
autoload :App, "rodauth/rails/app"
|
11
11
|
|
12
|
-
def self.configure
|
13
|
-
yield self
|
14
|
-
end
|
15
|
-
|
16
12
|
@app = nil
|
17
13
|
@middleware = true
|
18
14
|
|
19
15
|
class << self
|
16
|
+
def rodauth(name = nil)
|
17
|
+
url_options = ActionMailer::Base.default_url_options
|
18
|
+
|
19
|
+
scheme = url_options[:protocol] || "http"
|
20
|
+
port = url_options[:port]
|
21
|
+
port ||= Rack::Request::DEFAULT_PORTS[scheme] if Gem::Version.new(Rack.release) < Gem::Version.new("2.0")
|
22
|
+
host = url_options[:host]
|
23
|
+
host += ":#{port}" if port
|
24
|
+
|
25
|
+
rack_env = {
|
26
|
+
"HTTP_HOST" => host,
|
27
|
+
"rack.url_scheme" => scheme,
|
28
|
+
}
|
29
|
+
|
30
|
+
scope = app.new(rack_env)
|
31
|
+
|
32
|
+
scope.rodauth(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
if ::Rails.gem_version >= Gem::Version.new("5.2")
|
36
|
+
def secret_key_base
|
37
|
+
::Rails.application.secret_key_base
|
38
|
+
end
|
39
|
+
else
|
40
|
+
def secret_key_base
|
41
|
+
::Rails.application.secrets.secret_key_base
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def configure
|
46
|
+
yield self
|
47
|
+
end
|
48
|
+
|
20
49
|
attr_writer :app
|
21
50
|
attr_writer :middleware
|
22
51
|
|
data/lib/rodauth/rails/app.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
require "roda"
|
2
|
+
require "rodauth"
|
3
|
+
require "rodauth/rails/feature"
|
2
4
|
|
3
5
|
module Rodauth
|
4
6
|
module Rails
|
5
7
|
# The superclass for creating a Rodauth middleware.
|
6
8
|
class App < Roda
|
7
|
-
|
9
|
+
require "rodauth/rails/app/middleware"
|
10
|
+
plugin Middleware
|
11
|
+
|
8
12
|
plugin :hooks
|
9
13
|
plugin :render, layout: false
|
10
14
|
|
@@ -18,6 +22,12 @@ module Rodauth
|
|
18
22
|
# load the Rails integration
|
19
23
|
enable :rails
|
20
24
|
|
25
|
+
if options[:json] == :only && ActionPack.version >= Gem::Version.new("5.0")
|
26
|
+
rails_controller { ActionController::API }
|
27
|
+
else
|
28
|
+
rails_controller { ActionController::Base }
|
29
|
+
end
|
30
|
+
|
21
31
|
# database functions are more complex to set up, so disable them by default
|
22
32
|
use_database_authentication_functions? false
|
23
33
|
|
@@ -25,7 +35,7 @@ module Rodauth
|
|
25
35
|
set_deadline_values? true
|
26
36
|
|
27
37
|
# use HMACs for additional security
|
28
|
-
hmac_secret { ::Rails.
|
38
|
+
hmac_secret { Rodauth::Rails.secret_key_base }
|
29
39
|
|
30
40
|
# evaluate user configuration
|
31
41
|
instance_exec(&block)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Rodauth
|
2
|
+
module Rails
|
3
|
+
class App
|
4
|
+
# Roda plugin that extends middleware plugin by propagating response headers.
|
5
|
+
module Middleware
|
6
|
+
def self.load_dependencies(app)
|
7
|
+
app.plugin :hooks
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.configure(app)
|
11
|
+
app.after do
|
12
|
+
if response.empty? && response.headers.any?
|
13
|
+
env["rodauth.rails.headers"] = response.headers
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
app.plugin :middleware, handle_result: -> (env, res) do
|
18
|
+
if headers = env.delete("rodauth.rails.headers")
|
19
|
+
res[1] = headers.merge(res[1])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -9,10 +9,11 @@ module Rodauth
|
|
9
9
|
:rails_csrf_param,
|
10
10
|
:rails_csrf_token,
|
11
11
|
:rails_check_csrf!,
|
12
|
-
:rails_controller_instance,
|
13
12
|
:rails_controller,
|
14
13
|
)
|
15
14
|
|
15
|
+
auth_cached_method :rails_controller_instance
|
16
|
+
|
16
17
|
# Renders templates with layout. First tries to render a user-defined
|
17
18
|
# template, otherwise falls back to Rodauth's template.
|
18
19
|
def view(page, *)
|
@@ -25,7 +26,12 @@ module Rodauth
|
|
25
26
|
def render(page)
|
26
27
|
rails_render(partial: page.tr("-", "_"), layout: false) ||
|
27
28
|
rails_render(action: page.tr("-", "_"), layout: false) ||
|
28
|
-
super
|
29
|
+
super.html_safe
|
30
|
+
end
|
31
|
+
|
32
|
+
# Render Rails CSRF tags in Rodauth templates.
|
33
|
+
def csrf_tag(*)
|
34
|
+
rails_csrf_tag
|
29
35
|
end
|
30
36
|
|
31
37
|
# Verify Rails' authenticity token.
|
@@ -38,18 +44,77 @@ module Rodauth
|
|
38
44
|
true
|
39
45
|
end
|
40
46
|
|
41
|
-
# Render Rails CSRF tags in Rodauth templates.
|
42
|
-
def csrf_tag(*)
|
43
|
-
rails_csrf_tag
|
44
|
-
end
|
45
|
-
|
46
47
|
# Default the flash error key to Rails' default :alert.
|
47
48
|
def flash_error_key
|
48
49
|
:alert
|
49
50
|
end
|
50
51
|
|
52
|
+
# Evaluates the block in context of a Rodauth controller instance.
|
53
|
+
def rails_controller_eval(&block)
|
54
|
+
rails_controller_instance.instance_exec(&block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def button(*)
|
58
|
+
super.html_safe
|
59
|
+
end
|
60
|
+
|
51
61
|
private
|
52
62
|
|
63
|
+
# Runs controller callbacks and rescue handlers around Rodauth actions.
|
64
|
+
def _around_rodauth(&block)
|
65
|
+
result = nil
|
66
|
+
|
67
|
+
rails_controller_rescue do
|
68
|
+
rails_controller_callbacks do
|
69
|
+
result = catch(:halt) { super(&block) }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
if rails_controller_instance.performed?
|
74
|
+
rails_controller_response
|
75
|
+
elsif result
|
76
|
+
result[1].merge!(rails_controller_instance.response.headers)
|
77
|
+
throw :halt, result
|
78
|
+
else
|
79
|
+
result
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Runs any #(before|around|after)_action controller callbacks.
|
84
|
+
def rails_controller_callbacks
|
85
|
+
# don't verify CSRF token as part of callbacks, Rodauth will do that
|
86
|
+
rails_controller_forgery_protection { false }
|
87
|
+
|
88
|
+
rails_controller_instance.run_callbacks(:process_action) do
|
89
|
+
# turn the setting back to default so that form tags generate CSRF tags
|
90
|
+
rails_controller_forgery_protection { rails_controller.allow_forgery_protection }
|
91
|
+
|
92
|
+
yield
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Runs any registered #rescue_from controller handlers.
|
97
|
+
def rails_controller_rescue
|
98
|
+
yield
|
99
|
+
rescue Exception => exception
|
100
|
+
rails_controller_instance.rescue_with_handler(exception) || raise
|
101
|
+
|
102
|
+
unless rails_controller_instance.performed?
|
103
|
+
raise Rodauth::Rails::Error, "rescue_from handler didn't write any response"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns Roda response from controller response if set.
|
108
|
+
def rails_controller_response
|
109
|
+
controller_response = rails_controller_instance.response
|
110
|
+
|
111
|
+
response.status = controller_response.status
|
112
|
+
response.headers.merge! controller_response.headers
|
113
|
+
response.write controller_response.body
|
114
|
+
|
115
|
+
request.halt
|
116
|
+
end
|
117
|
+
|
53
118
|
# Create emails with ActionMailer which uses configured delivery method.
|
54
119
|
def create_email_to(to, subject, body)
|
55
120
|
Mailer.create_email(to: to, from: email_from, subject: "#{email_subject_prefix}#{subject}", body: body)
|
@@ -62,13 +127,16 @@ module Rodauth
|
|
62
127
|
|
63
128
|
# Calls the Rails renderer, returning nil if a template is missing.
|
64
129
|
def rails_render(*args)
|
65
|
-
return if
|
130
|
+
return if rails_api_controller?
|
66
131
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
132
|
+
rails_controller_instance.render_to_string(*args)
|
133
|
+
rescue ActionView::MissingTemplate
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
|
137
|
+
# Calls the controller to verify the authenticity token.
|
138
|
+
def rails_check_csrf!
|
139
|
+
rails_controller_instance.send(:verify_authenticity_token)
|
72
140
|
end
|
73
141
|
|
74
142
|
# Hidden tag with Rails CSRF token inserted into Rodauth templates.
|
@@ -86,30 +154,37 @@ module Rodauth
|
|
86
154
|
rails_controller_instance.send(:form_authenticity_token)
|
87
155
|
end
|
88
156
|
|
89
|
-
#
|
90
|
-
def
|
91
|
-
|
157
|
+
# allows/disables forgery protection
|
158
|
+
def rails_controller_forgery_protection(&value)
|
159
|
+
return if rails_api_controller?
|
160
|
+
|
161
|
+
rails_controller_instance.allow_forgery_protection = value.call
|
92
162
|
end
|
93
163
|
|
94
164
|
# Instances of the configured controller with current request's env hash.
|
95
|
-
def
|
96
|
-
|
97
|
-
|
165
|
+
def _rails_controller_instance
|
166
|
+
controller = rails_controller.new
|
167
|
+
rails_request = ActionDispatch::Request.new(scope.env)
|
98
168
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
instance.send(:set_response!, request)
|
104
|
-
instance.instance_variable_set(:@_request, request)
|
105
|
-
end
|
169
|
+
prepare_rails_controller(controller, rails_request)
|
170
|
+
|
171
|
+
controller
|
172
|
+
end
|
106
173
|
|
107
|
-
|
174
|
+
if ActionPack.version >= Gem::Version.new("5.0")
|
175
|
+
def prepare_rails_controller(controller, rails_request)
|
176
|
+
controller.set_request! rails_request
|
177
|
+
controller.set_response! rails_controller.make_response!(rails_request)
|
178
|
+
end
|
179
|
+
else
|
180
|
+
def prepare_rails_controller(controller, rails_request)
|
181
|
+
controller.send(:set_response!, rails_request)
|
182
|
+
controller.instance_variable_set(:@_request, rails_request)
|
183
|
+
end
|
108
184
|
end
|
109
185
|
|
110
|
-
|
111
|
-
|
112
|
-
ActionController::Base
|
186
|
+
def rails_api_controller?
|
187
|
+
defined?(ActionController::API) && rails_controller <= ActionController::API
|
113
188
|
end
|
114
189
|
|
115
190
|
# ActionMailer subclass for correct email delivering.
|
@@ -3,19 +3,16 @@ namespace :rodauth do
|
|
3
3
|
app = Rodauth::Rails.app
|
4
4
|
|
5
5
|
puts "Routes handled by #{app}:"
|
6
|
-
puts
|
7
6
|
|
8
|
-
app.opts[:rodauths].
|
9
|
-
|
10
|
-
.map { |handle_method| handle_method.to_s.sub(/\Ahandle_/, "") }
|
11
|
-
.uniq
|
7
|
+
app.opts[:rodauths].each_key do |rodauth_name|
|
8
|
+
rodauth = Rodauth::Rails.rodauth(rodauth_name)
|
12
9
|
|
13
|
-
|
10
|
+
routes = rodauth.class.routes.map do |handle_method|
|
11
|
+
path_method = "#{handle_method.to_s.sub(/\Ahandle_/, "")}_path"
|
14
12
|
|
15
|
-
routes = route_names.map do |name|
|
16
13
|
[
|
17
|
-
rodauth.public_send(
|
18
|
-
"rodauth#{rodauth_name && "(:#{rodauth_name})"}.#{
|
14
|
+
rodauth.public_send(path_method),
|
15
|
+
"rodauth#{rodauth_name && "(:#{rodauth_name})"}.#{path_method}",
|
19
16
|
]
|
20
17
|
end
|
21
18
|
|
@@ -25,8 +22,7 @@ namespace :rodauth do
|
|
25
22
|
"#{path.ljust(padding)} #{code}"
|
26
23
|
end
|
27
24
|
|
28
|
-
puts " #{route_lines.join("\n ")}"
|
29
|
-
puts
|
25
|
+
puts "\n #{route_lines.join("\n ")}" unless route_lines.empty?
|
30
26
|
end
|
31
27
|
end
|
32
28
|
end
|
data/rodauth-rails.gemspec
CHANGED
@@ -17,8 +17,10 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
19
|
spec.add_dependency "railties", ">= 4.2", "< 7"
|
20
|
-
spec.add_dependency "rodauth", "~> 2.
|
20
|
+
spec.add_dependency "rodauth", "~> 2.7"
|
21
21
|
spec.add_dependency "sequel-activerecord_connection", "~> 1.1"
|
22
22
|
spec.add_dependency "tilt"
|
23
23
|
spec.add_dependency "bcrypt"
|
24
|
+
|
25
|
+
spec.add_development_dependency "jwt"
|
24
26
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janko Marohnić
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -36,14 +36,14 @@ dependencies:
|
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '2.
|
39
|
+
version: '2.7'
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '2.
|
46
|
+
version: '2.7'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: sequel-activerecord_connection
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,6 +86,20 @@ dependencies:
|
|
86
86
|
- - ">="
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: jwt
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
89
103
|
description: Provides Rails integration for Rodauth.
|
90
104
|
email:
|
91
105
|
- janko.marohnic@gmail.com
|
@@ -187,10 +201,10 @@ files:
|
|
187
201
|
- lib/generators/rodauth/templates/db/migrate/create_rodauth.rb
|
188
202
|
- lib/generators/rodauth/views_generator.rb
|
189
203
|
- lib/rodauth-rails.rb
|
190
|
-
- lib/rodauth/features/rails.rb
|
191
204
|
- lib/rodauth/rails.rb
|
192
205
|
- lib/rodauth/rails/app.rb
|
193
206
|
- lib/rodauth/rails/app/flash.rb
|
207
|
+
- lib/rodauth/rails/app/middleware.rb
|
194
208
|
- lib/rodauth/rails/controller_methods.rb
|
195
209
|
- lib/rodauth/rails/feature.rb
|
196
210
|
- lib/rodauth/rails/middleware.rb
|
@@ -1 +0,0 @@
|
|
1
|
-
require "rodauth/rails/feature"
|