rodauth-rails 1.2.1 → 1.3.1

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: 43bfba245fc25ff73659728066a02b0227b0041823547767a10fb7025f354329
4
- data.tar.gz: 8e66a69d20e07b882e633330d27f7fafde524ba8e7af0052c83025bf101c3c10
3
+ metadata.gz: 4e268594e5890725cbba25ee24ab158df204ada3789aeebe428b737307128d9e
4
+ data.tar.gz: dca778863032dc428ac44b5feca48fe430ef55ea64f4e10050293d1c1a3d95c6
5
5
  SHA512:
6
- metadata.gz: e94b952207b08ba887d4168e442c669d15d04af11a0dada8f56d38f572ca1c662b0160067c2680cc984a203afe7cb443c1e3d9893544e9920655f4159c463d7f
7
- data.tar.gz: 56562e10ef5f361511fe090265b6647056f9befa8ba3d2095c90a5e07f18e4e61ba202a6dad28289ccaded5ba096a535ff38a98096e034d811f0749bcc1a1207
6
+ metadata.gz: 9008b2381959811820eca625f68c5df7ec2021319ceb2c7bdf6c75412f32ea51f01ffabd716da73f6565e263e0a74699c2c940a70296adf0b9c0b8576ef8e3de
7
+ data.tar.gz: 4b0d70d43ff624b610bcded93a528b089d103f11975b7fdec9c8bd38b1f81b5c003de2fa1e4068f2c2006841f91783044280fa575ec5951b2ae319f4836a49b7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## 1.3.1 (2022-04-22)
2
+
3
+ * Ensure response status is logged when calling a halting rodauth method inside a controller (@janko)
4
+
5
+ ## 1.3.0 (2022-04-01)
6
+
7
+ * Store password hash on the `accounts` table in generated Rodauth migration and configuration (@janko)
8
+
9
+ * Add support for controller testing with Minitest or RSpec (@janko)
10
+
11
+ * Fix `enum` declaration in generated `Account` model for Active Record < 7.0 (@janko)
12
+
13
+ * Ensure `require_login_redirect` points to the login page even if the login route changes (@janko)
14
+
15
+ ## 1.2.2 (2022-02-22)
16
+
17
+ * Fix flash messages not being preserved through consecutive redirects (@janko)
18
+
1
19
  ## 1.2.1 (2022-02-19)
2
20
 
3
21
  * Change `accounts.status` column type from string to integer (@zhongsheng)
