mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-28 00:16:48 +09:00
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)
122 lines
4.7 KiB
Ruby
122 lines
4.7 KiB
Ruby
require "test_helper"
|
|
|
|
class UserTest < ActiveSupport::TestCase
|
|
test "user does not prevent very long passwords" do
|
|
users(:david).update(password: "secret" * 50)
|
|
assert users(:david).valid?
|
|
end
|
|
|
|
test "a password challenge is checked whenever the password would change" do
|
|
assert_not User.find(users(:david).id).update(password: "a new secret123456", password_challenge: "")
|
|
assert_not User.find(users(:david).id).update(password: "a new secret123456", password_challenge: "not my password")
|
|
assert User.find(users(:david).id).update(name: "Dave", password_challenge: "")
|
|
assert User.find(users(:david).id).update(password: "a new secret123456", password_challenge: "secret123456")
|
|
assert User.find(users(:david).id).update(password: "another secret123456")
|
|
end
|
|
|
|
test "creating users grants membership to the open rooms" do
|
|
assert_difference -> { Membership.count }, +Rooms::Open.count do
|
|
create_new_user
|
|
end
|
|
end
|
|
|
|
test "memberships granted to the open rooms are like any other" do
|
|
freeze_time
|
|
user = create_new_user
|
|
|
|
assert_equal Rooms::Open.ids.sort, user.memberships.pluck(:room_id).sort
|
|
user.memberships.each do |membership|
|
|
assert membership.involved_in_mentions?
|
|
assert_equal Time.current, membership.created_at
|
|
assert_equal Time.current, membership.updated_at
|
|
end
|
|
end
|
|
|
|
test "deactivating a user deletes push subscriptions, searches, memberships for non-direct rooms, and changes their email address" do
|
|
assert_difference -> { Membership.count }, -users(:david).memberships.without_direct_rooms.count do
|
|
assert_difference -> { Push::Subscription.count }, -users(:david).push_subscriptions.count do
|
|
assert_difference -> { Search.count }, -users(:david).searches.count do
|
|
SecureRandom.stubs(:uuid).returns("2e7de450-cf04-4fa8-9b02-ff5ab2d733e7")
|
|
users(:david).deactivate
|
|
assert_equal "david-deactivated-2e7de450-cf04-4fa8-9b02-ff5ab2d733e7@37signals.com", users(:david).reload.email_address
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
test "deactivating a user deletes their sessions" do
|
|
assert_changes -> { users(:david).sessions.count }, from: 1, to: 0 do
|
|
users(:david).deactivate
|
|
end
|
|
end
|
|
|
|
test "changing the password signs out every session, the current one included" do
|
|
Current.session = users(:david).sessions.create!(user_agent: "This device", ip_address: "127.0.0.1")
|
|
|
|
assert_changes -> { users(:david).sessions.count }, to: 0 do
|
|
users(:david).update!(password: "a new secret123456")
|
|
end
|
|
end
|
|
|
|
test "changing the password closes the account's 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) })
|
|
|
|
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 the password removes every push subscription of the user" do
|
|
assert users(:david).push_subscriptions.any?
|
|
|
|
users(:david).update!(password: "a new secret123456")
|
|
|
|
assert_empty users(:david).push_subscriptions.reload
|
|
assert users(:jason).push_subscriptions.any?
|
|
end
|
|
|
|
test "a password change that rolls back keeps push subscriptions" do
|
|
assert_no_changes -> { users(:david).push_subscriptions.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
|
|
|
|
assert_no_changes -> { [ users(:david).sessions.count, users(:david).push_subscriptions.count ] } do
|
|
users(:david).update!(name: "Dave", email_address: "dave@37signals.com")
|
|
end
|
|
end
|
|
|
|
private
|
|
def create_new_user
|
|
User.create!(name: "User", email_address: "user@example.com", password: "secret123456")
|
|
end
|
|
end
|