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.
This commit is contained in:
Andrey Antukh 2021-04-25 19:43:09 +02:00
parent ce072937e4
commit 5d2f4bac76
3 changed files with 46 additions and 48 deletions

View file

@ -116,7 +116,6 @@
(s/def ::oidc-roles-attr ::us/keyword) (s/def ::oidc-roles-attr ::us/keyword)
(s/def ::host ::us/string) (s/def ::host ::us/string)
(s/def ::http-server-port ::us/integer) (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-idle-max-age ::dt/duration)
(s/def ::http-session-updater-batch-max-age ::dt/duration) (s/def ::http-session-updater-batch-max-age ::dt/duration)
(s/def ::http-session-updater-batch-max-size ::us/integer) (s/def ::http-session-updater-batch-max-size ::us/integer)

View file

@ -21,86 +21,85 @@
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[integrant.core :as ig])) [integrant.core :as ig]))
;; A default cookie name for storing the session. We don't allow
;; configure it.
(def cookie-name "session-id")
;; --- IMPL ;; --- IMPL
(defn- next-session-id (defn- create-session
([] (next-session-id 96)) [{:keys [conn tokens] :as cfg} {:keys [profile-id headers] :as request}]
([n] (let [token (tokens :generate {:iss "authentication"
(-> (bn/random-nonce n) :iat (dt/now)
(bc/bytes->b64u) :uid profile-id})
(bc/bytes->str)))) params {:user-agent (get headers "user-agent")
(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 :profile-id profile-id
:user-agent user-agent}) :id token}]
id)) (db/insert! conn :http-session params)))
(defn- delete (defn- delete-session
[{:keys [conn cookie-name] :as cfg} {:keys [cookies] :as request}] [{:keys [conn] :as cfg} {:keys [cookies] :as request}]
(when-let [token (get-in cookies [cookie-name :value])] (when-let [token (get-in cookies [cookie-name :value])]
(db/delete! conn :http-session {:id token})) (db/delete! conn :http-session {:id token}))
nil) nil)
(defn- retrieve (defn- retrieve-session
[{:keys [conn] :as cfg} token] [{:keys [conn] :as cfg} id]
(when token (when id
(db/exec-one! conn ["select id, profile_id from http_session where id = ?" token]))) (db/exec-one! conn ["select id, profile_id from http_session where id = ?" id])))
(defn- retrieve-from-request (defn- retrieve-from-request
[{:keys [cookie-name] :as cfg} {:keys [cookies] :as request}] [cfg {:keys [cookies] :as request}]
(->> (get-in cookies [cookie-name :value]) (->> (get-in cookies [cookie-name :value])
(retrieve cfg))) (retrieve-session cfg)))
(defn- cookies (defn- add-cookies
[{:keys [cookie-name] :as cfg} vals] [response {:keys [id] :as session}]
{cookie-name (merge vals {:path "/" :http-only true})}) (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 (defn- middleware
[cfg handler] [cfg handler]
(fn [request] (fn [request]
(if-let [{:keys [id profile-id] :as session} (retrieve-from-request cfg request)] (if-let [{:keys [id profile-id] :as session} (retrieve-from-request cfg request)]
(let [events-ch (::events-ch cfg)] (do
(a/>!! events-ch id) (a/>!! (::events-ch cfg) id)
(l/update-thread-context! {:profile-id profile-id}) (l/update-thread-context! {:profile-id profile-id})
(handler (assoc request :profile-id profile-id))) (handler (assoc request :profile-id profile-id)))
(handler request)))) (handler request))))
;; --- STATE INIT: SESSION ;; --- STATE INIT: SESSION
(s/def ::cookie-name ::cfg/http-session-cookie-name)
(defmethod ig/pre-init-spec ::session [_] (defmethod ig/pre-init-spec ::session [_]
(s/keys :req-un [::db/pool] (s/keys :req-un [::db/pool]))
:opt-un [::cookie-name]))
(defmethod ig/prep-key ::session (defmethod ig/prep-key ::session
[_ cfg] [_ cfg]
(merge {:cookie-name "auth-token" (d/merge {:buffer-size 64}
:buffer-size 64}
(d/without-nils cfg))) (d/without-nils cfg)))
(defmethod ig/init-key ::session (defmethod ig/init-key ::session
[_ {:keys [pool] :as cfg}] [_ {:keys [pool] :as cfg}]
(let [events (a/chan (a/dropping-buffer (:buffer-size cfg))) (let [events (a/chan (a/dropping-buffer (:buffer-size cfg)))
cfg (assoc cfg cfg (-> cfg
:conn pool (assoc :conn pool)
::events-ch events)] (assoc ::events-ch events))]
(-> cfg (-> cfg
(assoc :middleware #(middleware cfg %)) (assoc :middleware #(middleware cfg %))
(assoc :create (fn [profile-id] (assoc :create (fn [profile-id]
(fn [request response] (fn [request response]
(let [uagent (get-in request [:headers "user-agent"]) (let [request (assoc request :profile-id profile-id)
value (create cfg {:profile-id profile-id :user-agent uagent})] session (create-session cfg request)]
(assoc response :cookies (cookies cfg {:value value})))))) (add-cookies response session)))))
(assoc :delete (fn [request response] (assoc :delete (fn [request response]
(delete cfg request) (delete-session cfg request)
(assoc response (-> response
:status 204 (assoc :status 204)
:body "" (assoc :body "")
:cookies (cookies cfg {:value "" :max-age -1}))))))) (clear-cookies)))))))
(defmethod ig/halt-key! ::session (defmethod ig/halt-key! ::session
[_ data] [_ data]

View file

@ -61,7 +61,7 @@
:app.http.session/session :app.http.session/session
{:pool (ig/ref :app.db/pool) {:pool (ig/ref :app.db/pool)
:cookie-name (cf/get :http-session-cookie-name)} :tokens (ig/ref :app.tokens/tokens)}
:app.http.session/gc-task :app.http.session/gc-task
{:pool (ig/ref :app.db/pool) {:pool (ig/ref :app.db/pool)