(ns auto-ap.auth.impersonation-test (:require [auto-ap.integration.util :refer [admin-token user-token wrap-setup]] [auto-ap.routes.utils :as routes-utils] [auto-ap.session-version :as session-version] [auto-ap.ssr.auth :as ssr-auth] [buddy.sign.jwt :as jwt] [clj-time.core :as time] [clojure.test :refer [deftest is testing use-fixtures]] [config.core :refer [env]])) (use-fixtures :each wrap-setup) ;; ============================================================================ ;; Impersonation Behaviors (3.1 - 3.6) ;; ============================================================================ (deftest test-impersonation-success (testing "Behavior 3.1: It should allow admin users to assume another user's identity via signed JWT" (let [impersonation-jwt (jwt/sign {:user "Target" :user/role "user" :user/name "Target User" :db/id 456 :exp (time/plus (time/now) (time/hours 1))} (:jwt-secret env) {:alg :hs512})] (with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)] (let [response (ssr-auth/impersonate {:query-params {"jwt" impersonation-jwt}})] (is (= 200 (:status response))) (is (= "Target User" (get-in response [:session :identity :user/name]))) (is (= 456 (get-in response [:session :identity :db/id])))))))) (deftest test-impersonation-jwt-signature (testing "Behavior 3.2: It should validate the impersonation JWT signature with :jwt-secret and :hs512" (let [impersonation-jwt (jwt/sign {:user "Target" :user/role "user" :user/name "Target" :db/id 456 :exp (time/plus (time/now) (time/hours 1))} (:jwt-secret env) {:alg :hs512})] (with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)] ;; Valid JWT should succeed (let [response (ssr-auth/impersonate {:query-params {"jwt" impersonation-jwt}})] (is (= 200 (:status response)))) ;; Invalid signature should fail (let [bad-jwt (jwt/sign {:user "Target" :user/role "user" :db/id 456 :exp (time/plus (time/now) (time/hours 1))} "wrong-secret" {:alg :hs512})] (is (thrown? Exception (ssr-auth/impersonate {:query-params {"jwt" bad-jwt}})))))))) (deftest test-impersonation-expired-jwt (testing "Behavior 3.3: It should reject expired impersonation JWTs" (let [expired-jwt (jwt/sign {:user "Target" :user/role "user" :user/name "Target" :db/id 456 :exp (time/minus (time/now) (time/hours 1))} (:jwt-secret env) {:alg :hs512})] (with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)] (is (thrown? Exception (ssr-auth/impersonate {:query-params {"jwt" expired-jwt}}))))))) (deftest test-impersonation-route-gates (testing "Behavior 3.4: It should block non-admin users from accessing /impersonate" (with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)] (let [handler (routes-utils/wrap-client-redirect-unauthenticated (routes-utils/wrap-secure (routes-utils/wrap-admin ssr-auth/impersonate))) response (handler {:identity (user-token) :uri "/impersonate"})] (is (= 302 (:status response))) (is (re-find #"^/login" (get-in response [:headers "Location"])))))) (testing "Behavior 3.5: It should block unauthenticated users from accessing /impersonate" (with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)] (let [handler (routes-utils/wrap-client-redirect-unauthenticated (routes-utils/wrap-secure (routes-utils/wrap-admin ssr-auth/impersonate))) response (handler {:identity nil :uri "/impersonate"})] (is (= 302 (:status response))) (is (re-find #"^/login" (get-in response [:headers "Location"])))))) (testing "Behavior 3.6: It should replace the admin's session with the impersonated user's session" (let [impersonation-jwt (jwt/sign {:user "Target" :user/role "user" :user/name "Target User" :db/id 456 :exp (time/plus (time/now) (time/hours 1))} (:jwt-secret env) {:alg :hs512})] (with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)] (let [handler (routes-utils/wrap-client-redirect-unauthenticated (routes-utils/wrap-secure (routes-utils/wrap-admin ssr-auth/impersonate))) response (handler {:identity (admin-token) :uri "/impersonate" :query-params {"jwt" impersonation-jwt}})] (is (= 200 (:status response))) ;; The response should have the impersonated user's session (let [session (:session response)] (is (= "Target User" (get-in session [:identity :user/name]))) (is (= "user" (get-in session [:identity :user/role]))) (is (= 456 (get-in session [:identity :db/id]))) (is (= session-version/current-session-version (:version session)))))))))