rodauth-rails 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cc138f57505a1bbf92267e02b8d9b87f50c0dd9b6c114891f649c0b15878637
4
- data.tar.gz: 28fc8264a8629dd186a446a4d067167d572f8ea58b65c742f12f81a5192221db
3
+ metadata.gz: b8063be8ad00634114f74f0eb549c672e2b62cd1fa81cb7f124cc9cd12505e3f
4
+ data.tar.gz: 6f466e29420f9e4bacb58c855e942cc20289d2c3fc69a12638b97628d25dbbfb
5
5
  SHA512:
6
- metadata.gz: 74645990b10677d44503f272a63300465881c6e596b514ce0e1d1607689d8ace60c5e70a79c952256ad73bf704a8d268e27af3da8cdb617c5b381f752b302c4b
7
- data.tar.gz: 1525c4a51d4323e348ee2dc117e5ef320214f42f385549f5646af1d8e1792bfeac2be3f220b473873aed8fc72f4448aade9848b82fdd2c348874f8b4950e4631
6
+ metadata.gz: 8cc0af59c6ce29837fbc8a3401d456fd407ef76b74493b08ee9b4f2dfc8807d4a95c86f9bb0266401013d5162c009d46b7d07e3f741654af2cc267c0ee2c135e
7
+ data.tar.gz: 78c098dbaed458d5764ca2e7ee61f4710e01b2386d0cc04831b1732b9883d76c4b9f56c35c0a1e557c40951086d72bb0ed264f769313c1b45be30b2dd760024a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.11.0 (2021-05-06)
2
+
3
+ * Add controller-like logging for requests to Rodauth endpoints (@janko)
4
+
5
+ * Add `#rails_routes` to Roda and Rodauth instance for accessing Rails route helpers (@janko)
6
+
7
+ * Add `#rails_request` to Roda and Rodauth instance for retrieving an `ActionDispatch::Request` instance (@janko)
8
+
1
9
  ## 0.10.0 (2021-03-23)
2
10
 
3
11
  * Add `Rodauth::Rails::Auth` superclass for moving configurations into separate files (@janko)
data/README.md CHANGED
@@ -2,35 +2,6 @@
2
2
 
3
3
  Provides Rails integration for the [Rodauth] authentication framework.
4
4
 
