diff options
| author | EuAndreh <eu@euandre.org> | 2026-04-22 08:27:17 -0300 |
|---|---|---|
| committer | EuAndreh <eu@euandre.org> | 2026-04-22 08:27:17 -0300 |
| commit | f6f6167d54c2644b51a71f964adb768682e8227e (patch) | |
| tree | 68d33fb79a50d3ff813ea12bdc177a96d54c379a | |
| parent | m (diff) | |
| download | papod-f6f6167d54c2644b51a71f964adb768682e8227e.tar.gz papod-f6f6167d54c2644b51a71f964adb768682e8227e.tar.xz | |
m
| -rw-r--r-- | src/papod.clj | 279 | ||||
| -rw-r--r-- | tests/unit.clj | 246 |
2 files changed, 431 insertions, 94 deletions
diff --git a/src/papod.clj b/src/papod.clj index 63cfab9..aaa5c23 100644 --- a/src/papod.clj +++ b/src/papod.clj @@ -142,11 +142,13 @@ :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/value} + {:db/ident :papod.channel.type/public} + {:db/ident :papod.channel.type/private} + {:db/ident :papod.channel.type/unlisted} {:db/ident :papod.channel/type - :db/valueType :db.type/string + :db/valueType :db.type/ref :db/cardinality :db.cardinality/one - :db/index true - :db.attr/preds `non-empty?} + :db/index true} {:db/ident :papod.channel/label :db/valueType :db.type/string :db/cardinality :db.cardinality/one} @@ -238,6 +240,69 @@ :db/unique :db.unique/value :db/tupleAttrs [:papod.membership/channel :papod.membership/nick]} + ;; Session (auth record, references cracha session) + {:db/ident :papod.session/id + :db/valueType :db.type/uuid + :db/cardinality :db.cardinality/one + :db/unique :db.unique/identity} + {:db/ident :papod.session/nick + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one + :db.attr/preds `non-empty?} + {:db/ident :papod.session/created-at + :db/valueType :db.type/instant + :db/cardinality :db.cardinality/one} + {:db/ident :papod.session/finished-at + :db/valueType :db.type/instant + :db/cardinality :db.cardinality/one} + ;; Connection (TCP socket lifecycle) + {:db/ident :papod.connection/id + :db/valueType :db.type/uuid + :db/cardinality :db.cardinality/one + :db/unique :db.unique/identity} + {:db/ident :papod.connection/process + :db/valueType :db.type/ref + :db/cardinality :db.cardinality/one + :db/index true} + {:db/ident :papod.connection/created-at + :db/valueType :db.type/instant + :db/cardinality :db.cardinality/one} + {:db/ident :papod.connection/finished-at + :db/valueType :db.type/instant + :db/cardinality :db.cardinality/one} + ;; Logon (session + connection junction) + {:db/ident :papod.logon/id + :db/valueType :db.type/uuid + :db/cardinality :db.cardinality/one + :db/unique :db.unique/identity} + {:db/ident :papod.logon/session + :db/valueType :db.type/ref + :db/cardinality :db.cardinality/one + :db/index true} + {:db/ident :papod.logon/connection + :db/valueType :db.type/ref + :db/cardinality :db.cardinality/one + :db/index true} + {:db/ident :papod.logon/session+connection + :db/valueType :db.type/tuple + :db/cardinality :db.cardinality/one + :db/unique :db.unique/value + :db/tupleAttrs [:papod.logon/session + :papod.logon/connection]} + ;; Process (server instance) + {:db/ident :papod.process/id + :db/valueType :db.type/uuid + :db/cardinality :db.cardinality/one + :db/unique :db.unique/identity} + {:db/ident :papod.process/pid + :db/valueType :db.type/long + :db/cardinality :db.cardinality/one} + {:db/ident :papod.process/hostname + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one} + {:db/ident :papod.process/started-at + :db/valueType :db.type/instant + :db/cardinality :db.cardinality/one} ;; Event {:db/ident :papod.event/id :db/valueType :db.type/uuid @@ -309,6 +374,23 @@ :papod.membership/channel :papod.membership/nick :papod.membership/joined-at}} + {:db/ident :papod.session/attrs + :db.entity/attrs #{:papod.session/id + :papod.session/nick + :papod.session/created-at}} + {:db/ident :papod.process/attrs + :db.entity/attrs #{:papod.process/id + :papod.process/pid + :papod.process/hostname + :papod.process/started-at}} + {:db/ident :papod.connection/attrs + :db.entity/attrs #{:papod.connection/id + :papod.connection/process + :papod.connection/created-at}} + {:db/ident :papod.logon/attrs + :db.entity/attrs #{:papod.logon/id + :papod.logon/session + :papod.logon/connection}} {:db/ident :papod.event/attrs :db.entity/attrs #{:papod.event/id :papod.event/type @@ -592,7 +674,8 @@ (ffirst (d/q '{:find [?e] :in [$ ?id] - :where [[?e :papod.channel/id ?id]]} + :where [[?e :papod.channel/id ?id] + [?e :papod.channel/type :papod.channel.type/private]]} db uuid))) (catch Exception _ nil)) @@ -600,7 +683,9 @@ (ffirst (d/q '{:find [?e] :in [$ ?name] - :where [[?e :papod.channel/name ?name]]} + :where [[?e :papod.channel/name ?name] + [?e :papod.channel/type ?t] + [(not= ?t :papod.channel.type/private)]]} db handle)))) (defn- has-access? @@ -635,33 +720,19 @@ (catch Exception _))) (defn- handle-pass - [params client] - (cond - (:registered? @client) - [(numeric-reply client "462" ":You may not reregister")] - - (empty? params) - [(numeric-reply client "461" "PASS :Not enough parameters")] - - :else - (do (swap! client assoc :pass (first params)) - []))) - -(defn- handle-network [params client components] (cond (:registered? @client) [(numeric-reply client "462" ":You may not reregister")] (empty? params) - [(numeric-reply client "461" "NETWORK :Not enough parameters")] + [(numeric-reply client "461" "PASS :Not enough parameters")] :else (let [uuid-str (first params) conn (:conn components)] (if-not conn - (do (swap! client assoc :network-id nil) - []) + [] (try (let [uuid (java.util.UUID/fromString uuid-str) db (d/db conn) @@ -686,12 +757,23 @@ (defn- authenticate! [client components] (or (:authenticated? @client) - (let [{:keys [pass user]} @client - {:keys [username]} user - conn (cracha-conn components)] - (if-not conn - true - (uuid? (cracha/login! conn username pass "irc")))))) + (not (cracha-conn components)))) + +(defn- create-session+logon! + [conn client session-id] + (when (and conn session-id (:connection-id @client)) + (let [nick (client-target client)] + @(d/transact conn + [{:db/ensure :papod.session/attrs + :db/id "new-session" + :papod.session/id session-id + :papod.session/nick nick + :papod.session/created-at (java.util.Date.)} + {:db/ensure :papod.logon/attrs + :papod.logon/id (java.util.UUID/randomUUID) + :papod.logon/session "new-session" + :papod.logon/connection [:papod.connection/id + (:connection-id @client)]}])))) (defn- maybe-register! [client components] @@ -764,7 +846,7 @@ [(str ":" +server-name+ " PONG " +server-name+ " :" (first params))])) (defconst- +sasl-mechanisms+ - "PLAIN") + "PLAIN,RESUME") (defn- handle-cap [params client components] @@ -815,11 +897,58 @@ (cracha/login! conn authcid password "sasl"))] (swap! client assoc :sasl-state nil) (if (uuid? result) - (do (swap! client assoc :authenticated? true) + (do (swap! client assoc :authenticated? true + :session-id result) + (create-session+logon! (:conn components) client result) [(numeric-reply client "900" (str nick "!" nick "@localhost " authcid " :You are now logged in as " authcid)) (numeric-reply client "903" + ":SASL authentication successful") + (str ":" +server-name+ " NOTICE " nick + " :Session ID: " result)]) + [(numeric-reply client "904" + ":SASL authentication failed")])) + (catch Exception _ + (swap! client assoc :sasl-state nil) + [(numeric-reply client "904" ":SASL authentication failed")])))) + +(defn- handle-sasl-resume + [base64-data client components] + (let [nick (client-target client) + conn (:conn components)] + (try + (let [decoded (String. + (.decode (java.util.Base64/getDecoder) ^String base64-data) + "UTF-8") + session-id (java.util.UUID/fromString (string/trim decoded)) + db (when conn (d/db conn)) + session-eid (when db + (ffirst + (d/q '{:find [?s] + :in [$ ?id] + :where [[?s :papod.session/id ?id]]} + db session-id))) + session (when session-eid (d/entity db session-eid)) + valid? (and session + (= nick (:papod.session/nick session)) + (nil? (:papod.session/finished-at session)))] + (swap! client assoc :sasl-state nil) + (if valid? + (do (swap! client assoc :authenticated? true + :session-id session-id) + ;; Create logon for resumed session + (when (:connection-id @client) + @(d/transact conn + [{:db/ensure :papod.logon/attrs + :papod.logon/id (java.util.UUID/randomUUID) + :papod.logon/session session-eid + :papod.logon/connection [:papod.connection/id + (:connection-id @client)]}])) + [(numeric-reply client "900" + (str nick "!" nick "@localhost " nick + " :You are now logged in as " nick)) + (numeric-reply client "903" ":SASL authentication successful")]) [(numeric-reply client "904" ":SASL authentication failed")])) @@ -843,18 +972,29 @@ [(numeric-reply client "906" ":SASL authentication aborted")]) (nil? (:sasl-state @client)) - (if (= "PLAIN" (some-> arg string/upper-case)) - (do (swap! client assoc :sasl-state :authenticating) - ["AUTHENTICATE +"]) - [(numeric-reply client "908" - (str +sasl-mechanisms+ " :are available SASL mechanisms")) - (numeric-reply client "904" ":SASL authentication failed")]) + (let [mechanism (some-> arg string/upper-case)] + (case mechanism + "PLAIN" + (do (swap! client assoc :sasl-state :authenticating + :sasl-mechanism "PLAIN") + ["AUTHENTICATE +"]) + "RESUME" + (do (swap! client assoc :sasl-state :authenticating + :sasl-mechanism "RESUME") + ["AUTHENTICATE +"]) + [(numeric-reply client "908" + (str +sasl-mechanisms+ " :are available SASL mechanisms")) + (numeric-reply client "904" ":SASL authentication failed")])) (= :authenticating (:sasl-state @client)) (if (or (nil? arg) (= "+" arg)) - (do (swap! client assoc :sasl-state nil) + (do (swap! client assoc :sasl-state nil :sasl-mechanism nil) [(numeric-reply client "904" ":SASL authentication failed")]) - (handle-sasl-plain arg client components))))) + (let [mechanism (:sasl-mechanism @client)] + (swap! client assoc :sasl-mechanism nil) + (case mechanism + "PLAIN" (handle-sasl-plain arg client components) + "RESUME" (handle-sasl-resume arg client components))))))) (defn- nickserv-notice [client text] @@ -909,8 +1049,11 @@ [(nickserv-notice client "Invalid password")] :unconfirmed-user [(nickserv-notice client "Nick is not confirmed")] - (do (swap! client assoc :identified? true) - [(nickserv-notice client "You are now identified")])))))) + (do (swap! client assoc :identified? true + :session-id result) + (create-session+logon! (:conn components) client result) + [(nickserv-notice client "You are now identified") + (nickserv-notice client (str "Session ID: " result))])))))) (defn- handle-nickserv [content client components] @@ -1559,7 +1702,7 @@ :papod.channel/network [:papod.network/id (:network-id @client)] :papod.channel/name handle - :papod.channel/type "public" + :papod.channel/type :papod.channel.type/public :papod.channel/description "" :papod.channel/created-at (java.util.Date.)})))) ;; UPDATE in-memory @@ -1579,15 +1722,21 @@ (let [command (string/upper-case (:command message)) params (clean-params (:params message))] (case command - "PASS" (handle-pass params client) - "NETWORK" (handle-network params client components) + "PASS" (handle-pass params client components) "NICK" (handle-nick params client components) "USER" (handle-user params client components) "PING" (handle-ping params client) "CAP" (handle-cap params client components) "AUTHENTICATE" (handle-authenticate params client components) - (if-not (:registered? @client) + (cond + (not (:registered? @client)) [(numeric-reply client "451" ":You have not registered")] + + (not (:network-id @client)) + [(numeric-reply client "451" + ":No network selected (use PASS <network-uuid>)")] + + :else (case command "PRIVMSG" (handle-privmsg params client components) "JOIN" (handle-join params client components) @@ -1624,19 +1773,38 @@ (defn- init! [db-uri cracha-state] (d/create-database db-uri) - (let [conn (d/connect db-uri)] - @(d/transact conn schema) - {:conn conn - :cracha cracha-state - :clients (atom {}) - :channels (atom {})})) + (let [conn (d/connect db-uri) + _ @(d/transact conn schema) + process-id (java.util.UUID/randomUUID) + _ @(d/transact conn + [{:db/ensure :papod.process/attrs + :papod.process/id process-id + :papod.process/pid (.pid (java.lang.ProcessHandle/current)) + :papod.process/hostname (.getHostName + (java.net.InetAddress/getLocalHost)) + :papod.process/started-at (java.util.Date.)}])] + {:conn conn + :cracha cracha-state + :process-id process-id + :clients (atom {}) + :channels (atom {})})) (defn- client-loop! [socket components] - (let [r (.getInputStream socket) - w (.getOutputStream socket) - b (make-array Byte/TYPE +buffer-size+) - client (atom {:nick nil :user nil :pass nil :registered? false :w w})] + (let [r (.getInputStream socket) + w (.getOutputStream socket) + b (make-array Byte/TYPE +buffer-size+) + conn-id (java.util.UUID/randomUUID) + client (atom {:nick nil :user nil :pass nil :registered? false + :w w :connection-id conn-id})] + ;; Record connection start + (when-let [conn (:conn components)] + @(d/transact conn + [{:db/ensure :papod.connection/attrs + :papod.connection/id conn-id + :papod.connection/process [:papod.process/id + (:process-id components)] + :papod.connection/created-at (java.util.Date.)}])) (try (loop [acc ""] (let [n (.read r b)] @@ -1646,6 +1814,11 @@ client components))))) (finally + ;; Record connection end + (when-let [conn (:conn components)] + @(d/transact conn + [[:db/add [:papod.connection/id conn-id] + :papod.connection/finished-at (java.util.Date.)]])) (when-let [nick (:nick @client)] (when (:clients components) (swap! (:clients components) dissoc nick)) diff --git a/tests/unit.clj b/tests/unit.clj index af13931..8e8eb0a 100644 --- a/tests/unit.clj +++ b/tests/unit.clj @@ -91,6 +91,45 @@ (defn client [] (atom {:nick nil :user nil :pass nil :registered? false})) (def no-conn {}) +(defn test-components + [] + (let [fiinha-state (fiinha/initdb! + (str "datomic:mem://fiinha-" (java.util.UUID/randomUUID))) + cracha-state (cracha/init! + (str "datomic:mem://cracha-" (java.util.UUID/randomUUID)) + fiinha-state) + papod-uri (str "datomic:mem://papod-" (java.util.UUID/randomUUID)) + _ (d/create-database papod-uri) + conn (d/connect papod-uri)] + @(d/transact conn @#'papod/schema) + (let [proc-id (java.util.UUID/randomUUID)] + @(d/transact conn + [{:db/ensure :papod.process/attrs + :papod.process/id proc-id + :papod.process/pid 0 + :papod.process/hostname "test" + :papod.process/started-at (java.util.Date.)}]) + {:conn conn :cracha cracha-state :process-id proc-id + :clients (atom {}) :channels (atom {})}))) + +(defn test-network! + [conn] + (let [net-id (java.util.UUID/randomUUID)] + @(d/transact conn + [{:db/ensure :papod.network/attrs + :papod.network/id net-id + :papod.network/name (str "test-" net-id) + :papod.network/description "" + :papod.network/type "public" + :papod.network/created-at (java.util.Date.)}]) + net-id)) + +(defn test-components-with-network + [] + (let [components (test-components) + net-id (test-network! (:conn components))] + (assoc components :test-network-id net-id))) + (deftest test_clean-params (testing "removes leading empty strings" (is (= (clean-params ["" "a" "b"]) ["a" "b"])) @@ -98,12 +137,21 @@ (is (= (clean-params []) [])))) (deftest test_replies-for! - (testing "PASS stores password" - (let [c (client)] - (is (= (replies-for! {:command "PASS" :params ["" "secret"]} - c no-conn) + (testing "PASS selects network" + (let [{:keys [test-network-id] :as components} (test-components-with-network) + c (client)] + (is (= (replies-for! {:command "PASS" + :params ["" (str test-network-id)]} + c components) [])) - (is (= (:pass @c) "secret")))) + (is (= (:network-id @c) test-network-id)))) + (testing "PASS with invalid UUID" + (let [components (test-components) + c (client)] + (is (string/includes? + (first (replies-for! {:command "PASS" :params ["" "not-a-uuid"]} + c components)) + "403")))) (testing "PASS with no params" (let [c (client)] (is (string/includes? @@ -180,38 +228,6 @@ c no-conn)) "409"))))) -(defn test-components - [] - (let [fiinha-state (fiinha/initdb! - (str "datomic:mem://fiinha-" (java.util.UUID/randomUUID))) - cracha-state (cracha/init! - (str "datomic:mem://cracha-" (java.util.UUID/randomUUID)) - fiinha-state) - papod-uri (str "datomic:mem://papod-" (java.util.UUID/randomUUID)) - _ (d/create-database papod-uri) - conn (d/connect papod-uri)] - @(d/transact conn @#'papod/schema) - {:conn conn :cracha cracha-state - :clients (atom {}) :channels (atom {})})) - -(defn test-network! - [conn] - (let [net-id (java.util.UUID/randomUUID)] - @(d/transact conn - [{:db/ensure :papod.network/attrs - :papod.network/id net-id - :papod.network/name (str "test-" net-id) - :papod.network/description "" - :papod.network/type "public" - :papod.network/created-at (java.util.Date.)}]) - net-id)) - -(defn test-components-with-network - [] - (let [components (test-components) - net-id (test-network! (:conn components))] - (assoc components :test-network-id net-id))) - (defn registered-client ([nick w] (atom {:nick nick :user {:username nick} :pass nil :registered? true :w w})) @@ -265,7 +281,7 @@ [{:papod.channel/id (java.util.UUID/randomUUID) :papod.channel/network [:papod.network/id test-network-id] :papod.channel/name "#test" - :papod.channel/type "public" + :papod.channel/type :papod.channel.type/public :papod.channel/description "" :papod.channel/created-at (java.util.Date.)}]) (handle-privmsg ["#test" ":hi everyone"] sender components) @@ -437,9 +453,10 @@ ;; Send credentials (let [creds (b64 "\u0000alice\u0000mypass") replies (handle-authenticate [creds] c2 components)] - (is (= 2 (count replies))) + (is (= 3 (count replies))) (is (string/includes? (first replies) "900")) (is (string/includes? (second replies) "903")) + (is (string/includes? (nth replies 2) "Session ID")) (is (:authenticated? @c2))) ;; CAP END completes registration (let [replies (handle-cap ["END"] c2 components)] @@ -688,7 +705,7 @@ :db/id "new-channel" :papod.channel/id chan-id :papod.channel/network [:papod.network/id test-network-id] - :papod.channel/type "private" + :papod.channel/type :papod.channel.type/private :papod.channel/label "secret-project" :papod.channel/description "" :papod.channel/created-at (java.util.Date.)} @@ -714,7 +731,7 @@ [{:db/ensure :papod.channel/attrs :papod.channel/id (java.util.UUID/randomUUID) :papod.channel/network [:papod.network/id test-network-id] - :papod.channel/type "private" + :papod.channel/type :papod.channel.type/private :papod.channel/label "bob-firing-wg" :papod.channel/description "" :papod.channel/created-at (java.util.Date.)}])] @@ -811,6 +828,153 @@ :papod.network/id (java.util.UUID/randomUUID) :papod.network/name "incomplete"}])))))) +(deftest test_session-tracking + (testing "SASL auth creates session + logon" + (let [{:keys [test-network-id] :as components} (test-components-with-network) + out (java.io.ByteArrayOutputStream.) + c (client)] + ;; Register user in cracha first + (swap! c assoc :nick "alice" :registered? true :w out + :network-id test-network-id) + (handle-privmsg ["NickServ" ":REGISTER mypass"] c components) + ;; Fresh connection with connection-id (simulating client-loop!) + (let [conn-id (java.util.UUID/randomUUID) + conn (:conn components) + _ @(d/transact conn + [{:db/ensure :papod.connection/attrs + :papod.connection/id conn-id + :papod.connection/process [:papod.process/id + (:process-id components)] + :papod.connection/created-at (java.util.Date.)}]) + c2 (atom {:nick "alice" :caps #{"sasl"} + :w out :connection-id conn-id})] + (handle-authenticate ["PLAIN"] c2 components) + (let [creds (b64 "\u0000alice\u0000mypass") + replies (handle-authenticate [creds] c2 components)] + (is (string/includes? (first replies) "900")) + (is (some? (:session-id @c2))) + ;; Verify session in DB + (let [db (d/db conn) + sessions (d/q '{:find [?nick] + :where [[?s :papod.session/nick ?nick]]} + db)] + (is (= #{["alice"]} sessions))) + ;; Verify logon in DB + (let [db (d/db conn) + logons (d/q '{:find [?sid ?cid] + :where [[?l :papod.logon/session ?s] + [?l :papod.logon/connection ?c] + [?s :papod.session/id ?sid] + [?c :papod.connection/id ?cid]]} + db)] + (is (= 1 (count logons))) + (is (= conn-id (second (first logons))))))))) + (testing "NickServ IDENTIFY creates session + logon" + (let [{:keys [test-network-id] :as components} (test-components-with-network) + out (java.io.ByteArrayOutputStream.) + conn (:conn components) + conn-id (java.util.UUID/randomUUID) + _ @(d/transact conn + [{:db/ensure :papod.connection/attrs + :papod.connection/id conn-id + :papod.connection/process [:papod.process/id + (:process-id components)] + :papod.connection/created-at (java.util.Date.)}]) + c (registered-client "bob" out test-network-id)] + (swap! c assoc :connection-id conn-id) + ;; Register user + (handle-privmsg ["NickServ" ":REGISTER secret"] c components) + ;; Identify + (let [replies (handle-privmsg ["NickServ" ":IDENTIFY secret"] + c components)] + (is (string/includes? (first replies) "now identified")) + (is (some? (:session-id @c))) + ;; Session + logon in DB + (let [db (d/db conn) + logons (d/q '{:find [?nick ?cid] + :where [[?l :papod.logon/session ?s] + [?l :papod.logon/connection ?c] + [?s :papod.session/nick ?nick] + [?c :papod.connection/id ?cid]]} + db)] + (is (= 1 (count logons))) + (is (= ["bob" conn-id] (vec (first logons))))))) + (testing "SASL RESUME resumes existing session" + (let [{:keys [test-network-id] :as components} (test-components-with-network) + out (java.io.ByteArrayOutputStream.) + conn (:conn components) + ;; First: register + auth to create a session + conn-id1 (java.util.UUID/randomUUID) + _ @(d/transact conn + [{:db/ensure :papod.connection/attrs + :papod.connection/id conn-id1 + :papod.connection/process [:papod.process/id + (:process-id components)] + :papod.connection/created-at (java.util.Date.)}]) + c1 (atom {:nick "alice" :registered? true :w out + :caps #{"sasl"} :connection-id conn-id1 + :network-id test-network-id})] + ;; Register user in cracha + (handle-privmsg ["NickServ" ":REGISTER mypass"] c1 components) + ;; Auth via SASL PLAIN to get session + (handle-authenticate ["PLAIN"] c1 components) + (handle-authenticate [(b64 "\u0000alice\u0000mypass")] c1 components) + (let [session-id (:session-id @c1)] + (is (some? session-id)) + ;; Now simulate reconnect: new connection, RESUME with session ID + (let [conn-id2 (java.util.UUID/randomUUID) + _ @(d/transact conn + [{:db/ensure :papod.connection/attrs + :papod.connection/id conn-id2 + :papod.connection/process [:papod.process/id + (:process-id components)] + :papod.connection/created-at (java.util.Date.)}]) + c2 (atom {:nick "alice" :w out + :caps #{"sasl"} :connection-id conn-id2})] + (handle-authenticate ["RESUME"] c2 components) + (let [replies (handle-authenticate + [(b64 (str session-id))] c2 components)] + (is (string/includes? (first replies) "900")) + (is (:authenticated? @c2)) + (is (= session-id (:session-id @c2))) + ;; Two logons for the same session (two connections) + (let [db (d/db conn) + logons (d/q '{:find [?cid] + :in [$ ?sid] + :where [[?l :papod.logon/session ?s] + [?l :papod.logon/connection ?c] + [?s :papod.session/id ?sid] + [?c :papod.connection/id ?cid]]} + db session-id)] + (is (= 2 (count logons))) + (is (= #{[conn-id1] [conn-id2]} logons)))))))) + (testing "SASL RESUME fails with wrong nick" + (let [components (test-components-with-network) + out (java.io.ByteArrayOutputStream.) + conn (:conn components) + conn-id (java.util.UUID/randomUUID) + _ @(d/transact conn + [{:db/ensure :papod.connection/attrs + :papod.connection/id conn-id + :papod.connection/process [:papod.process/id + (:process-id components)] + :papod.connection/created-at (java.util.Date.)}]) + ;; Create a session for "alice" + session-id (java.util.UUID/randomUUID) + _ @(d/transact conn + [{:db/ensure :papod.session/attrs + :papod.session/id session-id + :papod.session/nick "alice" + :papod.session/created-at (java.util.Date.)}]) + ;; Try to resume as "bob" + c (atom {:nick "bob" :w out :caps #{"sasl"} + :connection-id conn-id})] + (handle-authenticate ["RESUME"] c components) + (let [replies (handle-authenticate + [(b64 (str session-id))] c components)] + (is (string/includes? (first replies) "904")) + (is (not (:authenticated? @c)))))))) + (defn -main [& _args] (binding [*out* *err*] |
