From 5d2f4bac76918c1948da2eef7d49310a71d72b4e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sun, 25 Apr 2021 19:43:09 +0200 Subject: [PATCH] :sparkles: Replace random session tokens with JWE tokens. We still maintain the http session state on the database for to prevent replay attacks to the main application. But internally, on less critical parts of the infraestructure, it usefull have access to the identified user without hit the main database for that information. --- backend/src/app/config.clj | 1 - backend/src/app/http/session.clj | 89 ++++++++++++++++---------------- backend/src/app/main.clj | 4 +- 3 files changed, 46 insertions(+), 48 deletions(-) diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 260e00c5f5..0e9d4ac5dd 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -116,7 +116,6 @@ (s/def ::oidc-roles-attr ::us/keyword) (s/def ::host ::us/string) (s/def ::http-server-port ::us/integer) -(s/def ::http-session-cookie-name ::us/string) (s/def ::http-session-idle-max-age ::dt/duration) (s/def ::http-session-updater-batch-max-age ::dt/duration) (s/def ::http-session-updater-batch-max-size ::us/integer) diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index 2f071089d8..c03f0995b4 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -21,86 +21,85 @@ [clojure.spec.alpha :as s] [integrant.core :as ig])) +;; A default cookie name for storing the session. We don't allow +;; configure it. +(def cookie-name "session-id") + ;; --- IMPL -(defn- next-session-id - ([] (next-session-id 96)) - ([n] - (-> (bn/random-nonce n) - (bc/bytes->b64u) - (bc/bytes->str)))) +(defn- create-session + [{:keys [conn tokens] :as cfg} {:keys [profile-id headers] :as request}] + (let [token (tokens :generate {:iss "authentication" + :iat (dt/now) + :uid profile-id}) + params {:user-agent (get headers "user-agent") + :profile-id profile-id + :id token}] + (db/insert! conn :http-session params))) -(defn- create - [{:keys [conn] :as cfg} {:keys [profile-id user-agent]}] - (let [id (next-session-id)] - (db/insert! conn :http-session {:id id - :profile-id profile-id - :user-agent user-agent}) - id)) - -(defn- delete - [{:keys [conn cookie-name] :as cfg} {:keys [cookies] :as request}] +(defn- delete-session + [{:keys [conn] :as cfg} {:keys [cookies] :as request}] (when-let [token (get-in cookies [cookie-name :value])] (db/delete! conn :http-session {:id token})) nil) -(defn- retrieve - [{:keys [conn] :as cfg} token] - (when token - (db/exec-one! conn ["select id, profile_id from http_session where id = ?" token]))) +(defn- retrieve-session + [{:keys [conn] :as cfg} id] + (when id + (db/exec-one! conn ["select id, profile_id from http_session where id = ?" id]))) (defn- retrieve-from-request - [{:keys [cookie-name] :as cfg} {:keys [cookies] :as request}] + [cfg {:keys [cookies] :as request}] (->> (get-in cookies [cookie-name :value]) - (retrieve cfg))) + (retrieve-session cfg))) -(defn- cookies - [{:keys [cookie-name] :as cfg} vals] - {cookie-name (merge vals {:path "/" :http-only true})}) +(defn- add-cookies + [response {:keys [id] :as session}] + (assoc response :cookies {"session-id" {:path "/" :http-only true :value id}})) + +(defn- clear-cookies + [response] + (assoc response :cookies {"session-id" {:value "" :max-age -1}})) (defn- middleware [cfg handler] (fn [request] (if-let [{:keys [id profile-id] :as session} (retrieve-from-request cfg request)] - (let [events-ch (::events-ch cfg)] - (a/>!! events-ch id) + (do + (a/>!! (::events-ch cfg) id) (l/update-thread-context! {:profile-id profile-id}) (handler (assoc request :profile-id profile-id))) (handler request)))) ;; --- STATE INIT: SESSION -(s/def ::cookie-name ::cfg/http-session-cookie-name) - (defmethod ig/pre-init-spec ::session [_] - (s/keys :req-un [::db/pool] - :opt-un [::cookie-name])) + (s/keys :req-un [::db/pool])) (defmethod ig/prep-key ::session [_ cfg] - (merge {:cookie-name "auth-token" - :buffer-size 64} - (d/without-nils cfg))) + (d/merge {:buffer-size 64} + (d/without-nils cfg))) (defmethod ig/init-key ::session [_ {:keys [pool] :as cfg}] (let [events (a/chan (a/dropping-buffer (:buffer-size cfg))) - cfg (assoc cfg - :conn pool - ::events-ch events)] + cfg (-> cfg + (assoc :conn pool) + (assoc ::events-ch events))] (-> cfg (assoc :middleware #(middleware cfg %)) (assoc :create (fn [profile-id] (fn [request response] - (let [uagent (get-in request [:headers "user-agent"]) - value (create cfg {:profile-id profile-id :user-agent uagent})] - (assoc response :cookies (cookies cfg {:value value})))))) + (let [request (assoc request :profile-id profile-id) + session (create-session cfg request)] + (add-cookies response session))))) (assoc :delete (fn [request response] - (delete cfg request) - (assoc response - :status 204 - :body "" - :cookies (cookies cfg {:value "" :max-age -1}))))))) + (delete-session cfg request) + (-> response + (assoc :status 204) + (assoc :body "") + (clear-cookies))))))) (defmethod ig/halt-key! ::session [_ data] diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index e40946e140..ab8c96ebbe 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -60,8 +60,8 @@ :storage (ig/ref :app.storage/storage)} :app.http.session/session - {:pool (ig/ref :app.db/pool) - :cookie-name (cf/get :http-session-cookie-name)} + {:pool (ig/ref :app.db/pool) + :tokens (ig/ref :app.tokens/tokens)} :app.http.session/gc-task {:pool (ig/ref :app.db/pool)