5
- ## Table of contents
6
-
7
- * [Resources](#resources)
8
- * [Why Rodauth?](#why-rodauth)
9
- * [Upgrading](#upgrading)
10
- * [Installation](#installation)
11
- * [Usage](#usage)
12
- - [Routes](#routes)
13
- - [Current account](#current-account)
14
- - [Requiring authentication](#requiring-authentication)
15
- - [Views](#views)
16
- - [Mailer](#mailer)
17
- - [Migrations](#migrations)
18
- - [Multiple configurations](#multiple-configurations)
19
- - [Calling controller methods](#calling-controller-methods)
20
- - [Rodauth instance](#rodauth-instance)
21
- * [How it works](#how-it-works)
22
- - [Middleware](#middleware)
23
- - [App](#app)
24
- - [Sequel](#sequel)
25
- * [JSON API](#json-api)
26
- * [OmniAuth](#omniauth)
27
- * [Configuring](#configuring)
28
- * [Custom extensions](#custom-extensions)
29
- * [Testing](#testing)
30
- * [Rodauth defaults](#rodauth-defaults)
31
- - [Database functions](#database-functions)
32
- - [Account statuses](#account-statuses)
33
-
34
5
  ## Resources
35
6
 
36
7
  Useful links:
@@ -43,6 +14,7 @@ Articles:
43
14
  * [Rodauth: A Refreshing Authentication Solution for Ruby](https://janko.io/rodauth-a-refreshing-authentication-solution-for-ruby/)
44
15
  * [Adding Authentication in Rails with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
45
16
  * [Adding Multifactor Authentication in Rails with Rodauth](https://janko.io/adding-multifactor-authentication-in-rails-with-rodauth/)
17
+ * [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)
46
18
 
47
19
  ## Why Rodauth?
48
20
 
@@ -61,6 +33,12 @@ of the advantages that stand out for me:
61
33
  * consistent before/after hooks around everything
62
34
  * dedicated object encapsulating all authentication logic
63
35
 
36
+ One commmon concern is the fact that, unlike most other authentication
37
+ frameworks for Rails, Rodauth uses [Sequel] for database interaction instead of
38
+ Active Record. There are good reasons for this, and to make Rodauth work
39
+ smoothly alongside Active Record, rodauth-rails configures Sequel to [reuse
40
+ Active Record's database connection][sequel-activerecord_connection].
41
+
64
42
  ## Upgrading
65
43
 
66
44
  ### Upgrading to 0.7.0
@@ -271,6 +249,19 @@ These routes are fully functional, feel free to visit them and interact with the
271
249
  pages. The templates that ship with Rodauth aim to provide a complete
272
250
  authentication experience, and the forms use [Bootstrap] markup.
273
251
 
252
+ Inside Rodauth configuration and the `route` block you can access Rails route
253
+ helpers through `#rails_routes`:
254
+
255
+ ```rb
256
+ class RodauthApp < Rodauth::Rails::App
257
+ configure do
258
+ # ...
259
+ login_redirect { rails_routes.activity_path }
260
+ # ...
261
+ end
262
+ end
263
+ ```
264
+
274
265
  ### Current account
275
266
 
276
267
  To be able to fetch currently authenticated account, let's define a
@@ -491,7 +482,6 @@ end
491
482
  ```rb
492
483
  # app/lib/rodauth_app.rb
493
484
  class RodauthApp < Rodauth::Rails::App
494
- # ...
495
485
  configure do
496
486
  # ...
497
487
  create_reset_password_email do
@@ -521,17 +511,17 @@ class RodauthApp < Rodauth::Rails::App
521
511
  end
522
512
  ```
523
513
 
524
- The above configuration uses `#deliver_later`, which assumes Active Job is
525
- configured. It's generally recommended to send emails in a background job,
526
- for better throughput and ability to retry. However, if you want to send emails
527
- synchronously, you can modify the code to call `#deliver` instead.
514
+ This configuration calls `#deliver_later`, which uses Active Job to deliver
515
+ emails in a background job. It's generally recommended to send emails
516
+ asynchronously for better request throughput and the ability to retry
517
+ deliveries. However, if you want to send emails synchronously, modify the
518
+ configuration to call `#deliver_now` instead.
528
519
 
529
- The `#send_email` method will receive whatever object is returned by the
530
- `#create_*_email` methods. But if that doesn't suit you, you can override
531
- `#send_*_email` methods instead, which are expected to send the email
532
- immediately. This might work better in scenarios such as using a 3rd-party
533
- service for transactional emails, where emails are sent via HTTP instead of
534
- SMTP.
520
+ If you're using a background processing library without an Active Job adapter,
521
+ or a 3rd-party service for sending transactional emails, this two-phase API
522
+ might not be suitable. In this case, instead of overriding `#create_*_email`
523
+ and `#send_email`, override the `#send_*_email` methods instead, which are
524
+ required to send the email immediately.
535
525
 
536
526
  ### Migrations
537
527
 
@@ -586,6 +576,7 @@ class RodauthApp < Rodauth::Rails::App
586
576
  r.rodauth(:admin)
587
577
  r.pass # allow the Rails app to handle other "/admin/*" requests
588
578
  end
579
+
589
580
  # ...
590
581
  end
591
582
  end
@@ -638,7 +629,7 @@ common settings:
638
629
 
639
630
  ```rb
640
631
  # app/lib/rodauth_base.rb
641
- class RodauthBase < Rodauth::Rails::App
632
+ class RodauthBase < Rodauth::Rails::Auth
642
633
  # common settings that can be shared between multiple configurations
643
634
  configure do
644
635
  enable :login, :logout
@@ -676,7 +667,7 @@ class RodauthAdmin < Rodauth::Rails::Auth
676
667
  end
677
668
 
678
669
  def superadmin?
679
- Role.where(account_id: session_id).any? { |role| role.name == "superadmin" }
670
+ Role.where(account_id: session_id, type: "superadmin").any?
680
671
  end
681
672
  end
682
673
  ```
@@ -1179,29 +1170,38 @@ end
1179
1170
  ```
1180
1171
 
1181
1172
  If you're delivering emails in the background, make sure to set Active Job
1182
- queue adapter to `:test`:
1173
+ queue adapter to `:test` or `:inline`:
1183
1174
 
1184
1175
  ```rb
1185
1176
  # config/environments/test.rb
1186
1177
  Rails.application.configure do |config|
1187
1178
  # ...
1188
- config.active_job.queue_adapter = :test
1179
+ config.active_job.queue_adapter = :test # or :inline
1189
1180
  # ...
1190
1181
  end
1191
1182
  ```
1192
1183
 
1193
- If you need to create the account manually, you can do it as follows:
1184
+ If you need to create an account record with a password directly, you can do it
1185
+ as follows:
1194
1186
 
1195
1187
  ```rb
1196
- account_id = DB[:accounts].insert(
1197
- email: "user@example.com",
1198
- status: "verified",
1199
- )
1200
-
1201
- DB[:account_password_hashes].insert(
1202
- account_id: account_id,
1203
- password_hash: BCrypt::Password.create("secret", cost: BCrpyt::Engine::MIN_COST),
1204
- )
1188
+ # app/models/account.rb
1189
+ class Account < ApplicationRecord
1190
+ has_one :password_hash, foreign_key: :id
1191
+ end
1192
+ ```
1193
+ ```rb
1194
+ # app/models/account/password_hash.rb
1195
+ class Account::PasswordHash < ApplicationRecord
1196
+ belongs_to :account, foreign_key: :id
1197
+ end
1198
+ ```
1199
+ ```rb
1200
+ require "bcrypt"
1201
+
1202
+ account = Account.create!(email: "user@example.com", status: "verified")
1203
+ password_hash = BCrypt::Password.create("secret", cost: BCrypt::Engine::MIN_COST)
1204
+ account.create_password_hash!(id: account.id, password_hash: password_hash)
1205
1205
  ```
1206
1206
 
1207
1207
  ## Rodauth defaults
@@ -1,4 +1,4 @@
1
- Someone has requested a that the account with this email be unlocked.
1
+ Someone has requested that the account with this email be unlocked.
2
2
  If you did not request the unlocking of this account, please ignore this
3
3
  message. If you requested the unlocking of this account, please go to
4
4
  <%%= @email_link %>
@@ -35,6 +35,14 @@ module Rodauth
35
35
  env[["rodauth", *name].join(".")] = rodauth(name)
36
36
  end
37
37
  end
38
+
39
+ def rails_routes
40
+ ::Rails.application.routes.url_helpers
41
+ end
42
+
43
+ def rails_request
44
+ ActionDispatch::Request.new(env)
45
+ end
38
46
  end
39
47
  end
40
48
  end
@@ -27,24 +27,18 @@ module Rodauth
27
27
  end
28
28
 
29
29
  def flash
30
- rails_request.flash
30
+ scope.rails_request.flash
31
31
  end
32
32
 
33
33
  if ActionPack.version >= Gem::Version.new("5.0")
34
34
  def commit_flash
35
- rails_request.commit_flash
35
+ scope.rails_request.commit_flash
36
36
  end
37
37
  else
38
38
  def commit_flash
39
39
  # ActionPack 4.2 automatically commits flash
40
40
  end
41
41
  end
42
-
43
- private
44
-
45
- def rails_request
46
- ActionDispatch::Request.new(env)
47
- end
48
42
  end
49
43
  end
50
44
  end
@@ -63,24 +63,33 @@ module Rodauth
63
63
  super.html_safe
64
64
  end
65
65
 
66
+ delegate :rails_routes, :rails_request, to: :scope
67
+
66
68
  private
67
69
 
68
70
  # Runs controller callbacks and rescue handlers around Rodauth actions.
69
71
  def _around_rodauth(&block)
70
72
  result = nil
71
73
 
72
- rails_controller_rescue do
73
- rails_controller_callbacks do
74
- result = catch(:halt) { super(&block) }
74
+ rails_instrument_request do
75
+ rails_controller_rescue do
76
+ rails_controller_callbacks do
77
+ result = catch(:halt) { super(&block) }
78
+ end
75
79
  end
80
+
81
+ result = handle_rails_controller_response(result)
76
82
  end
77
83
 
84
+ throw :halt, result if result
85
+ end
86
+
87
+ # Handles controller rendering a response or setting response headers.
88
+ def handle_rails_controller_response(result)
78
89
  if rails_controller_instance.performed?
79
90
  rails_controller_response
80
91
  elsif result
81
92
  result[1].merge!(rails_controller_instance.response.headers)
82
- throw :halt, result
83
- else
84
93
  result
85
94
  end
86
95
  end
@@ -109,6 +118,20 @@ module Rodauth
109
118
  end
110
119
  end
111
120
 
121
+ def rails_instrument_request
122
+ ActiveSupport::Notifications.instrument("start_processing.rodauth", rodauth: self)
123
+ ActiveSupport::Notifications.instrument("process_request.rodauth", rodauth: self) do |payload|
124
+ begin
125
+ status, headers, body = yield
126
+ payload[:status] = status || 404
127
+ payload[:headers] = headers
128
+ payload[:body] = body
129
+ ensure
130
+ rails_controller_instance.send(:append_info_to_payload, payload)
131
+ end
132
+ end
133
+ end
134
+
112
135
  # Returns Roda response from controller response if set.
113
136
  def rails_controller_response
114
137
  controller_response = rails_controller_instance.response
@@ -117,7 +140,7 @@ module Rodauth
117
140
  response.headers.merge! controller_response.headers
118
141
  response.write controller_response.body
119
142
 
120
- request.halt
143
+ response.finish
121
144
  end
122
145
 
123
146
  # Create emails with ActionMailer which uses configured delivery method.
@@ -168,11 +191,8 @@ module Rodauth
168
191
 
169
192
  # Instances of the configured controller with current request's env hash.
170
193
  def _rails_controller_instance
171
- controller = rails_controller.new
172
- rails_request = ActionDispatch::Request.new(scope.env)
173
-
194
+ controller = rails_controller.new
174
195
  prepare_rails_controller(controller, rails_request)
175
-
176
196
  controller
177
197
  end
178
198
 
@@ -0,0 +1,34 @@
1
+ module Rodauth
2
+ module Rails
3
+ class LogSubscriber < ActiveSupport::LogSubscriber
4
+ def start_processing(event)
5
+ rodauth = event.payload[:rodauth]
6
+ app_class = rodauth.scope.class.superclass
7
+ format = rodauth.rails_request.format.ref
8
+ format = format.to_s.upcase if format.is_a?(Symbol)
9
+ format = "*/*" if format.nil?
10
+
11
+ info "Processing by #{app_class} as #{format}"
12
+ end
13
+
14
+ def process_request(event)
15
+ status = event.payload[:status]
16
+
17
+ additions = ActionController::Base.log_process_action(event.payload)
18
+ if ::Rails.gem_version >= Gem::Version.new("6.0")
19
+ additions << "Allocations: #{event.allocations}"
20
+ end
21
+
22
+ message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
23
+ message << " (#{additions.join(" | ")})"
24
+ message << "\n\n" if defined?(::Rails.env) && ::Rails.env.development?
25
+
26
+ info message
27
+ end
28
+
29
+ def logger
30
+ ::Rails.logger
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,5 +1,6 @@
1
1
  require "rodauth/rails/middleware"
2
2
  require "rodauth/rails/controller_methods"
3
+ require "rodauth/rails/log_subscriber"
3
4
 
4
5
  require "rails"
5
6
 
@@ -16,6 +17,10 @@ module Rodauth
16
17
  end
17
18
  end
18
19
 
20
+ initializer "rodauth.log_subscriber" do
21
+ Rodauth::Rails::LogSubscriber.attach_to :rodauth
22
+ end
23
+
19
24
  initializer "rodauth.test" do
20
25
  # Rodauth uses RACK_ENV to set the default bcrypt hash cost
21
26
  ENV["RACK_ENV"] = "test" if ::Rails.env.test?
@@ -1,5 +1,5 @@
1
1
  module Rodauth
2
2
  module Rails
3
- VERSION = "0.10.0"
3
+ VERSION = "0.11.0"
4
4
  end
5
5
  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.10.0
4
+ version: 0.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: 2021-03-23 00:00:00.000000000 Z
11
+ date: 2021-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -207,6 +207,7 @@ files:
207
207
  - lib/rodauth/rails/auth.rb
208
208
  - lib/rodauth/rails/controller_methods.rb
209
209
  - lib/rodauth/rails/feature.rb
210
+ - lib/rodauth/rails/log_subscriber.rb
210
211
  - lib/rodauth/rails/middleware.rb
211
212
  - lib/rodauth/rails/railtie.rb
212
213
  - lib/rodauth/rails/tasks.rake