feat: add sign_in and sign_out to authenticatable
This commit is contained in:
1 parent
7e18e4f80d
commit
40225e5791
1 file changed
+167
@@ -163,3 +163,170 @@ module Prism
|
||||
true
|
||||
end
|
||||
|
||||
def sign_in(user, remember: false)
|
||||
session[authenticatable_session_key] = user.id
|
||||
@current_user = user
|
||||
if remember
|
||||
remember_user(user)
|
||||
elsif authenticatable_remember_sessions_model
|
||||
session.delete(:login_session_id)
|
||||
@current_login_session = nil
|
||||
end
|
||||
end
|
||||
|
||||
# Ends the current login: for `remember_cookie:`, rotates the shared
|
||||
# token (not just deletes the cookie) so a copy of the old cookie
|
||||
# value - already sitting in a proxy log, another browser tab,
|
||||
# wherever - stops working immediately, everywhere, instead of
|
||||
# remaining valid until it naturally expires. For `remember_sessions:`,
|
||||
# destroys only THIS ONE login-session row, leaving every other
|
||||
# remembered browser signed in exactly as before.
|
||||
def sign_out
|
||||
if authenticatable_remember_sessions_model
|
||||
current_login_session&.destroy
|
||||
forget_current_login_session_cookie
|
||||
elsif authenticatable_remember_cookie
|
||||
forget_user(current_user)
|
||||
end
|
||||
session.delete(authenticatable_session_key)
|
||||
@current_user = nil
|
||||
@current_login_session = nil
|
||||
end
|
||||
|
||||
def require_authentication
|
||||
return if user_signed_in?
|
||||
session[:return_to_after_authenticating] = request.path
|
||||
respond_to_unauthenticated
|
||||
end
|
||||
|
||||
# Where to send the user once they do sign in: the page they were
|
||||
# trying to reach before `require_authentication` intercepted them, or
|
||||
# '/' if they arrived at the sign-in page directly.
|
||||
def after_authentication_url
|
||||
session.delete(:return_to_after_authenticating) || '/'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Accepts either the model class itself or its name as a String/Symbol
|
||||
# (resolved lazily, the same way Associable resolves `class_name:`), so
|
||||
# `authenticate_with` can be declared before the model class exists yet
|
||||
# (e.g. load order, or a model built dynamically in a test).
|
||||
def authenticatable_model
|
||||
configured = self.class.authenticatable_model
|
||||
raise "#{self.class} must call authenticate_with model: SomeModel" unless configured
|
||||
configured.is_a?(Module) ? configured : Object.const_get(configured.to_s)
|
||||
end
|
||||
|
||||
def authenticatable_session_key
|
||||
self.class.authenticatable_session_key || :user_id
|
||||
end
|
||||
|
||||
def authenticatable_remember_cookie
|
||||
self.class.authenticatable_remember_cookie
|
||||
end
|
||||
|
||||
def authenticatable_remember_sessions_model
|
||||
configured = self.class.authenticatable_remember_sessions_model
|
||||
return nil unless configured
|
||||
configured.is_a?(Module) ? configured : Object.const_get(configured.to_s)
|
||||
end
|
||||
|
||||
def authenticatable_remember_sessions_foreign_key
|
||||
(self.class.authenticatable_remember_sessions_foreign_key || :user_id).to_s
|
||||
end
|
||||
|
||||
def authenticatable_max_remember_sessions
|
||||
self.class.authenticatable_max_remember_sessions
|
||||
end
|
||||
|
||||
def user_from_remember_cookie
|
||||
if authenticatable_remember_sessions_model
|
||||
user_from_remember_session_cookie
|
||||
elsif authenticatable_remember_cookie
|
||||
user_from_remember_column_cookie
|
||||
end
|
||||
end
|
||||
|
||||
def user_from_remember_session_cookie
|
||||
token = cookies.signed[REMEMBER_SESSION_COOKIE]
|
||||
return nil unless token
|
||||
login_session = authenticatable_remember_sessions_model.find_by(token: token)
|
||||
return nil unless login_session
|
||||
user = authenticatable_model.find_by(id: login_session[authenticatable_remember_sessions_foreign_key])
|
||||
return nil unless user
|
||||
session[authenticatable_session_key] = user.id
|
||||
session[:login_session_id] = login_session.id
|
||||
user
|
||||
end
|
||||
|
||||
def user_from_remember_column_cookie
|
||||
token = cookies.signed[authenticatable_remember_cookie]
|
||||
return nil unless token
|
||||
user = authenticatable_model.find_by(authenticatable_remember_cookie => token)
|
||||
session[authenticatable_session_key] = user.id if user
|
||||
user
|
||||
end
|
||||
|
||||
def remember_user(user)
|
||||
if authenticatable_remember_sessions_model
|
||||
remember_user_with_session(user)
|
||||
elsif authenticatable_remember_cookie
|
||||
remember_user_with_column(user)
|
||||
else
|
||||
raise "#{self.class} must pass remember_cookie: or remember_sessions: to authenticate_with " \
|
||||
'to use sign_in(remember: true)'
|
||||
end
|
||||
end
|
||||
|
||||
def remember_user_with_session(user)
|
||||
model = authenticatable_remember_sessions_model
|
||||
attributes = { authenticatable_remember_sessions_foreign_key => user.id }
|
||||
attributes['user_agent'] = request.user_agent if model.columns.include?('user_agent')
|
||||
attributes['ip_address'] = request.remote_addr if model.columns.include?('ip_address')
|
||||
login_session = model.create!(attributes)
|
||||
session[:login_session_id] = login_session.id
|
||||
@current_login_session = login_session
|
||||
cookies.signed[REMEMBER_SESSION_COOKIE] = { value: login_session.token, max_age: REMEMBER_COOKIE_MAX_AGE }
|
||||
prune_oldest_login_sessions(user)
|
||||
end
|
||||
|
||||
# Destroys the oldest rows for `user` beyond `max_remember_sessions:`, if
|
||||
# configured. The row just created above is always the newest by id, so
|
||||
# it is never among the ones pruned here (unless the limit is 0, which
|
||||
# would prune it too - authenticate_with does not special-case that,
|
||||
# matching Devise's own `0 means never remembered` behavior for a
|
||||
# deliberately-set zero).
|
||||
def prune_oldest_login_sessions(user)
|
||||
limit = authenticatable_max_remember_sessions
|
||||
return unless limit
|
||||
model = authenticatable_remember_sessions_model
|
||||
sessions = model.where(authenticatable_remember_sessions_foreign_key => user.id).order(:id, :asc).to_a
|
||||
overflow = sessions.size - limit
|
||||
return if overflow <= 0
|
||||
sessions.first(overflow).each(&:destroy)
|
||||
end
|
||||
|
||||
def remember_user_with_column(user)
|
||||
column = authenticatable_remember_cookie
|
||||
user.send("regenerate_#{column}!") if user.send(column).to_s.empty?
|
||||
cookies.signed[column] = { value: user.send(column), max_age: REMEMBER_COOKIE_MAX_AGE }
|
||||
end
|
||||
|
||||
def forget_user(user)
|
||||
column = authenticatable_remember_cookie
|
||||
user.send("regenerate_#{column}!") if user && user.persisted?
|
||||
cookies.set(column, '', max_age: 0)
|
||||
end
|
||||
|
||||
def forget_current_login_session_cookie
|
||||
cookies.set(REMEMBER_SESSION_COOKIE, '', max_age: 0)
|
||||
session.delete(:login_session_id)
|
||||
end
|
||||
|
||||
def respond_to_unauthenticated
|
||||
path = self.class.unauthenticated_redirect_path
|
||||
path ? redirect(path) : halt(401, 'authentication required')
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user