mirror of
https://github.com/penpot/penpot.git
synced 2026-08-23 21:28:38 +00:00
🐛 Fix session invalidation on logout to prevent token replay (#11317)
Logout only cleared the auth-token cookie but never deleted the server-side row because delete-fn read ::id which wrap-authz no longer sets since 363b4e3778. Make delete-fn delete via ::session/:id attached by wrap-authz so replayed tokens are rejected (CWE-613, GHSA-mj9f-5cwq-7p3q). Add regression tests covering invalidation, idempotency and isolation of other sessions. Fix verified with Red→Green TDD and full backend suite (677 tests). Closes #11316 AI-assisted-by: muse-spark-1.2-contributor
This commit is contained in:
parent
dd4a163217
commit
7c85837290
@ -204,7 +204,7 @@
|
||||
[{:keys [::manager]}]
|
||||
(assert (manager? manager) "expected valid session manager")
|
||||
(fn [request response]
|
||||
(some->> (get request ::id) (delete-session manager))
|
||||
(some->> (get request ::session) :id (delete-session manager))
|
||||
(clear-session-cookie response)))
|
||||
|
||||
(defn decode-token
|
||||
|
||||
106
backend/test/backend_tests/rpc_auth_test.clj
Normal file
106
backend/test/backend_tests/rpc_auth_test.clj
Normal file
@ -0,0 +1,106 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns backend-tests.rpc-auth-test
|
||||
(:require
|
||||
[app.common.uuid :as uuid]
|
||||
[app.http.session :as session]
|
||||
[backend-tests.helpers :as th]
|
||||
[clojure.test :as t]
|
||||
[yetti.response :as yres]))
|
||||
|
||||
(t/use-fixtures :once th/state-init)
|
||||
(t/use-fixtures :each th/database-reset)
|
||||
|
||||
(t/deftest logout-invalidates-current-session
|
||||
(let [prof (th/create-profile* 1)
|
||||
manager (::session/manager th/*system*)
|
||||
sid (uuid/random)
|
||||
_ (th/db-exec-one! ["INSERT INTO http_session_v2 (id, profile_id, user_agent) VALUES (?, ?, ?)"
|
||||
sid (:id prof) "test-agent"])
|
||||
session (session/read-session manager sid)]
|
||||
|
||||
;; Arrange: session exists before logout
|
||||
(t/is (some? session) "session should exist before logout")
|
||||
(t/is (= sid (:id session)))
|
||||
|
||||
;; Act: simulate Ring request as produced by wrap-authz (has ::session/session)
|
||||
;; delete-fn is used as response transform via rph/with-transform in auth/logout
|
||||
(let [request {::session/session session}
|
||||
response {}
|
||||
delete-fn (session/delete-fn th/*system*)
|
||||
result (delete-fn request response)]
|
||||
|
||||
;; Assert: server-side session is deleted (CWE-613)
|
||||
(t/is (nil? (session/read-session manager sid))
|
||||
"session must be deleted server-side after logout (GHSA-mj9f-5cwq-7p3q)")
|
||||
|
||||
;; Assert: cookie is cleared
|
||||
(t/is (= "" (get-in result [::yres/cookies "auth-token" :value]))
|
||||
"auth-token cookie should be cleared")
|
||||
(t/is (= 0 (get-in result [::yres/cookies "auth-token" :max-age]))
|
||||
"auth-token cookie max-age should be 0"))))
|
||||
|
||||
(t/deftest logout-clears-cookie-even-when-session-missing
|
||||
(let [manager (::session/manager th/*system*)
|
||||
sid (uuid/random)
|
||||
;; No session inserted, read should be nil
|
||||
_ (t/is (nil? (session/read-session manager sid)))
|
||||
request {}
|
||||
response {}
|
||||
delete-fn (session/delete-fn th/*system*)
|
||||
result (delete-fn request response)]
|
||||
|
||||
;; Should still clear cookie (idempotent)
|
||||
(t/is (= "" (get-in result [::yres/cookies "auth-token" :value])))
|
||||
(t/is (= 0 (get-in result [::yres/cookies "auth-token" :max-age])))))
|
||||
|
||||
(t/deftest logout-does-not-invalidate-other-sessions
|
||||
(let [prof (th/create-profile* 1)
|
||||
manager (::session/manager th/*system*)
|
||||
sid1 (uuid/random)
|
||||
sid2 (uuid/random)
|
||||
_ (th/db-exec-one! ["INSERT INTO http_session_v2 (id, profile_id, user_agent) VALUES (?, ?, ?)"
|
||||
sid1 (:id prof) "agent-1"])
|
||||
_ (th/db-exec-one! ["INSERT INTO http_session_v2 (id, profile_id, user_agent) VALUES (?, ?, ?)"
|
||||
sid2 (:id prof) "agent-2"])
|
||||
s1 (session/read-session manager sid1)
|
||||
s2 (session/read-session manager sid2)]
|
||||
|
||||
(t/is (some? s1))
|
||||
(t/is (some? s2))
|
||||
|
||||
;; Logout only sid1
|
||||
(let [request {::session/session s1}
|
||||
response {}
|
||||
delete-fn (session/delete-fn th/*system*)]
|
||||
(delete-fn request response))
|
||||
|
||||
;; sid1 deleted, sid2 intact
|
||||
(t/is (nil? (session/read-session manager sid1)) "current session should be deleted")
|
||||
(t/is (some? (session/read-session manager sid2)) "other sessions should remain")))
|
||||
|
||||
(t/deftest replay-after-logout-cannot-authenticate
|
||||
(let [prof (th/create-profile* 1)
|
||||
manager (::session/manager th/*system*)
|
||||
sid (uuid/random)
|
||||
_ (th/db-exec-one! ["INSERT INTO http_session_v2 (id, profile_id, user_agent) VALUES (?, ?, ?)"
|
||||
sid (:id prof) "test-agent"])
|
||||
session (session/read-session manager sid)]
|
||||
|
||||
(t/is (some? session) "session exists before logout")
|
||||
|
||||
;; Simulate logout
|
||||
(let [request {::session/session session}
|
||||
response {}
|
||||
delete-fn (session/delete-fn th/*system*)]
|
||||
(delete-fn request response))
|
||||
|
||||
;; Replay: attempt to read session with same sid should fail (no profile attached)
|
||||
(t/is (nil? (session/read-session manager sid))
|
||||
"replayed token must not resolve to a valid session after logout")))
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user