rodauth-rails 0.4.2 → 0.8.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -1
  3. data/README.md +419 -83
  4. data/lib/generators/rodauth/install_generator.rb +38 -23
  5. data/lib/generators/rodauth/migration/account_expiration.erb +7 -0
  6. data/lib/generators/rodauth/migration/active_sessions.erb +7 -0
  7. data/lib/generators/rodauth/migration/audit_logging.erb +16 -0
  8. data/lib/generators/rodauth/migration/base.erb +19 -0
  9. data/lib/generators/rodauth/migration/disallow_password_reuse.erb +5 -0
  10. data/lib/generators/rodauth/migration/email_auth.erb +7 -0
  11. data/lib/generators/rodauth/migration/jwt_refresh.erb +7 -0
  12. data/lib/generators/rodauth/migration/lockout.erb +11 -0
  13. data/lib/generators/rodauth/migration/otp.erb +7 -0
  14. data/lib/generators/rodauth/migration/password_expiration.erb +5 -0
  15. data/lib/generators/rodauth/migration/recovery_codes.erb +6 -0
  16. data/lib/generators/rodauth/migration/remember.erb +6 -0
  17. data/lib/generators/rodauth/migration/reset_password.erb +7 -0
  18. data/lib/generators/rodauth/migration/single_session.erb +5 -0
  19. data/lib/generators/rodauth/migration/sms_codes.erb +8 -0
  20. data/lib/generators/rodauth/migration/verify_account.erb +7 -0
  21. data/lib/generators/rodauth/migration/verify_login_change.erb +7 -0
  22. data/lib/generators/rodauth/migration/webauthn.erb +12 -0
  23. data/lib/generators/rodauth/migration_generator.rb +32 -0
  24. data/lib/generators/rodauth/migration_helpers.rb +69 -0
  25. data/lib/generators/rodauth/templates/app/controllers/rodauth_controller.rb +2 -1
  26. data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +18 -20
  27. data/lib/generators/rodauth/templates/config/initializers/sequel.rb +1 -5
  28. data/lib/generators/rodauth/templates/db/migrate/create_rodauth.rb +2 -176
  29. data/lib/rodauth/rails.rb +33 -4
  30. data/lib/rodauth/rails/app.rb +6 -2
  31. data/lib/rodauth/rails/app/flash.rb +1 -1
  32. data/lib/rodauth/rails/app/middleware.rb +26 -0
  33. data/lib/rodauth/rails/feature.rb +100 -30
  34. data/lib/rodauth/rails/railtie.rb +6 -0
  35. data/lib/rodauth/rails/tasks.rake +28 -0
  36. data/lib/rodauth/rails/version.rb +1 -1
  37. data/rodauth-rails.gemspec +1 -1
  38. metadata +26 -5
  39. data/lib/rodauth/features/rails.rb +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 230b201ad8b9f7c27a9ca00db1a27b4166b7ca0d202f32651fc15ff663bb860e
4
- data.tar.gz: b6b7205916994f829f8eb74c76d8b6172c52339b157f464afd2cd9bfa96f3f25
3
+ metadata.gz: 0f1312dbd1bb4dc0d954c77a5ff350b5c9e1ff3fc4dd45b8834cd3e7d0280a22
4
+ data.tar.gz: 5dda5720126361589a428add9b8256b35aa53644166ca7a8a6d14c5baef53f02
5
5
  SHA512:
