rodauth-rails 0.10.0 → 0.11.0

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