data/README.md CHANGED
@@ -10,7 +10,6 @@ Provides Rails integration for the [Rodauth] authentication framework.
10
10
  * [Rails demo](https://github.com/janko/rodauth-demo-rails)
11
11
  * [JSON API guide](https://github.com/janko/rodauth-rails/wiki/JSON-API)
12
12
  * [OmniAuth guide](https://github.com/janko/rodauth-rails/wiki/OmniAuth)
13
- * [Testing guide](https://github.com/janko/rodauth-rails/wiki/Testing)
14
13
 
15
14
  🎥 Screencasts:
16
15
 
@@ -40,7 +39,7 @@ of the advantages that stand out for me:
40
39
  * consistent before/after hooks around everything
41
40
  * dedicated object encapsulating all authentication logic
42
41
 
43
- One commmon concern is the fact that, unlike most other authentication
42
+ One common concern is the fact that, unlike most other authentication
44
43
  frameworks for Rails, Rodauth uses [Sequel] for database interaction instead of
45
44
  Active Record. There are good reasons for this, and to make Rodauth work
46
45
  smoothly alongside Active Record, rodauth-rails configures Sequel to [reuse
@@ -783,6 +782,86 @@ Rodauth::Rails.rodauth(session: { two_factor_auth_setup: true })
783
782
  Rodauth::Rails.rodauth(:admin, params: { "param" => "value" })
784
783
  ```
785
784
 
785
+ ## Testing
786
+
787
+ For system and integration tests, which run the whole middleware stack,
788
+ authentication can be exercised normally via HTTP endpoints. See [this wiki
789
+ page](https://github.com/janko/rodauth-rails/wiki/Testing) for some examples.
790
+
791
+ For controller tests, you can log in accounts by modifying the session:
792
+
793
+ ```rb
794
+ # app/controllers/articles_controller.rb
795
+ class ArticlesController < ApplicationController
796
+ before_action -> { rodauth.require_authentication }
797
+
798
+ def index
799
+ # ...
800
+ end
801
+ end
802
+ ```
803
+ ```rb
804
+ # test/controllers/articles_controller_test.rb
805
+ class ArticlesControllerTest < ActionController::TestCase
806
+ test "required authentication" do
807
+ get :index
808
+
809
+ assert_response 302
810
+ assert_redirected_to "/login"
811
+ assert_equal "Please login to continue", flash[:alert]
812
+
813
+ account = Account.create!(email: "user@example.com", password: "secret", status: "verified")
814
+ login(account)
815
+
816
+ get :index
817
+ assert_response 200
818
+
819
+ logout
820
+
821
+ get :index
822
+ assert_response 302
823
+ assert_equal "Please login to continue", flash[:alert]
824
+ end
825
+
826
+ private
827
+
828
+ # Manually modify the session into what Rodauth expects.
829
+ def login(account)
830
+ session[:account_id] = account.id
831
+ session[:authenticated_by] = ["password"] # or ["password", "totp"] for MFA
832
+ end
833
+
834
+ def logout
835
+ session.clear
836
+ end
837
+ end
838
+ ```
839
+
840
+ If you're using multiple configurations with different session prefixes, you'll need
841
+ to make sure to use those in controller tests as well:
842
+
843
+ ```rb
844
+ class RodauthAdmin < Rodauth::Rails::Auth
845
+ configure do
846
+ session_key_prefix "admin_"
847
+ end
848
+ end
849
+ ```
850
+ ```rb
851
+ # in a controller test:
852
+ session[:admin_account_id] = account.id
853
+ session[:admin_authenticated_by] = ["password"]
854
+ ```
855
+
856
+ If you want to access the Rodauth instance in controller tests, you can do so
857
+ through the controller instance:
858
+
859
+ ```rb
860
+ # in a controller test:
861
+ @controller.rodauth #=> #<RodauthMain ...>
862
+ @controller.rodauth(:admin) #=> #<RodauthAdmin ...>
863
+ ```
864
+
786
865
  ## Configuring
787
866
 
788
867
  ### Configuration methods
@@ -3,23 +3,18 @@ enable_extension "citext"
3
3
 
4
4
  <% end -%>
5
5
  create_table :accounts<%= primary_key_type %> do |t|
6
+ t.integer :status, null: false, default: 1
6
7
  <% case activerecord_adapter -%>
7
8
  <% when "postgresql" -%>
8
9
  t.citext :email, null: false
9
10
  <% else -%>
10
11
  t.string :email, null: false
11
12
  <% end -%>
12
- t.integer :status, null: false, default: 1
13
13
  <% case activerecord_adapter -%>
14
14
  <% when "postgresql", "sqlite3" -%>
15
15
  t.index :email, unique: true, where: "status IN (1, 2)"
16
16
  <% else -%>
17
17
  t.index :email, unique: true
18
18
  <% end -%>
19
- end
20
-
21
- # Used if storing password hashes in a separate table (default)
22
- create_table :account_password_hashes<%= primary_key_type %> do |t|
23
- t.foreign_key :accounts, column: :id
24
- t.string :password_hash, null: false
19
+ t.string :password_hash
25
20
  end
@@ -35,7 +35,7 @@ class RodauthMain < Rodauth::Rails::Auth
35
35
  account_status_column :status
36
36
 
37
37
  # Store password hash in a column instead of a separate table.
38
- # account_password_hash_column :password_digest
38
+ account_password_hash_column :password_hash
39
39
 
40
40
  # Set password when creating account instead of when verifying.
41
41
  verify_account_set_password? false
@@ -138,6 +138,9 @@ class RodauthMain < Rodauth::Rails::Auth
138
138
 
139
139
  # Redirect to login page after password reset.
140
140
  reset_password_redirect { login_path }
141
+
142
+ # Ensure requiring login follows login route changes.
143
+ require_login_redirect { login_path }
141
144
  <% end -%>
142
145
 
143
146
  # ==> Deadlines
@@ -1,4 +1,8 @@
1
1
  class Account < ApplicationRecord
2
2
  include Rodauth::Rails.model
3
+ <% if ActiveRecord.version >= Gem::Version.new("7.0") -%>
3
4
  enum :status, unverified: 1, verified: 2, closed: 3
5
+ <% else -%>
6
+ enum status: { unverified: 1, verified: 2, closed: 3 }
7
+ <% end -%>
4
8
  end
@@ -5,17 +5,21 @@ module Rodauth
5
5
  module Rails
6
6
  # The superclass for creating a Rodauth middleware.
7
7
  class App < Roda
8
- require "rodauth/rails/app/middleware"
9
- plugin Middleware
8
+ plugin :middleware, forward_response_headers: true do |middleware|
9
+ middleware.class_eval do
10
+ def self.inspect
11
+ "#{superclass}::Middleware"
12
+ end
13
+
14
+ def inspect
15
+ "#<#{self.class.inspect} request=#{request.inspect} response=#{response.inspect}>"
16
+ end
17
+ end
18
+ end
10
19
 
11
20
  plugin :hooks
12
21
  plugin :render, layout: false
13
22
 
14
- unless Rodauth::Rails.api_only?
15
- require "rodauth/rails/app/flash"
16
- plugin Flash
17
- end
18
-
19
23
  def self.configure(*args, **options, &block)
20
24
  auth_class = args.shift if args[0].is_a?(Class)
21
25
  name = args.shift if args[0].is_a?(Symbol)
@@ -35,6 +39,14 @@ module Rodauth
35
39
  end
36
40
  end
37
41
 
42
+ after do
43
+ rails_request.commit_flash
44
+ end unless ActionPack.version < Gem::Version.new("5.0")
45
+
46
+ def flash
47
+ rails_request.flash
48
+ end
49
+
38
50
  def rails_routes
39
51
  ::Rails.application.routes.url_helpers
40
52
  end
@@ -18,6 +18,15 @@ module Rodauth
18
18
 
19
19
  private
20
20
 
21
+ # Adds response status to instrumentation payload for logging,
22
+ # when calling a halting rodauth method inside a controller.
23
+ def append_info_to_payload(payload)
24
+ super
25
+ if request.env["rodauth.rails.status"]
26
+ payload[:status] = request.env.delete("rodauth.rails.status")
27
+ end
28
+ end
29
+
21
30
  def rodauth_response
22
31
  res = catch(:halt) { return yield }
23
32
 
@@ -54,6 +54,16 @@ module Rodauth
54
54
 
55
55
  private
56
56
 
57
+ unless ActionPack.version < Gem::Version.new("5.0")
58
+ # When calling a Rodauth method that redirects inside the Rails
59
+ # router, Roda's after hook that commits the flash would never get
60
+ # called, so we make sure to commit the flash beforehand.
61
+ def redirect(*)
62
+ rails_request.commit_flash
63
+ super
64
+ end
65
+ end
66
+
57
67
  def instantiate_rails_account
58
68
  if defined?(ActiveRecord::Base) && rails_account_model < ActiveRecord::Base
59
69
  rails_account_model.instantiate(account.stringify_keys)
@@ -10,6 +10,14 @@ module Rodauth
10
10
 
11
11
  def redirect(*)
12
12
  rails_instrument_redirection { super }
13
+ ensure
14
+ request.env["rodauth.rails.status"] = response.status
15
+ end
16
+
17
+ def return_response(*)
18
+ super
19
+ ensure
20
+ request.env["rodauth.rails.status"] = response.status
13
21
  end
14
22
 
15
23
  def rails_render(*)
@@ -1,5 +1,6 @@
1
1
  require "rodauth/rails/middleware"
2
2
  require "rodauth/rails/controller_methods"
3
+ require "rodauth/rails/test"
3
4
 
4
5
  require "rails"
5
6
 
@@ -21,6 +22,14 @@ module Rodauth
21
22
  initializer "rodauth.test" do
22
23
  # Rodauth uses RACK_ENV to set the default bcrypt hash cost
23
24
  ENV["RACK_ENV"] = "test" if ::Rails.env.test?
25
+
26
+ if ActionPack.version >= Gem::Version.new("5.0")
27
+ ActiveSupport.on_load(:action_controller_test_case) do
28
+ include Rodauth::Rails::Test::Controller
29
+ end
30
+ else
31
+ ActionController::TestCase.include Rodauth::Rails::Test::Controller
32
+ end
24
33
  end
25
34
 
26
35
  rake_tasks do
@@ -0,0 +1,41 @@
1
+ require "active_support/concern"
2
+
3
+ module Rodauth
4
+ module Rails
5
+ module Test
6
+ module Controller
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ setup :setup_rodauth
11
+ end
12
+
13
+ def process(*)
14
+ catch_rodauth { super }
15
+ end
16
+ ruby2_keywords(:process) if respond_to?(:ruby2_keywords, true)
17
+
18
+ private
19
+
20
+ def setup_rodauth
21
+ Rodauth::Rails.app.opts[:rodauths].each do |name, auth_class|
22
+ scope = auth_class.roda_class.new(request.env)
23
+ request.env[["rodauth", *name].join(".")] = auth_class.new(scope)
24
+ end
25
+ end
26
+
27
+ def catch_rodauth(&block)
28
+ result = catch(:halt, &block)
29
+
30
+ if result.is_a?(Array) # rodauth response
31
+ response.status = result[0]
32
+ response.headers.merge! result[1]
33
+ response.body = result[2]
34
+ end
35
+
36
+ response
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,7 @@
1
+ module Rodauth
2
+ module Rails
3
+ module Test
4
+ autoload :Controller, "rodauth/rails/test/controller"
5
+ end
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  module Rodauth
2
2
  module Rails
3
- VERSION = "1.2.1"
3
+ VERSION = "1.3.1"
4
4
  end
5
5
  end
@@ -17,7 +17,8 @@ Gem::Specification.new do |spec|
17
17
  spec.require_paths = ["lib"]
18
18
 
19
19
  spec.add_dependency "railties", ">= 4.2", "< 8"
20
- spec.add_dependency "rodauth", "~> 2.19"
20
+ spec.add_dependency "rodauth", "~> 2.23"
21
+ spec.add_dependency "roda", "~> 3.55"
21
22
  spec.add_dependency "sequel-activerecord_connection", "~> 1.1"
22
23
  spec.add_dependency "tilt"
23
24
  spec.add_dependency "bcrypt"
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.2.1
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-19 00:00:00.000000000 Z
11
+ date: 2022-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -36,14 +36,28 @@ dependencies:
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '2.19'
39
+ version: '2.23'
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '2.19'
46
+ version: '2.23'
47
+ - !ruby/object:Gem::Dependency
48
+ name: roda
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.55'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.55'
47
61
  - !ruby/object:Gem::Dependency
48
62
  name: sequel-activerecord_connection
49
63
  requirement: !ruby/object:Gem::Requirement
@@ -230,8 +244,6 @@ files:
230
244
  - lib/rodauth-rails.rb
231
245
  - lib/rodauth/rails.rb
232
246
  - lib/rodauth/rails/app.rb
233
- - lib/rodauth/rails/app/flash.rb
234
- - lib/rodauth/rails/app/middleware.rb
235
247
  - lib/rodauth/rails/auth.rb
236
248
  - lib/rodauth/rails/controller_methods.rb
237
249
  - lib/rodauth/rails/feature.rb
@@ -247,6 +259,8 @@ files:
247
259
  - lib/rodauth/rails/model/associations.rb
248
260
  - lib/rodauth/rails/railtie.rb
249
261
  - lib/rodauth/rails/tasks.rake
262
+ - lib/rodauth/rails/test.rb
263
+ - lib/rodauth/rails/test/controller.rb
250
264
  - lib/rodauth/rails/version.rb
251
265
  - rodauth-rails.gemspec
252
266
  homepage: https://github.com/janko/rodauth-rails
@@ -1,46 +0,0 @@
1
- module Rodauth
2
- module Rails
3
- class App
4
- # Roda plugin that sets up Rails flash integration.
5
- module Flash
6
- def self.load_dependencies(app)
7
- app.plugin :hooks
8
- end
9
-
10
- def self.configure(app)
11
- app.before { request.flash } # load flash
12
- app.after { request.commit_flash } # save flash
13
- end
14
-
15
- module InstanceMethods
16
- def flash
17
- request.flash
18
- end
19
- end
20
-
21
- module RequestMethods
22
- # If the redirect would bubble up outside of the Roda app, the after
23
- # hook would never get called, so we make sure to commit the flash.
24
- def redirect(*)
25
- commit_flash
26
- super
27
- end
28
-
29
- def flash
30
- scope.rails_request.flash
31
- end
32
-
33
- if ActionPack.version >= Gem::Version.new("5.0")
34
- def commit_flash
35
- scope.rails_request.commit_flash
36
- end
37
- else
38
- def commit_flash
39
- # ActionPack 4.2 automatically commits flash
40
- end
41
- end
42
- end
43
- end
44
- end
45
- end
46
- end
@@ -1,36 +0,0 @@
1
- module Rodauth
2
- module Rails
3
- class App
4
- # Roda plugin that extends middleware plugin by propagating response headers.
5
- module Middleware
6
- def self.configure(app)
7
- handle_result = -> (env, res) do
8
- if headers = env.delete("rodauth.rails.headers")
9
- res[1] = headers.merge(res[1])
10
- end
11
- end
12
-
13
- app.plugin :middleware, handle_result: handle_result do |middleware|
14
- middleware.plugin :hooks
15
-
16
- middleware.after do
17
- if response.empty? && response.headers.any?
18
- env["rodauth.rails.headers"] = response.headers
19
- end
20
- end
21
-
22
- middleware.class_eval do
23
- def self.inspect
24
- "#{superclass}::Middleware"
25
- end
26
-
27
- def inspect
28
- "#<#{self.class.inspect} request=#{request.inspect} response=#{response.inspect}>"
29
- end
30
- end
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end