6
- metadata.gz: fc5645e124796a66c59c51b3b1ab2f1a3b9d7168b53f0703543e42187bb29470d09d3b1103e543dad66ba9a7f940bd469383d767c7acf8fc1d1c8ac7675848e9
7
- data.tar.gz: 89cfd6289466b276240ffbb1111fef0b4274ae2479f5a1dbdaaa193333093508ddc692f47e7a8d57ffeafe70a7d25039371a7ebd88646d962f63f577039afc9c
6
+ metadata.gz: f70e5a44db25c016fe92169be342d1f489cd0e3307fe6c06dbe822c28c05f55dc696b26721836d315daabbbfb0889d18357cec3bb7aa52932649f5ecb08ceedb
7
+ data.tar.gz: 6af3cd43f9266729049d984c9da58beff019dd2f0148465d65c8f814602d9a9678308d752ef469f08ab381a1373b919d1e61b62b204751d95ca57d10ed05de2a
@@ -1,4 +1,42 @@
1
- # 0.4.2 (2020-11-08)
1
+ ## 0.8.0 (2021-01-03)
2
+
3
+ * Add `--api` option to `rodauth:install` generator for choosing JSON-only configuration (@janko)
4
+
5
+ * Don't blow up when a Rodauth request is made using an unsupported HTTP verb (@janko)
6
+
7
+ ## 0.7.0 (2020-11-27)
8
+
9
+ * Add `#rails_controller_eval` method for running code in context of a controller instance (@janko)
10
+
11
+ * Detect `secret_key_base` from credentials and `$SECRET_KEY_BASE` environment variable (@janko)
12
+
13
+ ## 0.6.1 (2020-11-25)
14
+
15
+ * Generate the Rodauth controller for API-only Rails apps as well (@janko)
16
+
17
+ * Fix remember cookie deadline not extending in remember feature (@janko)
18
+
19
+ ## 0.6.0 (2020-11-22)
20
+
21
+ * Add `Rodauth::Rails.rodauth` method for retrieving Rodauth instance outside of request context (@janko)
22
+
23
+ * Add default Action Dispatch response headers in Rodauth responses (@janko)
24
+
25
+ * Run controller rescue handlers around Rodauth actions (@janko)
26
+
27
+ * Run controller action callbacks around Rodauth actions (@janko)
28
+
29
+ ## 0.5.0 (2020-11-16)
30
+
31
+ * Support more Active Record adapters in `rodauth:install` generator (@janko)
32
+
33
+ * Add `rodauth:migration` generator for creating tables of specified features (@janko)
34
+
35
+ * Use UUIDs for primary keys if so configured in Rails generators (@janko)
36
+
37
+ * Add `rodauth:routes` rake task for printing routes handled by Rodauth middleware (@janko)
38
+
39
+ ## 0.4.2 (2020-11-08)
2
40
 
3
41
  * Drop support for Ruby 2.2 (@janko)
4
42
 
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.4"
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`
@@ -54,7 +102,6 @@ class CreateRodauth < ActiveRecord::Migration
54
102
  create_table :account_verification_keys do |t| ... end
55
103
  create_table :account_login_change_keys do |t| ... end
56
104
  create_table :account_remember_keys do |t| ... end
57
- # ...
58
105
  end
59
106
  end
60
107
  ```
@@ -88,7 +135,7 @@ ActiveRecord connection.
88
135
  require "sequel/core"
89
136
 
90
137
  # initialize Sequel and have it reuse Active Record's database connection
91
- DB = Sequel.postgres(extensions: :activerecord_connection)
138
+ DB = Sequel.connect("postgresql://", extensions: :activerecord_connection)
92
139
  ```
93
140
 
94
141
  ### Rodauth app
@@ -112,8 +159,9 @@ end
112
159
 
113
160
  ### Controller
114
161
 
115
- Your Rodauth app will by default use `RodauthController` for view rendering
116
- and CSRF protection.
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.
117
165
 
118
166
  ```rb
119
167
  # app/controllers/rodauth_controller.rb
@@ -121,7 +169,7 @@ class RodauthController < ApplicationController
121
169
  end
122
170
  ```
123
171
 
124
- ### Account Model
172
+ ### Account model
125
173
 
126
174
  Rodauth stores user accounts in the `accounts` table, so the generator will
127
175
  also create an `Account` model for custom use.
@@ -132,56 +180,83 @@ class Account < ApplicationRecord
132
180
  end
133
181
  ```
134
182
 
135
- ## Getting started
183
+ ## Usage
136
184
 
