🐛 Invalidate all sessions on profile deletion

When a profile is deleted, only the current session was being
invalidated. Other active sessions on different devices remained
functional until the background cleanup task completed.

Add session/invalidate-all helper that deletes all sessions for
a profile by profile_id, and call it from delete-profile before
the response transform. This ensures immediate access revocation
across all devices when an account is deleted.

Closes #11114

AI-assisted-by: qwen3.7-plus
This commit is contained in:
Andrey Antukh 2026-08-06 12:06:49 +00:00
parent 229d24e8f2
commit 8acd01855f
3 changed files with 37 additions and 0 deletions

View File

@ -226,6 +226,14 @@
(-> (db/exec-one! cfg [sql (:profile-id session) (:id session)])
(db/get-update-count))))
(defn invalidate-all
"Delete all sessions for a given profile. Used when a profile is deleted
to ensure immediate access revocation across all devices."
[cfg profile-id]
(let [sql "delete from http_session_v2 where profile_id = ?"]
(-> (db/exec-one! cfg [sql profile-id])
(db/get-update-count))))
(def ^:private sql:clear-organization-sso-sessions
(str "UPDATE http_session_v2 "
"SET props = props #- ARRAY['~:sso', ?]::text[] "

View File

@ -534,6 +534,10 @@
:deleted-at deleted-at
:id profile-id}})
;; Invalidate all sessions for this profile to ensure immediate
;; access revocation across all devices
(session/invalidate-all cfg profile-id)
(-> (rph/wrap nil)
(rph/with-transform (session/delete-fn cfg)))))

View File

@ -388,6 +388,31 @@
(let [result (th/run-task! :objects-gc {:min-age 0})]
(t/is (= 10 (:processed result))))))
(t/deftest profile-deletion-invalidates-all-sessions
(let [prof (th/create-profile* 1)
;; Insert 3 sessions for this profile directly into the database
session-ids (doall
(for [i (range 3)]
(let [sid (uuid/random)]
(th/db-exec-one! ["INSERT INTO http_session_v2 (id, profile_id, user_agent) VALUES (?, ?, ?)"
sid (:id prof) (str "user-agent-" i)])
sid)))]
;; Verify sessions exist
(let [count-before (:count (th/db-exec-one! ["SELECT count(*) FROM http_session_v2 WHERE profile_id = ?" (:id prof)]))]
(t/is (= 3 count-before)))
;; Request profile to be deleted
(let [params {::th/type :delete-profile
::rpc/profile-id (:id prof)}
out (th/command! params)]
(t/is (nil? (:error out))))
;; Verify ALL sessions were invalidated (not just one)
(let [count-after (:count (th/db-exec-one! ["SELECT count(*) FROM http_session_v2 WHERE profile_id = ?" (:id prof)]))]
(t/is (= 0 count-after)))))
(t/deftest email-blacklist-1
(t/is (false? (email.blacklist/enabled? th/*system*)))