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
|
true
|
||||||
end
|
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