137
- Let's start by adding some basic authentication navigation links to our home
138
- page:
185
+ ### Routes
186
+
187
+ We can see the list of routes our Rodauth middleware handles:
188
+
189
+ ```sh
190
+ $ rails rodauth:routes
191
+ ```
192
+ ```
193
+ Routes handled by RodauthApp:
194
+
195
+ /login rodauth.login_path
196
+ /create-account rodauth.create_account_path
197
+ /verify-account-resend rodauth.verify_account_resend_path
198
+ /verify-account rodauth.verify_account_path
199
+ /change-password rodauth.change_password_path
200
+ /change-login rodauth.change_login_path
201
+ /logout rodauth.logout_path
202
+ /remember rodauth.remember_path
203
+ /reset-password-request rodauth.reset_password_request_path
204
+ /reset-password rodauth.reset_password_path
205
+ /verify-login-change rodauth.verify_login_change_path
206
+ /close-account rodauth.close_account_path
207
+ ```
208
+
209
+ Using this information, we could add some basic authentication links to our
210
+ navigation header:
139
211
 
140
212
  ```erb
141
- <ul>
142
- <% if rodauth.authenticated? %>
143
- <li><%= link_to "Sign out", rodauth.logout_path, method: :post %></li>
144
- <% else %>
145
- <li><%= link_to "Sign in", rodauth.login_path %></li>
146
- <li><%= link_to "Sign up", rodauth.create_account_path %></li>
147
- <% end %>
148
- </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 %>
149
219
  ```
150
220
 
151
- These links are fully functional, feel free to visit them and interact with the
221
+ These routes are fully functional, feel free to visit them and interact with the
152
222
  pages. The templates that ship with Rodauth aim to provide a complete
153
223
  authentication experience, and the forms use [Bootstrap] markup.
154
224
 
155
- Let's also load the account record for authenticated requests and expose it via
156
- `#current_account`:
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:
157
230
 
158
231
  ```rb
159
232
  # app/controllers/application_controller.rb
160
233
  class ApplicationController < ActionController::Base
161
- before_action :load_account, if: -> { rodauth.authenticated? }
234
+ before_action :current_account, if: -> { rodauth.logged_in? }
162
235
 
163
236
  private
164
237
 
165
- def load_account
166
- @current_account = Account.find(rodauth.session_value)
238
+ def current_account
239
+ @current_account ||= Account.find(rodauth.session_value)
167
240
  rescue ActiveRecord::RecordNotFound
168
241
  rodauth.logout
169
242
  rodauth.login_required
170
243
  end
171
-
172
- attr_reader :current_account
173
244
  helper_method :current_account
174
245
  end
175
246
  ```
247
+
248
+ This allows us to access the current account in controllers and views:
249
+
176
250
  ```erb
177
251
  <p>Authenticated as: <%= current_account.email %></p>
178
252
  ```
179
253
 
180
254
  ### Requiring authentication
181
255
 
182
- Next, we'll likely want to require authentication for certain sections/pages of
183
- our app. We can do this in our Rodauth app's routing block, which helps keep
184
- the authentication logic encapsulated:
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:
185
260
 
186
261
  ```rb
187
262
  # app/lib/rodauth_app.rb
@@ -239,9 +314,9 @@ end
239
314
 
240
315
  ### Views
241
316
 
242
- The templates built into Rodauth are useful when getting started, but at some
243
- point we'll probably want more control over the markup. For that we can run the
244
- following command:
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:
245
320
 
246
321
  ```sh
247
322
  $ rails generate rodauth:views
@@ -265,7 +340,7 @@ $ rails generate rodauth:views --all
265
340
  ```
266
341
 
267
342
  You can also tell the generator to create views into another directory (in this
268
- case don't forget to rename the Rodauth controller accordingly).
343
+ case make sure to rename the Rodauth controller accordingly):
269
344
 
270
345
  ```sh
271
346
  # generates views into app/views/authentication
@@ -300,8 +375,8 @@ end
300
375
 
301
376
  ### Mailer
302
377
 
303
- Rodauth may send emails as part of the authentication flow. Most email settings
304
- can be customized:
378
+ Depending on the features you've enabled, Rodauth may send emails as part of
379
+ the authentication flow. Most email settings can be customized:
305
380
 
306
381
  ```rb
307
382
  # app/lib/rodauth_app.rb
@@ -330,7 +405,7 @@ $ rails generate rodauth:mailer
330
405
  ```
