rodauth-rails 1.9.0 → 1.11.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 +18 -0
- data/README.md +170 -240
- data/lib/rodauth/rails/app.rb +4 -3
- data/lib/rodauth/rails/tasks/routes.rb +70 -0
- data/lib/rodauth/rails/tasks.rake +6 -36
- data/lib/rodauth/rails/version.rb +1 -1
- data/lib/rodauth/rails.rb +20 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f902ae05472454543304221221b9681c7dab3d231d6551c7e82b6c4c1570dc8
|
4
|
+
data.tar.gz: b3a1948fb603be978bddea73a44b8109f4e3c76e132b2da3a8e6aad1b36f3d14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8547335032c3e0851406932463cd62e2377c16db2145e0da6abb8a7f20745c20f9e8d54bd2b57599e335a4f9b37078cf7f1c17caa53b4dab60105c410a401469
|
7
|
+
data.tar.gz: dd84bd6d57a4e8e78a6412a7ac38befa7d75ef693a6788014d383d3800a4535af71fa24239f8b0521cecc7c598766f48b2034a016a2a15235cfa9f9d2dd52761
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
## 1.11.0 (2023-08-21)
|
2
|
+
|
3
|
+
* Exclude WebAuthn JS routes in `rodauth:routes`, since those stop being relevant with custom JS (@janko)
|
4
|
+
|
5
|
+
* Separate HTTP verbs with `|` symbol in `rodauth:routes` for consistency with `rails routes` (@janko)
|
6
|
+
|
7
|
+
* Include two factor manage & auth JSON POST routes in `rodauth:routes` task (@janko)
|
8
|
+
|
9
|
+
* Make `rodauth:routes` rake task appear in `rails -T` list (@janko)
|
10
|
+
|
11
|
+
* Accept plugin options in `Rodauth::Rails.lib` (@janko)
|
12
|
+
|
13
|
+
* Support skipping loading Roda `render` plugin by passing `render: false` (@janko)
|
14
|
+
|
15
|
+
## 1.10.0 (2023-07-26)
|
16
|
+
|
17
|
+
* Add `Rodauth::Rails.lib` for easier usage of Rodauth as a library in Rails apps (@janko)
|
18
|
+
|
1
19
|
## 1.9.0 (2023-05-22)
|
2
20
|
|
3
21
|
* Add support for webauthn_autofill feature to the views generator (@janko)
|
data/README.md
CHANGED
@@ -24,6 +24,7 @@ Provides Rails integration for the [Rodauth] authentication framework.
|
|
24
24
|
* [How to build an OIDC provider using rodauth-oauth on Rails](https://honeyryderchuck.gitlab.io/httpx/2021/03/15/oidc-provider-on-rails-using-rodauth-oauth.html)
|
25
25
|
* [What It Took to Build a Rails Integration for Rodauth](https://janko.io/what-it-took-to-build-a-rails-integration-for-rodauth/)
|
26
26
|
* [Social Login in Rails with Rodauth](https://janko.io/social-login-in-rails-with-rodauth/)
|
27
|
+
* [Passkey Authentication with Rodauth](https://janko.io/passkey-authentication-with-rodauth/)
|
27
28
|
|
28
29
|
## Why Rodauth?
|
29
30
|
|
@@ -133,18 +134,18 @@ $ rails rodauth:routes
|
|
133
134
|
```
|
134
135
|
Routes handled by RodauthApp:
|
135
136
|
|
136
|
-
GET
|
137
|
-
GET
|
138
|
-
GET
|
139
|
-
GET
|
140
|
-
GET
|
141
|
-
GET
|
142
|
-
GET
|
143
|
-
GET
|
144
|
-
GET
|
145
|
-
GET
|
146
|
-
GET
|
147
|
-
GET
|
137
|
+
GET|POST /login rodauth.login_path
|
138
|
+
GET|POST /create-account rodauth.create_account_path
|
139
|
+
GET|POST /verify-account-resend rodauth.verify_account_resend_path
|
140
|
+
GET|POST /verify-account rodauth.verify_account_path
|
141
|
+
GET|POST /change-password rodauth.change_password_path
|
142
|
+
GET|POST /change-login rodauth.change_login_path
|
143
|
+
GET|POST /logout rodauth.logout_path
|
144
|
+
GET|POST /remember rodauth.remember_path
|
145
|
+
GET|POST /reset-password-request rodauth.reset_password_request_path
|
146
|
+
GET|POST /reset-password rodauth.reset_password_path
|
147
|
+
GET|POST /verify-login-change rodauth.verify_login_change_path
|
148
|
+
GET|POST /close-account rodauth.close_account_path
|
148
149
|
```
|
149
150
|
|
150
151
|
Using this information, you can add some basic authentication links to your
|
@@ -185,25 +186,6 @@ current_account #=> #<Account id=123 email="user@example.com">
|
|
185
186
|
current_account.email #=> "user@example.com"
|
186
187
|
```
|
187
188
|
|
188
|
-
If the session is logged in, but the account doesn't exist in the database, the
|
189
|
-
session will be reset.
|
190
|
-
|
191
|
-
#### Custom account model
|
192
|
-
|
193
|
-
The `#rails_account` method will try to infer the account model class from the
|
194
|
-
configured accounts table name. However, if the model class cannot be inferred
|
195
|
-
from the table name, you can configure it manually:
|
196
|
-
|
197
|
-
```rb
|
198
|
-
# app/misc/rodauth_main.rb
|
199
|
-
class RodauthMain < Rodauth::Rails::Auth
|
200
|
-
configure do
|
201
|
-
# ...
|
202
|
-
rails_account_model { Authentication::Account } # custom model name
|
203
|
-
end
|
204
|
-
end
|
205
|
-
```
|
206
|
-
|
207
189
|
### Requiring authentication
|
208
190
|
|
209
191
|
You'll likely want to require authentication for certain parts of your app,
|
@@ -219,8 +201,8 @@ class RodauthApp < Rodauth::Rails::App
|
|
219
201
|
# ...
|
220
202
|
r.rodauth # route rodauth requests
|
221
203
|
|
222
|
-
# require authentication for /dashboard/*
|
223
|
-
if r.path.start_with?("/dashboard")
|
204
|
+
# require authentication for /dashboard/* routes
|
205
|
+
if r.path.start_with?("/dashboard")
|
224
206
|
rodauth.require_account # redirect to login page if not authenticated
|
225
207
|
end
|
226
208
|
end
|
@@ -245,12 +227,6 @@ class DashboardController < ApplicationController
|
|
245
227
|
before_action :authenticate
|
246
228
|
end
|
247
229
|
```
|
248
|
-
```rb
|
249
|
-
# app/controllers/posts_controller.rb
|
250
|
-
class PostsController < ApplicationController
|
251
|
-
before_action :authenticate, except: [:index, :show]
|
252
|
-
end
|
253
|
-
```
|
254
230
|
|
255
231
|
#### Routing constraints
|
256
232
|
|
@@ -260,7 +236,7 @@ level. You can do this via the built-in `authenticated` routing constraint:
|
|
260
236
|
```rb
|
261
237
|
# config/routes.rb
|
262
238
|
Rails.application.routes.draw do
|
263
|
-
constraints Rodauth::Rails.
|
239
|
+
constraints Rodauth::Rails.authenticate do
|
264
240
|
# ... authenticated routes ...
|
265
241
|
end
|
266
242
|
end
|
@@ -273,48 +249,89 @@ called with the Rodauth instance:
|
|
273
249
|
# config/routes.rb
|
274
250
|
Rails.application.routes.draw do
|
275
251
|
# require multifactor authentication to be setup
|
276
|
-
constraints Rodauth::Rails.
|
252
|
+
constraints Rodauth::Rails.authenticate { |rodauth| rodauth.uses_two_factor_authentication? } do
|
277
253
|
# ...
|
278
254
|
end
|
279
255
|
end
|
280
256
|
```
|
281
257
|
|
282
|
-
|
258
|
+
You can specify a different Rodauth configuration by passing the configuration name:
|
283
259
|
|
284
260
|
```rb
|
285
261
|
# config/routes.rb
|
286
262
|
Rails.application.routes.draw do
|
287
|
-
|
288
|
-
constraints Rodauth::Rails.authenticated { |rodauth| rodauth.rails_account.admin? } do
|
263
|
+
constraints Rodauth::Rails.authenticate(:admin) do
|
289
264
|
# ...
|
290
265
|
end
|
291
266
|
end
|
292
267
|
```
|
293
268
|
|
294
|
-
|
269
|
+
If you need something more custom, you can always create the routing constraint
|
270
|
+
manually:
|
295
271
|
|
296
272
|
```rb
|
297
273
|
# config/routes.rb
|
298
274
|
Rails.application.routes.draw do
|
299
|
-
constraints
|
300
|
-
#
|
275
|
+
constraints -> (r) { !r.env["rodauth"].logged_in? } do # or env["rodauth.admin"]
|
276
|
+
# routes when the user is not logged in
|
301
277
|
end
|
302
278
|
end
|
303
279
|
```
|
304
280
|
|
305
|
-
|
306
|
-
|
281
|
+
### Controller
|
282
|
+
|
283
|
+
Your Rodauth configuration is connected to a Rails controller (`RodauthController` by default), and
|
284
|
+
it automatically executes any callbacks and rescue handlers defined on it (or the parent controller)
|
285
|
+
around Rodauth endpoints.
|
307
286
|
|
308
287
|
```rb
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
288
|
+
class RodauthController < ApplicationController
|
289
|
+
before_action :set_locale # executes before Rodauth endpoints
|
290
|
+
rescue_from("MyApp::SomeError") { |exception| ... } # rescues around Rodauth endpoints
|
291
|
+
end
|
292
|
+
```
|
293
|
+
|
294
|
+
#### Calling controller methods
|
295
|
+
|
296
|
+
You can call any controller methods from your Rodauth configuration via `rails_controller_eval`:
|
297
|
+
|
298
|
+
```rb
|
299
|
+
# app/controllers/application_controller.rb
|
300
|
+
class ApplicationController < ActionController::Base
|
301
|
+
private
|
302
|
+
def setup_tracking(account_id)
|
303
|
+
# ... some implementation ...
|
304
|
+
end
|
305
|
+
end
|
306
|
+
```
|
307
|
+
```rb
|
308
|
+
# app/misc/rodauth_main.rb
|
309
|
+
class RodauthMain < Rodauth::Rails::Auth
|
310
|
+
configure do
|
311
|
+
after_create_account do
|
312
|
+
rails_controller_eval { setup_tracking(account_id) }
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
```
|
317
|
+
|
318
|
+
### Rails URL helpers
|
319
|
+
|
320
|
+
Inside Rodauth configuration and the `route` block you can access Rails route
|
321
|
+
helpers through `#rails_routes`:
|
322
|
+
|
323
|
+
```rb
|
324
|
+
# app/misc/rodauth_main.rb
|
325
|
+
class RodauthMain < Rodauth::Rails::Auth
|
326
|
+
configure do
|
327
|
+
login_redirect { rails_routes.activity_path }
|
328
|
+
change_password_redirect { rails_routes.profile_path }
|
329
|
+
change_login_redirect { rails_routes.profile_path }
|
313
330
|
end
|
314
331
|
end
|
315
332
|
```
|
316
333
|
|
317
|
-
|
334
|
+
## Views
|
318
335
|
|
319
336
|
The templates built into Rodauth are useful when getting started, but soon
|
320
337
|
you'll want to start editing the markup. You can run the following command to
|
@@ -349,7 +366,7 @@ Use `--name` to generate views for a different Rodauth configuration:
|
|
349
366
|
$ rails generate rodauth:views webauthn two_factor_base --name admin
|
350
367
|
```
|
351
368
|
|
352
|
-
|
369
|
+
### Page titles
|
353
370
|
|
354
371
|
The generated configuration sets `title_instance_variable` to make page titles
|
355
372
|
available in your views via `@page_title` instance variable, which you can then
|
@@ -359,9 +376,7 @@ use in your layout:
|
|
359
376
|
# app/misc/rodauth_main.rb
|
360
377
|
class RodauthMain < Rodauth::Rails::Auth
|
361
378
|
configure do
|
362
|
-
# ...
|
363
379
|
title_instance_variable :@page_title
|
364
|
-
# ...
|
365
380
|
end
|
366
381
|
end
|
367
382
|
```
|
@@ -373,28 +388,11 @@ end
|
|
373
388
|
<title><%= @page_title || "Default title" %></title>
|
374
389
|
<!-- ... -->
|
375
390
|
</head>
|
376
|
-
|
377
|
-
<!-- ... -->
|
378
|
-
</body>
|
391
|
+
<!-- ... -->
|
379
392
|
</html>
|
380
393
|
```
|
381
394
|
|
382
|
-
|
383
|
-
generated Rodauth views, giving it the result of the corresponding
|
384
|
-
`*_page_title` method:
|
385
|
-
|
386
|
-
```erb
|
387
|
-
<!-- app/views/rodauth/login.html.erb -->
|
388
|
-
<%= content_for :page_title, rodauth.login_page_title %>
|
389
|
-
<!-- ... -->
|
390
|
-
```
|
391
|
-
```erb
|
392
|
-
<!-- app/views/rodauth/change_password.html.erb -->
|
393
|
-
<%= content_for :page_title, rodauth.change_password_page_title %>
|
394
|
-
<!-- ... -->
|
395
|
-
```
|
396
|
-
|
397
|
-
#### Layout
|
395
|
+
### Layout
|
398
396
|
|
399
397
|
To use different layouts for different Rodauth views, you can compare the
|
400
398
|
request path in the layout method:
|
@@ -422,7 +420,7 @@ class RodauthController < ApplicationController
|
|
422
420
|
end
|
423
421
|
```
|
424
422
|
|
425
|
-
|
423
|
+
### Turbo
|
426
424
|
|
427
425
|
[Turbo] has been disabled by default on all built-in and generated view
|
428
426
|
templates, because some Rodauth actions (multi-phase login, adding recovery
|
@@ -431,7 +429,7 @@ codes) aren't Turbo-compatible, as they return 200 responses on POST requests.
|
|
431
429
|
That being said, most of Rodauth *is* Turbo-compatible, so feel free to enable
|
432
430
|
Turbo for actions where you want to use it.
|
433
431
|
|
434
|
-
|
432
|
+
## Mailer
|
435
433
|
|
436
434
|
The install generator will create `RodauthMailer` with default email templates,
|
437
435
|
and configure Rodauth features that send emails as part of the authentication
|
@@ -440,73 +438,45 @@ flow to use it.
|
|
440
438
|
```rb
|
441
439
|
# app/mailers/rodauth_mailer.rb
|
442
440
|
class RodauthMailer < ApplicationMailer
|
443
|
-
def verify_account(account_id, key)
|
444
|
-
|
445
|
-
end
|
446
|
-
def
|
447
|
-
|
448
|
-
end
|
449
|
-
def verify_login_change(account_id, old_login, new_login, key)
|
450
|
-
# ...
|
451
|
-
end
|
452
|
-
def password_changed(account_id)
|
453
|
-
# ...
|
454
|
-
end
|
455
|
-
# def email_auth(account_id, key)
|
456
|
-
# ...
|
457
|
-
# end
|
458
|
-
# def unlock_account(account_id, key)
|
459
|
-
# ...
|
460
|
-
# end
|
441
|
+
def verify_account(account_id, key) ... end
|
442
|
+
def reset_password(account_id, key) ... end
|
443
|
+
def verify_login_change(account_id, key) ... end
|
444
|
+
def password_changed(account_id) ... end
|
445
|
+
# def email_auth(account_id, key) ... end
|
446
|
+
# def unlock_account(account_id, key) ... end
|
461
447
|
end
|
462
448
|
```
|
463
449
|
```rb
|
464
450
|
# app/misc/rodauth_main.rb
|
465
451
|
class RodauthMain < Rodauth::Rails::Auth
|
466
452
|
configure do
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
end
|
474
|
-
create_verify_login_change_email do |_login|
|
475
|
-
RodauthMailer.verify_login_change(account_id, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_key_value)
|
476
|
-
end
|
477
|
-
create_password_changed_email do
|
478
|
-
RodauthMailer.password_changed(account_id)
|
479
|
-
end
|
480
|
-
# create_email_auth_email do
|
481
|
-
# RodauthMailer.email_auth(account_id, email_auth_key_value)
|
482
|
-
# end
|
483
|
-
# create_unlock_account_email do
|
484
|
-
# RodauthMailer.unlock_account(account_id, unlock_account_key_value)
|
485
|
-
# end
|
453
|
+
create_reset_password_email { RodauthMailer.reset_password(account_id, reset_password_key_value) }
|
454
|
+
create_verify_account_email { RodauthMailer.verify_account(account_id, verify_account_key_value) }
|
455
|
+
create_verify_login_change_email { |_login| RodauthMailer.verify_login_change(account_id, verify_login_change_key_value) }
|
456
|
+
create_password_changed_email { RodauthMailer.password_changed(account_id) }
|
457
|
+
# create_email_auth_email { RodauthMailer.email_auth(account_id, email_auth_key_value) }
|
458
|
+
# create_unlock_account_email { RodauthMailer.unlock_account(account_id, unlock_account_key_value) }
|
486
459
|
send_email do |email|
|
487
460
|
# queue email delivery on the mailer after the transaction commits
|
488
461
|
db.after_commit { email.deliver_later }
|
489
462
|
end
|
490
|
-
# ...
|
491
463
|
end
|
492
464
|
end
|
493
465
|
```
|
494
466
|
|
495
467
|
This configuration calls `#deliver_later`, which uses Active Job to deliver
|
496
|
-
emails in a background job.
|
497
|
-
|
498
|
-
deliveries. However, if you want to send emails synchronously, you can modify
|
499
|
-
the configuration to call `#deliver_now` instead.
|
468
|
+
emails in a background job. If you want to send emails synchronously, you can
|
469
|
+
modify the configuration to call `#deliver_now` instead.
|
500
470
|
|
501
471
|
If you're using a background processing library without an Active Job adapter,
|
502
472
|
or a 3rd-party service for sending transactional emails, see [this wiki
|
503
473
|
page][custom mailer worker] on how to set it up.
|
504
474
|
|
505
|
-
|
475
|
+
## Migrations
|
506
476
|
|
507
477
|
The install generator will create a migration for tables used by the Rodauth
|
508
478
|
features enabled by default. For any additional features, you can use the
|
509
|
-
migration generator
|
479
|
+
migration generator to create the required tables:
|
510
480
|
|
511
481
|
```sh
|
512
482
|
$ rails generate rodauth:migration otp sms_codes recovery_codes
|
@@ -522,10 +492,10 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
|
|
522
492
|
end
|
523
493
|
```
|
524
494
|
|
525
|
-
|
495
|
+
### Table prefix
|
526
496
|
|
527
497
|
If you're storing account records in a table other than `accounts`, you'll want
|
528
|
-
to specify the
|
498
|
+
to specify the appropriate table prefix when generating new migrations:
|
529
499
|
|
530
500
|
```sh
|
531
501
|
$ rails generate rodauth:migration base active_sessions --prefix user
|
@@ -546,7 +516,7 @@ class CreateRodauthUserBaseActiveSessions < ActiveRecord::Migration
|
|
546
516
|
end
|
547
517
|
```
|
548
518
|
|
549
|
-
|
519
|
+
### Custom migration name
|
550
520
|
|
551
521
|
You can change the default migration name:
|
552
522
|
|
@@ -570,37 +540,23 @@ tables used by enabled authentication features.
|
|
570
540
|
|
571
541
|
```rb
|
572
542
|
class Account < ActiveRecord::Base # Sequel::Model
|
573
|
-
include Rodauth::Rails.model # or
|
543
|
+
include Rodauth::Rails.model # or Rodauth::Rails.model(:admin)
|
574
544
|
end
|
575
545
|
```
|
576
|
-
|
577
|
-
The password attribute can be used to set or clear the password hash. It
|
578
|
-
handles both storing the password hash in a column on the accounts table, or in
|
579
|
-
a separate table.
|
580
|
-
|
581
546
|
```rb
|
547
|
+
# setting password hash
|
582
548
|
account = Account.create!(email: "user@example.com", password: "secret123")
|
583
|
-
|
584
|
-
# when password hash is stored in a column on the accounts table
|
585
549
|
account.password_hash #=> "$2a$12$k/Ub1I2iomi84RacqY89Hu4.M0vK7klRnRtzorDyvOkVI.hKhkNw."
|
586
550
|
|
587
|
-
#
|
588
|
-
account.
|
589
|
-
account.password_hash.password_hash #=> "$2a$12$k/Ub1..." (inaccessible when using database authentication functions)
|
590
|
-
|
591
|
-
account.password = nil # clears password hash
|
551
|
+
# clearing password hash
|
552
|
+
account.password = nil
|
592
553
|
account.password_hash #=> nil
|
593
|
-
```
|
594
|
-
|
595
|
-
The associations are defined for tables used by enabled authentication features:
|
596
554
|
|
597
|
-
|
555
|
+
# associations
|
598
556
|
account.remember_key #=> #<Account::RememberKey> (record from `account_remember_keys` table)
|
599
557
|
account.active_session_keys #=> [#<Account::ActiveSessionKey>,...] (records from `account_active_session_keys` table)
|
600
558
|
```
|
601
559
|
|
602
|
-
See the [rodauth-model] documentation for more details.
|
603
|
-
|
604
560
|
## Multiple configurations
|
605
561
|
|
606
562
|
If you need to handle multiple types of accounts that require different
|
@@ -646,43 +602,9 @@ Then in your application you can reference the secondary Rodauth instance:
|
|
646
602
|
rodauth(:admin).login_path #=> "/admin/login"
|
647
603
|
```
|
648
604
|
|
649
|
-
|
650
|
-
configuration to the database
|
651
|
-
that
|
652
|
-
|
653
|
-
### Sharing configuration
|
654
|
-
|
655
|
-
If there are common settings that you want to share between Rodauth
|
656
|
-
configurations, you can do so via inheritance:
|
657
|
-
|
658
|
-
```rb
|
659
|
-
# app/misc/rodauth_base.rb
|
660
|
-
class RodauthBase < Rodauth::Rails::Auth
|
661
|
-
# common settings that are shared between multiple configurations
|
662
|
-
configure do
|
663
|
-
enable :login, :logout
|
664
|
-
login_return_to_requested_location? true
|
665
|
-
logout_redirect "/"
|
666
|
-
# ...
|
667
|
-
end
|
668
|
-
end
|
669
|
-
```
|
670
|
-
```rb
|
671
|
-
# app/misc/rodauth_main.rb
|
672
|
-
class RodauthMain < RodauthBase # inherit common settings
|
673
|
-
configure do
|
674
|
-
# ... customize main ...
|
675
|
-
end
|
676
|
-
end
|
677
|
-
```
|
678
|
-
```rb
|
679
|
-
# app/misc/rodauth_admin.rb
|
680
|
-
class RodauthAdmin < RodauthBase # inherit common settings
|
681
|
-
configure do
|
682
|
-
# ... customize admin ...
|
683
|
-
end
|
684
|
-
end
|
685
|
-
```
|
605
|
+
You'll likely want to save the information of which account belongs to which
|
606
|
+
configuration to the database, see [this guide][account types] on how you can do
|
607
|
+
that. Note that you can also [share configuration via inheritance][inheritance].
|
686
608
|
|
687
609
|
## Outside of a request
|
688
610
|
|
@@ -769,6 +691,44 @@ Rodauth::Rails.rodauth(session: { two_factor_auth_setup: true })
|
|
769
691
|
Rodauth::Rails.rodauth(:admin, params: { "param" => "value" })
|
770
692
|
```
|
771
693
|
|
694
|
+
### Using as a library
|
695
|
+
|
696
|
+
Rodauth offers a `Rodauth.lib` method for configuring Rodauth so that it can be used as a library, instead of routing requests (see [internal_request] feature). This gem provides a `Rodauth::Rails.lib` counterpart that does the same but with Rails integration:
|
697
|
+
|
698
|
+
```rb
|
699
|
+
# app/misc/rodauth_main.rb
|
700
|
+
require "rodauth/rails"
|
701
|
+
require "sequel/core"
|
702
|
+
|
703
|
+
RodauthMain = Rodauth::Rails.lib do
|
704
|
+
enable :create_account, :login, :close_account
|
705
|
+
db Sequel.postgres(extensions: :activerecord_connection, keep_reference: false)
|
706
|
+
# ...
|
707
|
+
end
|
708
|
+
```
|
709
|
+
```rb
|
710
|
+
RodauthMain.create_account(login: "email@example.com", password: "secret123")
|
711
|
+
RodauthMain.login(login: "email@example.com", password: "secret123")
|
712
|
+
RodauthMain.close_account(account_login: "email@example.com")
|
713
|
+
```
|
714
|
+
|
715
|
+
Note that you'll want to skip requiring `rodauth-rails` on Rails boot, to avoid it automatically inserting the Rodauth middleware, and remove some unnecessary files generated by the install generator.
|
716
|
+
|
717
|
+
```rb
|
718
|
+
# Gemfile
|
719
|
+
gem "rodauth-rails", require: false
|
720
|
+
```
|
721
|
+
```sh
|
722
|
+
$ rm config/initializers/rodauth.rb app/misc/rodauth_app.rb app/controllers/rodauth_controller.rb
|
723
|
+
```
|
724
|
+
|
725
|
+
The `Rodauth::Rails.lib` call will forward any Rodauth [plugin options] passed to it:
|
726
|
+
|
727
|
+
```rb
|
728
|
+
# skips loading Roda render plugin and Tilt gem (used for rendering built-in templates)
|
729
|
+
Rodauth::Rails.lib(render: false) { ... }
|
730
|
+
```
|
731
|
+
|
772
732
|
## Testing
|
773
733
|
|
774
734
|
For system and integration tests, which run the whole middleware stack,
|
@@ -788,13 +748,13 @@ end
|
|
788
748
|
```
|
789
749
|
|
790
750
|
One can write `ActionDispatch::IntegrationTest` test helpers for `login` and
|
791
|
-
`logout` by making requests to the
|
751
|
+
`logout` by making requests to the Rodauth endpoints:
|
792
752
|
|
793
753
|
```rb
|
794
754
|
# test/controllers/articles_controller_test.rb
|
795
755
|
class ArticlesControllerTest < ActionDispatch::IntegrationTest
|
796
|
-
def login(
|
797
|
-
post "/login", params: {
|
756
|
+
def login(email, password)
|
757
|
+
post "/login", params: { email: email, password: password }
|
798
758
|
assert_redirected_to "/"
|
799
759
|
end
|
800
760
|
|
@@ -846,6 +806,15 @@ methods:
|
|
846
806
|
| `rails_controller` | Controller class to use for rendering and CSRF protection. |
|
847
807
|
| `rails_account_model` | Model class connected with the accounts table. |
|
848
808
|
|
809
|
+
```rb
|
810
|
+
class RodauthMain < Rodauth::Rails::Auth
|
811
|
+
configure do
|
812
|
+
rails_controller { Authentication::RodauthController }
|
813
|
+
rails_account_model { Authentication::Account }
|
814
|
+
end
|
815
|
+
end
|
816
|
+
```
|
817
|
+
|
849
818
|
For the list of configuration methods provided by Rodauth, see the [feature
|
850
819
|
documentation].
|
851
820
|
|
@@ -875,53 +844,10 @@ end
|
|
875
844
|
rodauth.admin? #=> true
|
876
845
|
```
|
877
846
|
|
878
|
-
### Rails URL helpers
|
879
|
-
|
880
|
-
Inside Rodauth configuration and the `route` block you can access Rails route
|
881
|
-
helpers through `#rails_routes`:
|
882
|
-
|
883
|
-
```rb
|
884
|
-
# app/misc/rodauth_main.rb
|
885
|
-
class RodauthMain < Rodauth::Rails::Auth
|
886
|
-
configure do
|
887
|
-
login_redirect { rails_routes.activity_path }
|
888
|
-
change_password_redirect { rails_routes.profile_path }
|
889
|
-
change_login_redirect { rails_routes.profile_path }
|
890
|
-
end
|
891
|
-
end
|
892
|
-
```
|
893
|
-
|
894
|
-
### Calling controller methods
|
895
|
-
|
896
|
-
When using Rodauth before/after hooks or generally overriding your Rodauth
|
897
|
-
configuration, in some cases you might want to call methods defined on your
|
898
|
-
controllers. You can do so with `rails_controller_eval`, for example:
|
899
|
-
|
900
|
-
```rb
|
901
|
-
# app/controllers/application_controller.rb
|
902
|
-
class ApplicationController < ActionController::Base
|
903
|
-
private
|
904
|
-
def setup_tracking(account_id)
|
905
|
-
# ... some implementation ...
|
906
|
-
end
|
907
|
-
end
|
908
|
-
```
|
909
|
-
```rb
|
910
|
-
# app/misc/rodauth_main.rb
|
911
|
-
class RodauthMain < Rodauth::Rails::Auth
|
912
|
-
configure do
|
913
|
-
after_create_account do
|
914
|
-
rails_controller_eval { setup_tracking(account_id) }
|
915
|
-
end
|
916
|
-
end
|
917
|
-
end
|
918
|
-
```
|
919
|
-
|
920
847
|
### Single-file configuration
|
921
848
|
|
922
|
-
If you would prefer
|
923
|
-
|
924
|
-
anonymous auth class.
|
849
|
+
If you would prefer, you can have all your Rodauth logic contained inside the
|
850
|
+
Rodauth app class:
|
925
851
|
|
926
852
|
```rb
|
927
853
|
# app/misc/rodauth_app.rb
|
@@ -944,6 +870,19 @@ class RodauthApp < Rodauth::Rails::App
|
|
944
870
|
end
|
945
871
|
```
|
946
872
|
|
873
|
+
### Manually inserting middleware
|
874
|
+
|
875
|
+
You can choose to insert the Rodauth middleware somewhere earlier than
|
876
|
+
in front of the Rails router:
|
877
|
+
|
878
|
+
```rb
|
879
|
+
Rodauth::Rails.configure do |config|
|
880
|
+
config.middleware = false # disable auto-insertion
|
881
|
+
end
|
882
|
+
|
883
|
+
Rails.application.config.middleware.insert_before AnotherMiddleware, Rodauth::Rails::Middleware
|
884
|
+
```
|
885
|
+
|
947
886
|
## How it works
|
948
887
|
|
949
888
|
### Rack middleware
|
@@ -958,16 +897,6 @@ $ rails middleware
|
|
958
897
|
# run MyApp::Application.routes
|
959
898
|
```
|
960
899
|
|
961
|
-
It can be inserted at any point in the middleware stack:
|
962
|
-
|
963
|
-
```rb
|
964
|
-
Rodauth::Rails.configure do |config|
|
965
|
-
config.middleware = false # disable auto-insertion
|
966
|
-
end
|
967
|
-
|
968
|
-
Rails.application.config.middleware.insert_before AnotherMiddleware, Rodauth::Rails::Middleware
|
969
|
-
```
|
970
|
-
|
971
900
|
The middleware retrieves the Rodauth app via `Rodauth::Rails.app`, which is
|
972
901
|
specified as a string to keep the class autoloadable and reloadable in
|
973
902
|
development.
|
@@ -1010,7 +939,7 @@ class RodauthApp < Rodauth::Rails::App
|
|
1010
939
|
configure(:admin) { ... }
|
1011
940
|
|
1012
941
|
# plugin options
|
1013
|
-
configure(RodauthMain, json: :only)
|
942
|
+
configure(RodauthMain, json: :only, render: false)
|
1014
943
|
end
|
1015
944
|
```
|
1016
945
|
|
@@ -1239,3 +1168,4 @@ conduct](CODE_OF_CONDUCT.md).
|
|
1239
1168
|
[Turbo]: https://turbo.hotwired.dev/
|
1240
1169
|
[rodauth-model]: https://github.com/janko/rodauth-model
|
1241
1170
|
[JSON API]: https://github.com/janko/rodauth-rails/wiki/JSON-API
|
1171
|
+
[inheritance]: http://rodauth.jeremyevans.net/rdoc/files/doc/guides/share_configuration_rdoc.html
|
data/lib/rodauth/rails/app.rb
CHANGED
@@ -18,16 +18,17 @@ module Rodauth
|
|
18
18
|
end
|
19
19
|
|
20
20
|
plugin :hooks
|
21
|
-
plugin :render, layout: false
|
22
21
|
plugin :pass
|
23
22
|
|
24
23
|
def self.configure(*args, **options, &block)
|
25
24
|
auth_class = args.shift if args[0].is_a?(Class)
|
26
|
-
|
25
|
+
auth_class ||= Class.new(Rodauth::Rails::Auth)
|
26
|
+
name = args.shift if args[0].is_a?(Symbol)
|
27
27
|
|
28
28
|
fail ArgumentError, "need to pass optional Rodauth::Auth subclass and optional configuration name" if args.any?
|
29
29
|
|
30
|
-
|
30
|
+
# we'll render Rodauth's built-in view templates within Rails layouts
|
31
|
+
plugin :render, layout: false unless options[:render] == false
|
31
32
|
|
32
33
|
plugin :rodauth, auth_class: auth_class, name: name, csrf: false, flash: false, json: true, **options, &block
|
33
34
|
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Rodauth
|
2
|
+
module Rails
|
3
|
+
module Tasks
|
4
|
+
class Routes
|
5
|
+
IGNORE = [:webauthn_setup_js, :webauthn_auth_js, :webauthn_autofill_js]
|
6
|
+
JSON_POST = [:two_factor_manage, :two_factor_auth]
|
7
|
+
|
8
|
+
attr_reader :auth_class
|
9
|
+
|
10
|
+
def initialize(auth_class)
|
11
|
+
@auth_class = auth_class
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
routes = auth_class.route_hash.map do |path, handle_method|
|
16
|
+
route_name = handle_method.to_s.sub(/\Ahandle_/, "").to_sym
|
17
|
+
next if IGNORE.include?(route_name)
|
18
|
+
verbs = route_verbs(route_name)
|
19
|
+
|
20
|
+
[
|
21
|
+
verbs.join("|"),
|
22
|
+
"#{rodauth.prefix}#{path}",
|
23
|
+
"rodauth#{configuration_name && "(:#{configuration_name})"}.#{route_name}_path",
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
27
|
+
routes.compact!
|
28
|
+
padding = routes.transpose.map { |string| string.map(&:length).max }
|
29
|
+
|
30
|
+
output_lines = routes.map do |columns|
|
31
|
+
[columns[0].ljust(padding[0]), columns[1].ljust(padding[1]), columns[2]].join(" ")
|
32
|
+
end
|
33
|
+
|
34
|
+
puts "\n #{output_lines.join("\n ")}"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def route_verbs(route_name)
|
40
|
+
file_path, start_line = rodauth.method(:"_handle_#{route_name}").source_location
|
41
|
+
lines = File.foreach(file_path).to_a
|
42
|
+
indentation = lines[start_line - 1][/^\s+/]
|
43
|
+
verbs = []
|
44
|
+
|
45
|
+
lines[start_line..-1].each do |code|
|
46
|
+
verbs << :GET if code.include?("r.get") && !rodauth.only_json?
|
47
|
+
verbs << :POST if code.include?("r.post")
|
48
|
+
break if code.start_with?("#{indentation}end")
|
49
|
+
end
|
50
|
+
|
51
|
+
verbs << :POST if rodauth.features.include?(:json) && JSON_POST.include?(route_name)
|
52
|
+
verbs
|
53
|
+
end
|
54
|
+
|
55
|
+
def rodauth
|
56
|
+
auth_class.new(scope)
|
57
|
+
end
|
58
|
+
|
59
|
+
def scope
|
60
|
+
auth_class.roda_class.new({})
|
61
|
+
end
|
62
|
+
|
63
|
+
def configuration_name
|
64
|
+
auth_class.configuration_name
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
@@ -1,42 +1,12 @@
|
|
1
|
+
require "rodauth/rails/tasks/routes"
|
2
|
+
|
1
3
|
namespace :rodauth do
|
4
|
+
desc "Lists endpoints that will be routed by your Rodauth app"
|
2
5
|
task routes: :environment do
|
3
|
-
|
4
|
-
|
5
|
-
puts "Routes handled by #{app}:"
|
6
|
-
|
7
|
-
app.opts[:rodauths].each do |configuration_name, auth_class|
|
8
|
-
rodauth = auth_class.allocate
|
9
|
-
only_json = rodauth.method(:only_json?).owner != Rodauth::Base && rodauth.only_json?
|
10
|
-
|
11
|
-
routes = auth_class.route_hash.map do |path, handle_method|
|
12
|
-
file_path, start_line = rodauth.method(:"_#{handle_method}").source_location
|
13
|
-
lines = File.foreach(file_path).to_a
|
14
|
-
indentation = lines[start_line - 1][/^\s+/]
|
15
|
-
verbs = []
|
16
|
-
|
17
|
-
lines[start_line..-1].each do |code|
|
18
|
-
verbs << :GET if code.include?("r.get") && !only_json
|
19
|
-
verbs << :POST if code.include?("r.post")
|
20
|
-
break if code.start_with?("#{indentation}end")
|
21
|
-
end
|
22
|
-
|
23
|
-
path_method = "#{handle_method.to_s.sub(/\Ahandle_/, "")}_path"
|
24
|
-
|
25
|
-
[
|
26
|
-
verbs.join("/"),
|
27
|
-
"#{rodauth.prefix}#{path}",
|
28
|
-
"rodauth#{configuration_name && "(:#{configuration_name})"}.#{path_method}",
|
29
|
-
]
|
30
|
-
end
|
31
|
-
|
32
|
-
verbs_padding = routes.map { |verbs, _, _| verbs.length }.max
|
33
|
-
path_padding = routes.map { |_, path, _| path.length }.max
|
34
|
-
|
35
|
-
route_lines = routes.map do |verbs, path, code|
|
36
|
-
"#{verbs.ljust(verbs_padding)} #{path.ljust(path_padding)} #{code}"
|
37
|
-
end
|
6
|
+
puts "Routes handled by #{Rodauth::Rails.app}:"
|
38
7
|
|
39
|
-
|
8
|
+
Rodauth::Rails.app.opts[:rodauths].each_value do |auth_class|
|
9
|
+
Rodauth::Rails::Tasks::Routes.new(auth_class).call
|
40
10
|
end
|
41
11
|
end
|
42
12
|
end
|
data/lib/rodauth/rails.rb
CHANGED
@@ -16,6 +16,16 @@ module Rodauth
|
|
16
16
|
@middleware = true
|
17
17
|
|
18
18
|
class << self
|
19
|
+
def lib(**options, &block)
|
20
|
+
c = Class.new(Rodauth::Rails::App)
|
21
|
+
c.configure(json: false, **options) do
|
22
|
+
enable :internal_request
|
23
|
+
instance_exec(&block)
|
24
|
+
end
|
25
|
+
c.freeze
|
26
|
+
c.rodauth
|
27
|
+
end
|
28
|
+
|
19
29
|
def rodauth(name = nil, account: nil, **options)
|
20
30
|
auth_class = app.rodauth!(name)
|
21
31
|
|
@@ -47,8 +57,17 @@ module Rodauth
|
|
47
57
|
Rodauth::Model.new(app.rodauth!(name), **options)
|
48
58
|
end
|
49
59
|
|
50
|
-
#
|
60
|
+
# Routing constraint that requires authenticated account.
|
61
|
+
def authenticate(name = nil, &condition)
|
62
|
+
lambda do |request|
|
63
|
+
rodauth = request.env.fetch ["rodauth", *name].join(".")
|
64
|
+
rodauth.require_account
|
65
|
+
condition.nil? || condition.call(rodauth)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
51
69
|
def authenticated(name = nil, &condition)
|
70
|
+
warn "Rodauth::Rails.authenticated has been deprecated in favor of Rodauth::Rails.authenticate, which additionally requires existence of the account record."
|
52
71
|
lambda do |request|
|
53
72
|
rodauth = request.env.fetch ["rodauth", *name].join(".")
|
54
73
|
rodauth.require_authentication
|
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: 1.
|
4
|
+
version: 1.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janko Marohnić
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -328,6 +328,7 @@ files:
|
|
328
328
|
- lib/rodauth/rails/model.rb
|
329
329
|
- lib/rodauth/rails/railtie.rb
|
330
330
|
- lib/rodauth/rails/tasks.rake
|
331
|
+
- lib/rodauth/rails/tasks/routes.rb
|
331
332
|
- lib/rodauth/rails/test.rb
|
332
333
|
- lib/rodauth/rails/test/controller.rb
|
333
334
|
- lib/rodauth/rails/version.rb
|
@@ -351,7 +352,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
351
352
|
- !ruby/object:Gem::Version
|
352
353
|
version: '0'
|
353
354
|
requirements: []
|
354
|
-
rubygems_version: 3.4.
|
355
|
+
rubygems_version: 3.4.10
|
355
356
|
signing_key:
|
356
357
|
specification_version: 4
|
357
358
|
summary: Provides Rails integration for Rodauth.
|