Created
December 1, 2023 02:58
-
-
Save bjeanes/f06ba065880feccae4bd5c8e2b56225b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Authentication | |
class API | |
# Some methods we use are on the "instance" which Rodauth doesn't (yet) consider part of its API. We'll hardcode the | |
# ones we want to let through here so we are conscious which parts of the private API we are exposing. | |
@@delegated_instance_methods = [ | |
:otp_interval, | |
:otp_exists?, | |
:account_webauthn_usage, | |
:set_password, | |
:clear_other_sessions, | |
/_path$/, | |
/_url$/, | |
/_redirect$/, # redirect locations | |
/_subject$/, # email subjects | |
/\?$/ # any predicate is unlikely to cause harm | |
].freeze | |
def initialize( | |
config_or_record = :user, | |
actor: nil, | |
evidence: nil, # deprecated, use :audit_reason | |
env: {}, | |
session: {}, | |
**opts | |
) | |
env = (RequestContext.request&.env || {}).merge(env || {}).except( | |
Rack::REQUEST_METHOD, | |
Rack::PATH_INFO, | |
Rack::HTTP_HOST, | |
Rack::CONTENT_TYPE, | |
Rack::SCRIPT_NAME | |
) | |
actor ||= RequestContext.actor || :missing # don't allow an explicit nil actor | |
opts[:audit_actor] = actor if actor | |
opts[:audit_reason] ||= evidence if evidence | |
opts[:allowed_emails] ||= :none | |
opts.each do |k, v| | |
env.merge!("internal.#{k}" => v) if v.present? | |
end | |
@config_name, @account = resolve_config_name_and_account(config_or_record) | |
@session = session | |
@env = env | |
@auth_class = RodauthConfig.opts[:rodauths].fetch(@config_name) | |
end | |
def internal(**opts, &blk) | |
blk ||= proc { self } | |
internal_request_eval(**opts) do | |
account_from_session if session_value | |
instance_eval(&blk) | |
end | |
end | |
private | |
def resolve_config_name_and_account(config_or_record) | |
case config_or_record | |
when :admin, :user, "admin", "user" | |
[config_or_record.to_sym, nil] | |
when User, Admin # NOTE: object can be wrapped with Draper; this matches either way | |
config = config_or_record.model_name.param_key.to_sym # this approach for both decorated and naked objects | |
account = Account.find(config_or_record.id) | |
[config, account] | |
when Account | |
account = config_or_record | |
[(account.user ? :user : :admin), account] | |
else | |
raise ArgumentError, "expected config name or instance of User or Admin, got: #{config_or_record.inspect}}" | |
end | |
end | |
def method_missing(meth, *args, **opts, &blk) | |
if delegated_class_method?(meth) | |
opts[:account_id] = @account.id if @account | |
opts[:env] = @env.merge(opts[:env] || {}) | |
opts[:session] = @session.merge(opts[:session] || {}) | |
@auth_class.send(meth, *args, **opts, &blk) | |
elsif delegated_instance_method?(meth) | |
internal { send(meth, *args, **opts, &blk) } | |
else | |
super | |
end | |
end | |
def respond_to_missing?(meth, _include_private = false) | |
delegated_class_method?(meth) || delegated_instance_method?(meth) || super | |
end | |
def delegated_class_method?(meth) | |
# This _should_ only match internal request methods, but this has only been anecdotally demonstrated | |
@auth_class.methods(_ancestors = false).include?(meth) | |
end | |
def delegated_instance_method?(meth) | |
case meth.to_sym | |
when *@@delegated_instance_methods | |
true | |
else | |
false | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment