mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-27 16:06:48 +09:00
Close live connections on the transaction that changed the password
An after_commit condition reads the record's last save, so another save of the user in the same transaction skipped the disconnect even though the sessions were gone. The callback that deletes them now registers the disconnect on the transaction itself, which also leaves a rolled back change alone. A password changed along with the avatar is covered too: the attachment's touch doesn't hide the change.
This commit is contained in:
1 parent
b01bb7a0a7
commit
e158388f3d
3 files changed
+40
-5
No files matched your search
+5
-5
@@ -21,7 +21,6 @@ class User < ApplicationRecord
|
||||
|
||||
after_create_commit :grant_membership_to_open_rooms
|
||||
after_update :sign_out_all_sessions, if: :saved_change_to_password_digest?
|
||||
after_update_commit :disconnect_signed_out_sessions, if: :saved_change_to_password_digest?
|
||||
|
||||
scope :ordered, -> { order("LOWER(name)") }
|
||||
scope :filtered_by, ->(query) { where("name like ?", "%#{query}%") }
|
||||
@@ -52,14 +51,15 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
private
|
||||
# A new password is how someone shuts out whoever else got into their account, so it ends every session opened
|
||||
# with the old one, copies of the changing browser's cookie included. The profile page signs that browser back in.
|
||||
# A new password is how someone shuts out whoever else got into their account, so it ends every session, copies
|
||||
# of the changing browser's cookie included. Connections close once that commits, registered on the transaction
|
||||
# because a later save in it would replace the saved change an after_commit condition reads.
|
||||
def sign_out_all_sessions
|
||||
sessions.delete_all
|
||||
self.class.current_transaction.after_commit { disconnect_signed_out_sessions }
|
||||
end
|
||||
|
||||
# After commit, so a connection that drops can't come back on a session that's still there. The password is
|
||||
# saved by then, so a realtime service that can't be reached mustn't fail the change.
|
||||
# The password is saved by now, so a realtime service that can't be reached mustn't fail the change.
|
||||
def disconnect_signed_out_sessions
|
||||
reset_remote_connections
|
||||
rescue => error
|
||||
|
||||
@@ -55,6 +55,19 @@ class Users::ProfilesControllerTest < ActionDispatch::IntegrationTest
|
||||
copy.assert_redirected_to new_session_url
|
||||
end
|
||||
|
||||
test "changing the password along with the avatar still signs this browser back in and closes live connections" do
|
||||
remote_connections = mock
|
||||
remote_connections.expects(:disconnect).with(reconnect: true)
|
||||
ActionCable.server.stubs(:remote_connections).returns(mock.tap { |m| m.expects(:where).with(current_user: users(:david)).returns(remote_connections) })
|
||||
|
||||
put user_profile_url, params: { user: { password: "a new secret123456", avatar: fixture_file_upload("moon.jpg", "image/jpeg") } }
|
||||
assert_redirected_to user_profile_url
|
||||
assert users(:david).reload.avatar.attached?
|
||||
|
||||
get user_profile_url
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "changing the password closes the account's live connections" do
|
||||
remote_connections = mock
|
||||
remote_connections.expects(:disconnect).with(reconnect: true)
|
||||
|
||||
@@ -58,6 +58,28 @@ class UserTest < ActiveSupport::TestCase
|
||||
users(:david).update!(password: "a new secret123456")
|
||||
end
|
||||
|
||||
test "changing the password closes live connections even when the user is saved again before commit" do
|
||||
remote_connections = mock
|
||||
remote_connections.expects(:disconnect).with(reconnect: true)
|
||||
ActionCable.server.stubs(:remote_connections).returns(mock.tap { |m| m.expects(:where).with(current_user: users(:david)).returns(remote_connections) })
|
||||
|
||||
User.transaction do
|
||||
users(:david).update!(password: "a new secret123456")
|
||||
users(:david).update!(name: "Dave")
|
||||
end
|
||||
end
|
||||
|
||||
test "a password change that rolls back keeps every session and connection" do
|
||||
ActionCable.server.expects(:remote_connections).never
|
||||
|
||||
assert_no_changes -> { users(:david).sessions.count } do
|
||||
User.transaction do
|
||||
users(:david).update!(password: "a new secret123456")
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "changing other details keeps every session" do
|
||||
ActionCable.server.expects(:remote_connections).never
|
||||
|
||||
|
||||
Reference in new issue
Block a user