331
406
 
332
407
  This will create a `RodauthMailer` with the associated mailer views in
333
- `app/views/rodauth_mailer` directory.
408
+ `app/views/rodauth_mailer` directory:
334
409
 
335
410
  ```rb
336
411
  # app/mailers/rodauth_mailer.rb
@@ -345,8 +420,8 @@ end
345
420
  ```
346
421
 
347
422
  You can then uncomment the lines in your Rodauth configuration to have it call
348
- your mailer. If you've enabled additional authentication features, make sure to
349
- override their `send_*_email` methods as well.
423
+ your mailer. If you've enabled additional authentication features that send
424
+ emails, make sure to override their `create_*_email` methods as well.
350
425
 
351
426
  ```rb
352
427
  # app/lib/rodauth_app.rb
@@ -354,67 +429,102 @@ class RodauthApp < Rodauth::Rails::App
354
429
  # ...
355
430
  configure do
356
431
  # ...
357
- send_reset_password_email do
358
- mailer_send(:reset_password, email_to, reset_password_email_link)
432
+ create_reset_password_email do
433
+ RodauthMailer.reset_password(email_to, reset_password_email_link)
359
434
  end
360
- send_verify_account_email do
361
- mailer_send(:verify_account, email_to, verify_account_email_link)
435
+ create_verify_account_email do
436
+ RodauthMailer.verify_account(email_to, verify_account_email_link)
362
437
  end
363
- send_verify_login_change_email do |login|
364
- mailer_send(:verify_login_change, login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link)
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)
365
440
  end
366
- send_password_changed_email do
367
- mailer_send(:password_changed, email_to)
441
+ create_password_changed_email do
442
+ RodauthMailer.password_changed(email_to)
368
443
  end
369
- # send_email_auth_email do
370
- # mailer_send(:email_auth, email_to, email_auth_email_link)
444
+ # create_email_auth_email do
445
+ # RodauthMailer.email_auth(email_to, email_auth_email_link)
371
446
  # end
372
- # send_unlock_account_email do
373
- # mailer_send(:unlock_account, email_to, unlock_account_email_link)
447
+ # create_unlock_account_email do
448
+ # RodauthMailer.unlock_account(email_to, unlock_account_email_link)
374
449
  # end
375
- auth_class_eval do
450
+ send_email do |email|
376
451
  # queue email delivery on the mailer after the transaction commits
377
- def mailer_send(type, *args)
378
- db.after_commit do
379
- RodauthMailer.public_send(type, *args).deliver_later
380
- end
381
- end
452
+ db.after_commit { email.deliver_later }
382
453
  end
383
454
  # ...
384
455
  end
385
456
  end
386
457
  ```
387
458
 
388
- ### JSON API
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
+
464
+ ### Migrations
389
465
 
390
- JSON API support in Rodauth is provided by the [JWT feature]. First you'll need
391
- to add the [JWT gem] to your Gemfile:
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:
392
469
 
470
+ ```sh
471
+ $ rails generate rodauth:migration otp sms_codes recovery_codes
472
+ ```
393
473
  ```rb
394
- gem "jwt"
474
+ # db/migration/*_create_rodauth_otp_sms_codes_recovery_codes.rb
475
+ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
476
+ def change
477
+ create_table :account_otp_keys do |t| ... end
478
+ create_table :account_sms_codes do |t| ... end
479
+ create_table :account_recovery_codes do |t| ... end
480
+ end
481
+ end
395
482
  ```
396
483
 
397
- The following configuration will enable the Rodauth endpoints to be accessed
398
- via JSON requests (in addition to HTML requests):
484
+ ### Calling controller methods
399
485
 
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:
489
+
490
+ ```rb
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
498
+ ```
400
499
  ```rb
401
500
  # app/lib/rodauth_app.rb
402
501
  class RodauthApp < Rodauth::Rails::App
403
- configure(json: true) do
404
- # ...
405
- enable :jwt
406
- jwt_secret "...your secret key..."
407
- # ...
502
+ configure do
503
+ after_create_account do
504
+ rails_controller_eval { setup_tracking(account_id) }
505
+ end
408
506
  end
409
507
  end
410
508
  ```
