mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-29 00:44:05 +09:00
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.
(cherry picked from commit 19319a1cff)
This commit is contained in:
1 parent
2021bc3296
commit
48da2b4e4e
8 files changed
+66
-5
No files matched your search
@@ -57,7 +57,7 @@ module Authentication
|
||||
end
|
||||
|
||||
def start_new_session_for(user)
|
||||
user.sessions.start!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
|
||||
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
|
||||
authenticated_as session
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,5 +11,7 @@ class Sessions::TransfersController < ApplicationController
|
||||
else
|
||||
head :bad_request
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
head :bad_request
|
||||
end
|
||||
end
|
||||
@@ -14,6 +14,8 @@ class SessionsController < ApplicationController
|
||||
else
|
||||
render_rejection :unauthorized
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render_rejection :unauthorized
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
||||
+12
-3
@@ -7,13 +7,22 @@ class Session < ApplicationRecord
|
||||
|
||||
before_create { self.last_active_at ||= Time.now }
|
||||
|
||||
def self.start!(user_agent:, ip_address:)
|
||||
create! user_agent: user_agent, ip_address: ip_address
|
||||
end
|
||||
validate :user_password_unchanged, on: :create
|
||||
|
||||
def resume(user_agent:, ip_address:)
|
||||
if last_active_at.before?(ACTIVITY_REFRESH_RATE.ago)
|
||||
update! user_agent: user_agent, ip_address: ip_address, last_active_at: Time.now
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# A password change ends every session, so one can't start on a password checked before the change. Validated in
|
||||
# the save's transaction, which holds the database's write lock from the start, so a change commits either before
|
||||
# this check, and fails it, or after the insert, and ends this session with the others. Compared here rather than in
|
||||
# the query, which logs its values.
|
||||
def user_password_unchanged
|
||||
unless User.where(id: user_id).pick(:password_digest) == user.password_digest
|
||||
errors.add :user, :invalid
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user