Add mvp access-token support

This commit is contained in:
Alejandro Alonso 2023-05-03 07:27:09 +02:00 committed by Andrey Antukh
parent b90aef4e1d
commit 890583a13a
23 changed files with 907 additions and 100 deletions

View file

@ -26,12 +26,18 @@
(when token
(tokens/verify props {:token token :iss "access-token"})))
(defn- get-token-perms
(def sql:get-token-data
"SELECT perms, profile_id, expires_at
FROM access_token
WHERE id = ?
AND (expires_at IS NULL
OR (expires_at > now()));")
(defn- get-token-data
[pool token-id]
(when-not (db/read-only? pool)
(when-let [token (db/get* pool :access-token {:id token-id} {:columns [:perms]})]
(some-> (:perms token)
(db/decode-pgarray #{})))))
(some-> (db/exec-one! pool [sql:get-token-data token-id])
(update :perms db/decode-pgarray #{}))))
(defn- wrap-soft-auth
"Soft Authentication, will be executed synchronously on the undertow
@ -56,10 +62,14 @@
"Authorization middleware, will be executed synchronously on vthread."
[handler {:keys [::db/pool]}]
(fn [request]
(let [perms (some->> (::id request) (get-token-perms pool))]
(let [{:keys [perms profile-id expires-at]} (some->> (::id request) (get-token-data pool))]
(handler (cond-> request
(some? perms)
(assoc ::perms perms))))))
(assoc ::perms perms)
(some? profile-id)
(assoc ::profile-id profile-id)
(some? expires-at)
(assoc ::expires-at expires-at))))))
(def soft-auth
{:name ::soft-auth

View file

@ -17,6 +17,7 @@
[app.config :as cf]
[app.db :as db]
[app.http :as-alias http]
[app.http.access-token :as-alias actoken]
[app.http.client :as http.client]
[app.loggers.audit.tasks :as-alias tasks]
[app.loggers.webhooks :as-alias webhooks]
@ -152,7 +153,11 @@
(dissoc :profile-id)
(dissoc :type)))
(clean-props))]
(clean-props))
token-id (::actoken/id request)
context (d/without-nils
{:access-token-id (some-> token-id str)})]
{::type (or (::type resultm)
(::rpc/type cfg))
@ -161,6 +166,7 @@
::profile-id profile-id
::ip-addr (some-> request parse-client-ip)
::props props
::context context
;; NOTE: for batch-key lookup we need the params as-is
;; because the rpc api does not need to know the
@ -188,6 +194,7 @@
:type (::type event)
:profile-id (::profile-id event)
:ip-addr (::ip-addr event)
:context (::context event)
:props (::props event)}]
(when (contains? cf/flags :audit-log)
@ -201,6 +208,7 @@
(db/insert! conn-or-pool :audit-log
(-> params
(update :props db/tjson)
(update :context db/tjson)
(update :ip-addr db/inet)
(assoc :created-at now)
(assoc :tracked-at now)

View file

@ -315,7 +315,8 @@
{:name "0101-mod-server-error-report-table"
:fn (mg/resource "app/migrations/sql/0101-mod-server-error-report-table.sql")}
])
{:name "0102-mod-access-token-table"
:fn (mg/resource "app/migrations/sql/0102-mod-access-token-table.sql")}])
(defn apply-migrations!
[pool name migrations]

View file

@ -0,0 +1,2 @@
ALTER TABLE access_token
ADD COLUMN expires_at timestamptz NULL;

View file

@ -112,22 +112,6 @@
:hint "authentication required for this endpoint")
(f cfg params)))))
(defn- wrap-access-token
"Wraps service method with access token validation."
[_ f {:keys [::sv/name] :as mdata}]
(if (contains? cf/flags :access-tokens)
(fn [cfg params]
(let [request (::http/request params)]
(if (contains? request ::actoken/id)
(let [perms (::actoken/perms request #{})]
(if (contains? perms name)
(f cfg params)
(ex/raise :type :authorization
:code :operation-not-allowed
:allowed perms)))
(f cfg params))))
f))
(defn- wrap-audit
[_ f mdata]
(if (or (contains? cf/flags :webhooks)
@ -157,8 +141,7 @@
(rlimit/wrap cfg $ mdata)
(wrap-audit cfg $ mdata)
(wrap-spec-conform cfg $ mdata)
(wrap-authentication cfg $ mdata)
(wrap-access-token cfg $ mdata)))
(wrap-authentication cfg $ mdata)))
(defn- wrap
[cfg f mdata]

View file

@ -19,18 +19,19 @@
[clojure.spec.alpha :as s]))
(defn- decode-row
[{:keys [perms] :as row}]
(cond-> row
(db/pgarray? perms "text")
(assoc :perms (db/decode-pgarray perms #{}))))
[row]
(dissoc row :perms))
(defn- create-access-token
[{:keys [::conn ::main/props]} profile-id name perms]
(defn create-access-token
[{:keys [::db/conn ::main/props]} profile-id name expiration]
(let [created-at (dt/now)
token-id (uuid/next)
token (tokens/generate props {:iss "access-token"
:tid token-id
:iat created-at})]
:iat created-at})
expires-at (some-> expiration dt/in-future)]
(db/insert! conn :access-token
{:id token-id
:name name
@ -38,33 +39,36 @@
:profile-id profile-id
:created-at created-at
:updated-at created-at
:perms (db/create-array conn "text" perms)})))
:expires-at expires-at
:perms (db/create-array conn "text" [])})))
(defn repl-create-access-token
[{:keys [::db/pool] :as system} profile-id name perms]
[{:keys [::db/pool] :as system} profile-id name expiration]
(db/with-atomic [conn pool]
(let [props (:app.setup/props system)]
(create-access-token {::conn conn ::main/props props}
(create-access-token {::db/conn conn ::main/props props}
profile-id
name
perms))))
expiration))))
(s/def ::name ::us/not-empty-string)
(s/def ::perms ::us/set-of-strings)
(s/def ::expiration ::dt/duration)
(s/def ::create-access-token
(s/keys :req [::rpc/profile-id]
:req-un [::name ::perms]))
:req-un [::name]
:opt-un [::expiration]))
(sv/defmethod ::create-access-token
{::doc/added "1.18"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id name perms]}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id name expiration]}]
(db/with-atomic [conn pool]
(let [cfg (assoc cfg ::conn conn)]
(let [cfg (assoc cfg ::db/conn conn)]
(quotes/check-quote! conn
{::quotes/id ::quotes/access-tokens-per-profile
::quotes/profile-id profile-id})
(-> (create-access-token cfg profile-id name perms)
(-> (create-access-token cfg profile-id name expiration)
(decode-row)))))
(s/def ::delete-access-token
@ -83,5 +87,8 @@
(sv/defmethod ::get-access-tokens
{::doc/added "1.18"}
[{:keys [::db/pool]} {:keys [::rpc/profile-id]}]
(->> (db/query pool :access-token {:profile-id profile-id})
(->> (db/query pool :access-token
{:profile-id profile-id}
{:order-by [[:expires-at :asc] [:created-at :asc]]
:columns [:id :name :perms :created-at :updated-at :expires-at]})
(mapv decode-row)))