Skip to content

Instantly share code, notes, and snippets.

@bjeanes
Created July 22, 2021 00:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bjeanes/a48be85c35614fea4c31a53ae66e287d to your computer and use it in GitHub Desktop.
Save bjeanes/a48be85c35614fea4c31a53ae66e287d to your computer and use it in GitHub Desktop.
module Authentication
class API
def initialize(
config_name = nil,
env: {},
session: {},
**opts
)
@opts = opts
@session = session
@env = env
@auth_class = RodauthApp.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 method_missing(meth, *args, **opts, &blk)
if delegated_class_method?(meth)
opts = @opts.merge(opts)
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) }
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)
return false unless meth =~ /(_path|_url|_redirect|\?)\z/
@auth_class.instance_methods(_ancestors = true).include?(meth) ||
@auth_class.private_instance_methods(_ancestors = true).include?(meth)
end
end
end
module Authentication
class API
def initialize(
config_or_record = :user,
actor: nil,
evidence: nil, # deprecated, use :audit_reason
env: {},
session: {},
**opts
)
env = (RequestContext.request&.env || {}).merge(env || {})
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 = RodauthApp.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'
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) }
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)
return false unless meth =~ /(_path|_url|_redirect|\?)\z/
# Hmm I thought `.instance_methods` returned public+private but there are some methods we need that are
# only showing up in the latter, so check both.
@auth_class.instance_methods(_ancestors = true).include?(meth) ||
@auth_class.private_instance_methods(_ancestors = true).include?(meth)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment