rodauth-rails 1.2.1 → 1.3.1
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 +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +81 -2
- data/lib/generators/rodauth/migration/base.erb +2 -7
- data/lib/generators/rodauth/templates/app/misc/rodauth_main.rb +4 -1
- data/lib/generators/rodauth/templates/app/models/account.rb +4 -0
- data/lib/rodauth/rails/app.rb +19 -7
- data/lib/rodauth/rails/controller_methods.rb +9 -0
- data/lib/rodauth/rails/feature/base.rb +10 -0
- data/lib/rodauth/rails/feature/instrumentation.rb +8 -0
- data/lib/rodauth/rails/railtie.rb +9 -0
- data/lib/rodauth/rails/test/controller.rb +41 -0
- data/lib/rodauth/rails/test.rb +7 -0
- data/lib/rodauth/rails/version.rb +1 -1
- data/rodauth-rails.gemspec +2 -1
- metadata +20 -6
- data/lib/rodauth/rails/app/flash.rb +0 -46
- data/lib/rodauth/rails/app/middleware.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e268594e5890725cbba25ee24ab158df204ada3789aeebe428b737307128d9e
|
4
|
+
data.tar.gz: dca778863032dc428ac44b5feca48fe430ef55ea64f4e10050293d1c1a3d95c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
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
|
-
|
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
|
data/lib/rodauth/rails/app.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
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
|
data/rodauth-rails.gemspec
CHANGED
@@ -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.
|
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.
|
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-
|
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.
|
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.
|
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
|