Authentication.api(some_user).change_login(new_login: 'user@example.com')
Authentication.api(:admin).create_account(login: 'admin@example.com')
Authentication.api(:user).login_path # => '/log_in'
# verify_account without knowing emailing the user or knowing the key that would be generated
Authentication.api(user) do
transaction do
before_verify_account
verify_account
remove_verify_account_key
after_verify_account
end
end
-
-
Save bjeanes/85eabe7fb03e73cb9b1e6bc7cc81a0f8 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 | |
def self.api(*args, **opts, &blk) | |
api = API.new(*args, **opts) | |
return api.internal(&blk) if block_given? | |
api | |
end | |
class API | |
def initialize( | |
config_or_record = :user, | |
actor: nil, | |
reason: nil, | |
env: {}, | |
session: {} | |
) | |
config_name, account = resolve_config_name_and_account(config_or_record) | |
# RequestContext.request will be the parent request in Rails, which assists in my custom auditing information capture | |
env = (RequestContext.request&.env || {}).merge(env) | |
actor ||= RequestContext.actor | |
env.merge!("internal.audit_actor" => actor) if actor | |
env.merge!("internal.audit_reason" => reason) if reason | |
@account = account | |
@session = session | |
@env = env | |
@config_name = config_name.to_sym | |
@auth_class = RodauthConfig.opts[:rodauths].fetch(@config_name) | |
end | |
def internal(params: {}, &blk) | |
# FIXME: can I build this based on @auth_class without duplicating the session special-casing | |
# which internal_request does? | |
instance = Rodauth::Rails.rodauth( | |
@config_name, | |
account: @account, | |
env: @env.except("rack.input", "QUERY_STRING"), # rodauth-rails doesn't replace these, so need to exclude | |
form: params, | |
session: @session | |
) | |
instance.instance_eval(&blk) if block_given? | |
instance | |
end | |
private | |
def resolve_config_name_and_account(config_or_record) | |
case config_or_record | |
when :admin, :user | |
[config_or_record, 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, *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) | |
return false unless meth =~ /_path$|_url$/ # We only delegate _path and _url methods | |
@auth_class.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