Files
once-campfire/app/models/user.rb
T
Jeremy Daer c78c3fd7ef Sign out the account's other sessions when its password changes
Changing the password is how someone shuts out whoever else is signed
in to their account, but every session opened with the old password
stayed valid. Now the others end and their live connections close, as
on sign-out. The session the password was changed from stays signed in.

Changing the password outside a request, as from a console, ends every
session.
2026-10-19 10:28:06 -07:00

82 lines
2.7 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class User < ApplicationRecord
include Avatar, Bannable, Bot, Mentionable, Role, Transferable
has_many :memberships, dependent: :delete_all
has_many :rooms, through: :memberships
has_many :reachable_messages, through: :rooms, source: :messages
has_many :messages, dependent: :destroy, foreign_key: :creator_id
has_many :push_subscriptions, class_name: "Push::Subscription", dependent: :delete_all
has_many :boosts, dependent: :destroy, foreign_key: :booster_id
has_many :searches, dependent: :delete_all
has_many :sessions, dependent: :destroy
has_many :bans, dependent: :destroy
enum :status, %i[ active deactivated banned ], default: :active
has_secure_password validations: false
after_create_commit :grant_membership_to_open_rooms
after_update :sign_out_other_sessions, if: :saved_change_to_password_digest?
scope :ordered, -> { order("LOWER(name)") }
scope :filtered_by, ->(query) { where("name like ?", "%#{query}%") }
def initials
name.scan(/\b\w/).join
end
def title
[ name, bio ].compact_blank.join(" – ")
end
def deactivate
transaction do
close_remote_connections
memberships.without_direct_rooms.delete_all
push_subscriptions.delete_all
searches.delete_all
sessions.delete_all
update! status: :deactivated, email_address: deactived_email_address
end
end
def reset_remote_connections
close_remote_connections reconnect: true
end
private
# A new password is how someone shuts out whoever else got into their account, so it ends the sessions opened
# with the old one. The session it was changed from, if any, stays.
def sign_out_other_sessions
sessions.excluding(Current.session).delete_all
end
# One statement, which SQLite runs under the write lock, so no room is closed between reading the open ones and
# inserting. A room that Room#destroy_later closes is either closed before, and skipped, or after, and takes this
# membership away with the others. A room that granted itself to every user when it was created is skipped too,
# as insert_all did.
def grant_membership_to_open_rooms
now = Time.current
Membership.connection.execute Membership.sanitize_sql([ <<~SQL, id, now, now ])
insert into memberships (room_id, user_id, created_at, updated_at)
select id, ?, ?, ? from rooms where type = 'Rooms::Open'
on conflict do nothing
SQL
end
def deactived_email_address
email_address&.gsub(/@/, "-deactivated-#{SecureRandom.uuid}@")
end
def close_remote_connections(reconnect: false)
ActionCable.server.remote_connections.where(current_user: self).disconnect reconnect: reconnect
end
end