Skip to content

Commit

Permalink
Add Rodauth::Rails.authenticate constraint
Browse files Browse the repository at this point in the history
It improves upon the existing `Rodauth::Rails.authenticated` constraint
by additionally requiring the existence of the account record in the
database. It also has clearer naming, communicating that it requires
authentication, and doesn't just check if the account is authenticated
(like Devise's `authenticated` constraint).
  • Loading branch information
janko committed Aug 4, 2023
1 parent b378bce commit f6deb8e
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 15 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ level. You can do this via the built-in `authenticated` routing constraint:
```rb
# config/routes.rb
Rails.application.routes.draw do
constraints Rodauth::Rails.authenticated do
constraints Rodauth::Rails.authenticate do
# ... authenticated routes ...
end
end
Expand All @@ -249,7 +249,7 @@ called with the Rodauth instance:
# config/routes.rb
Rails.application.routes.draw do
# require multifactor authentication to be setup
constraints Rodauth::Rails.authenticated { |rodauth| rodauth.uses_two_factor_authentication? } do
constraints Rodauth::Rails.authenticate { |rodauth| rodauth.uses_two_factor_authentication? } do
# ...
end
end
Expand All @@ -260,7 +260,7 @@ You can specify a different Rodauth configuration by passing the configuration n
```rb
# config/routes.rb
Rails.application.routes.draw do
constraints Rodauth::Rails.authenticated(:admin) do
constraints Rodauth::Rails.authenticate(:admin) do
# ...
end
end
Expand Down
11 changes: 10 additions & 1 deletion lib/rodauth/rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,17 @@ def model(name = nil, **options)
Rodauth::Model.new(app.rodauth!(name), **options)
end

# routing constraint that requires authentication
# Routing constraint that requires authenticated account.
def authenticate(name = nil, &condition)
lambda do |request|
rodauth = request.env.fetch ["rodauth", *name].join(".")
rodauth.require_account
condition.nil? || condition.call(rodauth)
end
end

def authenticated(name = nil, &condition)
warn "Rodauth::Rails.authenticated has been deprecated in favor of Rodauth::Rails.authenticate, which additionally requires existence of the account record."
lambda do |request|
rodauth = request.env.fetch ["rodauth", *name].join(".")
rodauth.require_authentication
Expand Down
2 changes: 1 addition & 1 deletion test/rails_app/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
get :roda
end

constraints Rodauth::Rails.authenticated do
constraints Rodauth::Rails.authenticate do
get "/authenticated" => "test#root"
end
end
24 changes: 14 additions & 10 deletions test/rodauth_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,33 +50,37 @@ class RodauthTest < UnitTest
Rails.env = "test"
end

test "builds authenticated constraint" do
Account.create!(email: "[email protected]", password: "secret")
test "builds authenticate constraint" do
account = Account.create!(email: "[email protected]", password: "secret", status: "verified")

rodauth = Rodauth::Rails.rodauth
rodauth.scope.env["rodauth"] = rodauth
request = rodauth.request

error = assert_raises(Rodauth::InternalRequestError) { Rodauth::Rails.authenticated.call(rodauth.request) }
error = assert_raises(Rodauth::InternalRequestError) { Rodauth::Rails.authenticate.call(request) }
assert_equal :login_required, error.reason

rodauth.account_from_login("[email protected]")
rodauth.login_session("password")
assert_equal true, Rodauth::Rails.authenticated.call(rodauth.request)
assert_equal true, Rodauth::Rails.authenticate.call(request)

rodauth.add_recovery_code
rodauth.session.delete(:two_factor_auth_setup)
error = assert_raises(Rodauth::InternalRequestError) { Rodauth::Rails.authenticated.call(rodauth.request) }
error = assert_raises(Rodauth::InternalRequestError) { Rodauth::Rails.authenticate.call(request) }
assert_equal :two_factor_need_authentication, error.reason

rodauth.send(:two_factor_update_session, "recovery_codes")
assert_equal true, Rodauth::Rails.authenticated.call(rodauth.request)
assert_equal true, Rodauth::Rails.authenticate.call(request)

constraint = Rodauth::Rails.authenticated { |rodauth| rodauth.authenticated_by.include?("otp") }
assert_equal false, constraint.call(rodauth.request)
constraint = Rodauth::Rails.authenticate { |rodauth| rodauth.authenticated_by.include?("otp") }
assert_equal false, constraint.call(request)

rodauth.scope.env["rodauth.admin"] = rodauth.scope.env.delete("rodauth")
constraint = Rodauth::Rails.authenticated(:admin)
assert_equal true, constraint.call(rodauth.request)
assert_equal true, Rodauth::Rails.authenticate(:admin).call(request)

capture_io { account.destroy } # silence composite primary key warnings
error = assert_raises(Rodauth::InternalRequestError) { Rodauth::Rails.authenticate(:admin).call(request) }
assert_equal :login_required, error.reason
end

test "returns current account if logged in" do
Expand Down

0 comments on commit f6deb8e

Please sign in to comment.