From 8acd01855fb5504b691078538d6ec49ee699024f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Aug 2026 12:06:49 +0000 Subject: [PATCH] :bug: 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 --- backend/src/app/http/session.clj | 8 ++++++ backend/src/app/rpc/commands/profile.clj | 4 +++ .../test/backend_tests/rpc_profile_test.clj | 25 +++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index 614942c072..61140a780c 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -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[] " diff --git a/backend/src/app/rpc/commands/profile.clj b/backend/src/app/rpc/commands/profile.clj index 26716cc411..77307a1482 100644 --- a/backend/src/app/rpc/commands/profile.clj +++ b/backend/src/app/rpc/commands/profile.clj @@ -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))))) diff --git a/backend/test/backend_tests/rpc_profile_test.clj b/backend/test/backend_tests/rpc_profile_test.clj index f846cfb343..f9900d44ab 100644 --- a/backend/test/backend_tests/rpc_profile_test.clj +++ b/backend/test/backend_tests/rpc_profile_test.clj @@ -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*)))