Files
once-campfire/app/controllers/concerns/authentication.rb
T
Jeremy Daer 19319a1cff Refuse a session for a password that changed after it was checked
A password change ends every session, but a sign-in or sign-in link checked
against the old password just before the change could still save its session
just after, and that session outlived the change.

The session now checks, as it's saved, that the user's password is still the
one the request loaded. The save's transaction holds SQLite's write lock from
the start, so a change commits either before the check, which fails it, or
after the insert, and ends this session with the others. Sessions start
through the user's association so the check sees the user that was
authenticated, and a refused sign-in is answered like a wrong password.
2026-10-20 08:37:07 -07:00

113 lines
2.9 KiB
Ruby

module Authentication
extend ActiveSupport::Concern
include SessionLookup
included do
before_action :require_authentication
before_action :deny_bots
helper_method :signed_in?
protect_from_forgery using: :header_only, with: :exception, unless: -> { authenticated_by.bot_key? }
end
class_methods do
def allow_unauthenticated_access(**options)
skip_before_action :require_authentication, **options
end
def allow_bot_access(**options)
skip_before_action :deny_bots, **options
end
def require_unauthenticated_access(**options)
skip_before_action :require_authentication, **options
before_action :restore_authentication, :redirect_signed_in_user_to_root, **options
end
end
private
def signed_in?
Current.user.present?
end
def require_authentication
restore_authentication || bot_authentication || request_authentication
end
def restore_authentication
if session = find_session_by_cookie
resume_session session
end
end
def bot_authentication
if params[:bot_key].present? && bot = User.authenticate_bot(params[:bot_key].strip)
Current.user = bot
set_authenticated_by(:bot_key)
end
end
def request_authentication
session[:return_to_after_authenticating] = request.url
redirect_to new_session_url
end
def redirect_signed_in_user_to_root
redirect_to root_url if signed_in?
end
def start_new_session_for(user)
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
authenticated_as session
end
end
def resume_session(session)
session.resume user_agent: request.user_agent, ip_address: request.remote_ip
authenticated_as session
end
def terminate_current_session
Current.session&.destroy!
reset_session
remove_authentication_cookie
disconnect_remote_connections
end
def disconnect_remote_connections
Current.user&.reset_remote_connections
rescue => error
Rails.logger.warn "Could not disconnect remote connections on sign out: #{error.class}"
end
def authenticated_as(session)
Current.session = session
set_authenticated_by(:session)
set_authentication_cookie(session)
end
def post_authenticating_url
session.delete(:return_to_after_authenticating) || root_url
end
def set_authentication_cookie(session)
cookies.signed.permanent[:session_token] = { value: session.token, httponly: true, same_site: :lax }
end
def remove_authentication_cookie
cookies.delete(:session_token)
end
def deny_bots
head :forbidden if authenticated_by.bot_key?
end
def set_authenticated_by(method)
@authenticated_by = method.to_s.inquiry
end
def authenticated_by
@authenticated_by ||= "".inquiry
end
end