mikker / passwordless

🗝 Authentication for your Rails app without the icky-ness of passwords
MIT License
1.26k stars 85 forks source link

Feature Request: Constraint helpers for `config/routes.rb` #139

Closed henrikbjorn closed 2 months ago

henrikbjorn commented 1 year ago

When using devise they have helpers that can be used in config/routes.rb like:

authenticated :admin do
  root to: 'admin/dashboard#show', as: :admin_root
end

Which i personally like very much and better than doing require_user! in my controllers.

Would be a awesome addition to passwordless.

Not sure how it would work with Passwordless as most the user finding and authorizing etc. is in the ControllerHelper where Devise calls Warden e.g: https://github.com/heartcombo/devise/blob/main/lib/devise/rails/routes.rb#L477

mikker commented 1 year ago

Good question! PRs welcome ❤️

yshmarov commented 10 months ago

on a custom app that I built, something like this works for me:

# app/constraints/user_constraint.rb
class UserConstraint
  def initialize(&block)
    @block = block
  end

  def matches?(request)
    user = current_user(request)
    user.present? && @block.call(user)
  end

  def current_user(request)
    User.find_by(id: request.session[:user_id])
  end
end
# config/routes.rb
  # authenticate :user, ->(user) { user.admin? } do # <- devise syntax
  constraints UserConstraint.new { |user| user.admin? } do
    mount GoodJob::Engine, at: "good_job"
    mount Avo::Engine, at: Avo.configuration.root_path
  end

  # authenticated :user do # <- devise syntax
  constraints UserConstraint.new { |user| user.present? } do
    get 'dashboard', to: 'static#dashboard'
  end
tdegrunt commented 3 months ago

Similarly to the above, but the following works for me with Passwordless:

# app/constraints/user_constraint.rb
class PasswordlessConstraint
  include Passwordless::ControllerHelpers

  attr_reader :authenticatable_type, :session, :lambda

  def initialize(authenticatable_type, lambda)
    @authenticatable_type = authenticatable_type
    @lambda = lambda
  end

  def matches?(request)
    @session = request.session
    authenticatable = authenticate_by_session(authenticatable_type)
    authenticatable.present? && lambda.call(authenticatable)
  end
end
# config/routes.rb
constraints PasswordlessConstraint.new(User, ->(user) { user.has_role?("admin") }) do
  root to: 'admin/dashboard#show', as: :admin_root
end

The rest is syntactic sugar, but a bit too much magic.

mikker commented 3 months ago

@tdegrunt That's amazing! Would love to include it if you're up for creating a PR with a test and some docs?

henrikbjorn commented 3 months ago

Will this re-query the database for the user?

We might want to think about using env as warden does.

tdegrunt commented 3 months ago

Took me a while, been on holiday, but added a PR for this.