411
509
 
412
- If you want the endpoints to be only accessible via JSON requests, or if your
413
- Rails app is in API-only mode, instead of `json: true` pass `json: :only` to
414
- the configure method.
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:
515
+
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
+ ```
415
525
 
416
- Make sure to store the `jwt_secret` in a secure place, such as Rails
417
- credentials or environment variables.
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.
418
528
 
419
529
  ## How it works
420
530
 
@@ -460,19 +570,45 @@ end
460
570
  The `Rodauth::Rails::App` class is a [Roda] subclass that provides Rails
461
571
  integration for Rodauth:
462
572
 
463
- * uses Rails' flash instead of Roda's
464
- * uses Rails' CSRF protection instead of Roda's
573
+ * uses Action Dispatch flash instead of Roda's
574
+ * uses Action Dispatch CSRF protection instead of Roda's
465
575
  * sets [HMAC] secret to Rails' secret key base
466
- * uses ActionController for rendering templates
467
- * uses ActionMailer for sending emails
576
+ * uses Action Controller for rendering templates
577
+ * runs Action Controller callbacks & rescue handlers around Rodauth actions
578
+ * uses Action Mailer for sending emails
468
579
 
469
- The `configure { ... }` method wraps configuring the Rodauth plugin, forwarding
580
+ The `configure` method wraps configuring the Rodauth plugin, forwarding
470
581
  any additional [plugin options].
471
582
 
472
583
  ```rb
473
- configure { ... } # defining default Rodauth configuration
474
- configure(json: true) { ... } # passing options to the Rodauth plugin
475
- configure(:secondary) { ... } # defining multiple Rodauth configurations
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
476
612
  ```
477
613
 
478
614
  ### Sequel
@@ -488,6 +624,142 @@ connection (using the [sequel-activerecord_connection] gem).
488
624
  This means that, from the usage perspective, Sequel can be considered just
489
625
  as an implementation detail of Rodauth.
490
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
+
491
763
  ## Configuring
492
764
 
493
765
  For the list of configuration methods provided by Rodauth, see the [feature
@@ -521,6 +793,37 @@ Rodauth::Rails.configure do |config|
521
793
  end
522
794
  ```
523
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
+
524
827
  ## Testing
525
828
 
526
829
  If you're writing system tests, it's generally better to go through the actual
@@ -588,6 +891,24 @@ disables the use of database functions, though you can always turn it back on.
588
891
  use_database_authentication_functions? true
589
892
  ```
590
893
 
894
+ To create the database functions, pass the Sequel database object into the
895
+ Rodauth method for creating database functions:
896
+
897
+ ```rb
898
+ # db/migrate/*_create_rodauth_database_functions.rb
899
+ require "rodauth/migrations"
900
+
901
+ class CreateRodauthDatabaseFunctions < ActiveRecord::Migration
902
+ def up
903
+ Rodauth.create_database_authentication_functions(DB)
904
+ end
905
+
906
+ def down
907
+ Rodauth.drop_database_authentication_functions(DB)
908
+ end
909
+ end
910
+ ```
911
+
591
912
  ### Account statuses
592
913
 
593
914
  The recommended [Rodauth migration] stores possible account status values in a
@@ -640,9 +961,7 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
640
961
 
641
962
  [Rodauth]: https://github.com/jeremyevans/rodauth
642
963
  [Sequel]: https://github.com/jeremyevans/sequel
643
- [rendering views outside of controllers]: https://blog.bigbinary.com/2016/01/08/rendering-views-outside-of-controllers-in-rails-5.html
644
964
  [feature documentation]: http://rodauth.jeremyevans.net/documentation.html
645
- [JWT feature]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
646
965
  [JWT gem]: https://github.com/jwt/ruby-jwt
647
966
  [Bootstrap]: https://getbootstrap.com/
648
967
  [Roda]: http://roda.jeremyevans.net/
@@ -651,3 +970,20 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
651
970
  [Rodauth migration]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-Creating+tables
652
971
  [sequel-activerecord_connection]: https://github.com/janko/sequel-activerecord_connection
653
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