# frozen_string_literal: true module Kingsman module Controllers # Provide sign in and sign out functionality. # Included by default in all controllers. module SignInOut # Return true if the given scope is signed in session. If no scope given, return # true if any scope is signed in. This will run authentication hooks, which may # cause exceptions to be thrown from this method; if you simply want to check # if a scope has already previously been authenticated without running # authentication hooks, you can directly call `warden.authenticated?(scope: scope)` def signed_in?(scope = nil) [scope || Kingsman.mappings.keys].flatten.any? do |_scope| warden.authenticate?(scope: _scope) end end # Sign in a user that already was authenticated. This helper is useful for logging # users in after sign up. All options given to sign_in is passed forward # to the set_user method in warden. # If you are using a custom warden strategy and the timeoutable module, you have to # set `env["kingsman.skip_timeout"] = true` in the request to use this method, like we do # in the sessions controller: https://github.com/heartcombo/kingsman/blob/main/app/controllers/kingsman/sessions_controller.rb#L7 # # Examples: # # sign_in :user, @user # sign_in(scope, resource) # sign_in @user # sign_in(resource) # sign_in @user, event: :authentication # sign_in(resource, options) # sign_in @user, store: false # sign_in(resource, options) # def sign_in(resource_or_scope, *args) options = args.extract_options! scope = Kingsman::Mapping.find_scope!(resource_or_scope) resource = args.last || resource_or_scope expire_data_after_sign_in! if warden.user(scope) == resource && !options.delete(:force) # Do nothing. User already signed in and we are not forcing it. true else warden.set_user(resource, options.merge!(scope: scope)) end end # Sign in a user bypassing the warden callbacks and stores the user # straight in session. This option is useful in cases the user is already # signed in, but we want to refresh the credentials in session. # # Examples: # # bypass_sign_in @user, scope: :user # bypass_sign_in @user def bypass_sign_in(resource, scope: nil) scope ||= Kingsman::Mapping.find_scope!(resource) expire_data_after_sign_in! warden.session_serializer.store(resource, scope) end # Sign out a given user or scope. This helper is useful for signing out a user # after deleting accounts. Returns true if there was a logout and false if there # is no user logged in on the referred scope # # Examples: # # sign_out :user # sign_out(scope) # sign_out @user # sign_out(resource) # def sign_out(resource_or_scope = nil) return sign_out_all_scopes unless resource_or_scope scope = Kingsman::Mapping.find_scope!(resource_or_scope) user = warden.user(scope: scope, run_callbacks: false) # If there is no user warden.logout(scope) warden.clear_strategies_cache!(scope: scope) instance_variable_set(:"@current_#{scope}", nil) !!user end # Sign out all active users or scopes. This helper is useful for signing out all roles # in one click. This signs out ALL scopes in warden. Returns true if there was at least one logout # and false if there was no user logged in on all scopes. def sign_out_all_scopes(lock = true) users = Kingsman.mappings.keys.map { |s| warden.user(scope: s, run_callbacks: false) } warden.logout expire_data_after_sign_out! warden.clear_strategies_cache! warden.lock! if lock users.any? end private def expire_data_after_sign_in! # TODO: remove once Rails 5.2+ and forward are only supported. # session.keys will return an empty array if the session is not yet loaded. # This is a bug in both Rack and Rails. # A call to #empty? forces the session to be loaded. session.empty? session.keys.grep(/^kingsman\./).each { |k| session.delete(k) } end alias :expire_data_after_sign_out! :expire_data_after_sign_in! end end end