0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-08 16:00:19 -05:00

♻️ Refactor token generation API

This commit is contained in:
Andrey Antukh 2022-08-24 13:44:33 +02:00
parent 44f4d9c50c
commit d6d9d25fce
16 changed files with 167 additions and 183 deletions

View file

@ -15,9 +15,11 @@
[app.common.uri :as u]
[app.config :as cf]
[app.db :as db]
[app.http.client :as http]
[app.http.middleware :as hmw]
[app.loggers.audit :as audit]
[app.rpc.queries.profile :as profile]
[app.tokens :as tokens]
[app.util.json :as json]
[app.util.time :as dt]
[app.worker :as wrk]
@ -47,7 +49,7 @@
(defn- discover-oidc-config
[{:keys [http-client]} {:keys [base-uri] :as opts}]
(let [discovery-uri (u/join base-uri ".well-known/openid-configuration")
response (ex/try (http-client {:method :get :uri (str discovery-uri)} {:sync? true}))]
response (ex/try (http/req! http-client {:method :get :uri (str discovery-uri)} {:sync? true}))]
(cond
(ex/exception? response)
(do
@ -158,10 +160,10 @@
(defn- retrieve-github-email
[{:keys [http-client]} tdata info]
(or (some-> info :email p/resolved)
(-> (http-client {:uri "https://api.github.com/user/emails"
:headers {"Authorization" (dm/str (:type tdata) " " (:token tdata))}
:timeout 6000
:method :get})
(-> (http/req! http-client {:uri "https://api.github.com/user/emails"
:headers {"Authorization" (dm/str (:type tdata) " " (:token tdata))}
:timeout 6000
:method :get})
(p/then (fn [{:keys [status body] :as response}]
(when-not (s/int-in-range? 200 300 status)
(ex/raise :type :internal
@ -278,7 +280,7 @@
:uri (:token-uri provider)
:body (u/map->query-string params)}]
(p/then
(http-client req)
(http/req! http-client req)
(fn [{:keys [status body] :as res}]
(if (= status 200)
(let [data (json/read body)]
@ -292,11 +294,10 @@
(defn- retrieve-user-info
[{:keys [provider http-client] :as cfg} tdata]
(letfn [(retrieve []
(http-client {:uri (:user-uri provider)
:headers {"Authorization" (str (:type tdata) " " (:token tdata))}
:timeout 6000
:method :get}))
(http/req! http-client {:uri (:user-uri provider)
:headers {"Authorization" (str (:type tdata) " " (:token tdata))}
:timeout 6000
:method :get}))
(validate-response [response]
(when-not (s/int-in-range? 200 300 (:status response))
(ex/raise :type :internal
@ -353,7 +354,7 @@
::props]))
(defn retrieve-info
[{:keys [tokens provider] :as cfg} {:keys [params] :as request}]
[{:keys [sprops provider] :as cfg} {:keys [params] :as request}]
(letfn [(validate-oidc [info]
;; If the provider is OIDC, we can proceed to check
;; roles if they are defined.
@ -392,7 +393,7 @@
(let [state (get params :state)
code (get params :code)
state (tokens :verify {:token state :iss :oauth})]
state (tokens/verify sprops {:token state :iss :oauth})]
(-> (p/resolved code)
(p/then #(retrieve-access-token cfg %))
(p/then #(retrieve-user-info cfg %))
@ -420,13 +421,13 @@
(redirect-response uri)))
(defn- generate-redirect
[{:keys [tokens session audit] :as cfg} request info profile]
[{:keys [sprops session audit] :as cfg} request info profile]
(if profile
(let [sxf ((:create session) (:id profile))
token (or (:invitation-token info)
(tokens :generate {:iss :auth
:exp (dt/in-future "15m")
:profile-id (:id profile)}))
(tokens/generate sprops {:iss :auth
:exp (dt/in-future "15m")
:profile-id (:id profile)}))
params {:token token}
uri (-> (u/uri (:public-uri cfg))
@ -448,7 +449,7 @@
:iss :prepared-register
:is-active true
:exp (dt/in-future {:hours 48}))
token (tokens :generate info)
token (tokens/generate sprops info)
params (d/without-nils
{:token token
:fullname (:fullname info)})
@ -458,13 +459,13 @@
(redirect-response uri))))
(defn- auth-handler
[{:keys [tokens] :as cfg} {:keys [params] :as request}]
[{:keys [sprops] :as cfg} {:keys [params] :as request}]
(let [props (audit/extract-utm-params params)
state (tokens :generate
{:iss :oauth
:invitation-token (:invitation-token params)
:props props
:exp (dt/in-future "15m")})
state (tokens/generate sprops
{:iss :oauth
:invitation-token (:invitation-token params)
:props props
:exp (dt/in-future "15m")})
uri (build-auth-uri cfg state)]
(yrs/response 200 {:redirect-uri uri})))
@ -496,16 +497,16 @@
:hint "provider not configured"))))))})
(s/def ::public-uri ::us/not-empty-string)
(s/def ::http-client fn?)
(s/def ::http-client ::http/client)
(s/def ::session map?)
(s/def ::tokens fn?)
(s/def ::sprops map?)
(s/def ::providers map?)
(defmethod ig/pre-init-spec ::routes
[_]
(s/keys :req-un [::public-uri
::session
::tokens
::sprops
::http-client
::providers
::db/pool

View file

@ -114,18 +114,18 @@
;; HTTP ROUTER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::oauth map?)
(s/def ::storage map?)
(s/def ::assets map?)
(s/def ::feedback fn?)
(s/def ::ws fn?)
(s/def ::audit-handler fn?)
(s/def ::awsns-handler fn?)
(s/def ::session map?)
(s/def ::rpc-routes (s/nilable vector?))
(s/def ::debug-routes (s/nilable vector?))
(s/def ::oidc-routes (s/nilable vector?))
(s/def ::doc-routes (s/nilable vector?))
(s/def ::feedback fn?)
(s/def ::oauth map?)
(s/def ::oidc-routes (s/nilable vector?))
(s/def ::rpc-routes (s/nilable vector?))
(s/def ::session map?)
(s/def ::storage map?)
(s/def ::ws fn?)
(defmethod ig/pre-init-spec ::router [_]
(s/keys :req-un [::mtx/metrics

View file

@ -11,6 +11,8 @@
[app.common.logging :as l]
[app.db :as db]
[app.db.sql :as sql]
[app.http.client :as http]
[app.tokens :as tokens]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]
@ -24,10 +26,11 @@
(declare parse-notification)
(declare process-report)
(s/def ::http-client fn?)
(s/def ::http-client ::http/client)
(s/def ::sprops map?)
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::db/pool ::http-client]))
(s/keys :req-un [::db/pool ::http-client ::sprops]))
(defmethod ig/init-key ::handler
[_ {:keys [executor] :as cfg}]
@ -46,7 +49,7 @@
(let [surl (get body "SubscribeURL")
stopic (get body "TopicArn")]
(l/info :action "subscription received" :topic stopic :url surl)
(http-client {:uri surl :method :post :timeout 10000} {:sync? true}))
(http/req! http-client {:uri surl :method :post :timeout 10000} {:sync? true}))
(= mtype "Notification")
(when-let [message (parse-json (get body "Message"))]
@ -97,10 +100,10 @@
(get mail "headers")))
(defn- extract-identity
[{:keys [tokens] :as cfg} headers]
[{:keys [sprops]} headers]
(let [tdata (get headers "x-penpot-data")]
(when-not (str/empty? tdata)
(let [result (tokens :verify {:token tdata :iss :profile-identity})]
(let [result (tokens/verify sprops {:token tdata :iss :profile-identity})]
(:profile-id result)))))
(defn- parse-notification

View file

@ -31,7 +31,6 @@
(http/send-async req {:client client :as response-type}))))
{::client client})))
(defn req!
"A convencience toplevel function for gradual migration to a new API
convention."

View file

@ -11,6 +11,7 @@
[app.config :as cf]
[app.db :as db]
[app.db.sql :as sql]
[app.tokens :as tokens]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.spec.alpha :as s]
@ -39,7 +40,7 @@
(delete-session [store key]))
(defn- make-database-store
[{:keys [pool tokens executor]}]
[{:keys [pool sprops executor]}]
(reify ISessionStore
(read-session [_ token]
(px/with-dispatch executor
@ -50,9 +51,9 @@
(let [profile-id (:profile-id data)
user-agent (:user-agent data)
created-at (or (:created-at data) (dt/now))
token (tokens :generate {:iss "authentication"
:iat created-at
:uid profile-id})
token (tokens/generate sprops {:iss "authentication"
:iat created-at
:uid profile-id})
params {:user-agent user-agent
:profile-id profile-id
:created-at created-at
@ -75,7 +76,7 @@
nil))))
(defn make-inmemory-store
[{:keys [tokens]}]
[{:keys [sprops]}]
(let [cache (atom {})]
(reify ISessionStore
(read-session [_ token]
@ -86,9 +87,9 @@
(let [profile-id (:profile-id data)
user-agent (:user-agent data)
created-at (or (:created-at data) (dt/now))
token (tokens :generate {:iss "authentication"
:iat created-at
:uid profile-id})
token (tokens/generate sprops {:iss "authentication"
:iat created-at
:uid profile-id})
params {:user-agent user-agent
:created-at created-at
:updated-at created-at
@ -108,9 +109,9 @@
(swap! cache dissoc token)
nil)))))
(s/def ::tokens fn?)
(s/def ::sprops map?)
(defmethod ig/pre-init-spec ::store [_]
(s/keys :req-un [::db/pool ::wrk/executor ::tokens]))
(s/keys :req-un [::db/pool ::wrk/executor ::sprops]))
(defmethod ig/init-key ::store
[_ {:keys [pool] :as cfg}]

View file

@ -15,6 +15,7 @@
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.tokens :as tokens]
[app.util.async :as aa]
[app.util.time :as dt]
[app.worker :as wrk]
@ -237,10 +238,10 @@
(s/def ::http-client fn?)
(s/def ::uri ::us/string)
(s/def ::tokens fn?)
(s/def ::sprops map?)
(defmethod ig/pre-init-spec ::archive-task [_]
(s/keys :req-un [::db/pool ::tokens ::http-client]
(s/keys :req-un [::db/pool ::sprops ::http-client]
:opt-un [::uri]))
(defmethod ig/init-key ::archive-task
@ -276,7 +277,7 @@
for update skip locked;")
(defn archive-events
[{:keys [pool uri tokens http-client] :as cfg}]
[{:keys [pool uri sprops http-client] :as cfg}]
(letfn [(decode-row [{:keys [props ip-addr context] :as row}]
(cond-> row
(db/pgobject? props)
@ -300,9 +301,9 @@
:context]))
(send [events]
(let [token (tokens :generate {:iss "authentication"
:iat (dt/now)
:uid uuid/zero})
(let [token (tokens/generate sprops {:iss "authentication"
:iat (dt/now)
:uid uuid/zero})
body (t/encode {:events events})
headers {"content-type" "application/transit+json"
"origin" (cf/get :public-uri)

View file

@ -69,9 +69,6 @@
:executor (ig/ref [::default :app.worker/executor])
:redis-uri (cf/get :redis-uri)}
:app.tokens/tokens
{:keys (ig/ref :app.setup/keys)}
:app.storage.tmp/cleaner
{:executor (ig/ref [::worker :app.worker/executor])
:scheduler (ig/ref :app.worker/scheduler)}
@ -92,7 +89,7 @@
:app.http.session/store
{:pool (ig/ref :app.db/pool)
:tokens (ig/ref :app.tokens/tokens)
:sprops (ig/ref :app.setup/props)
:executor (ig/ref [::default :app.worker/executor])}
:app.http.session/gc-task
@ -100,7 +97,7 @@
:max-age (cf/get :auth-token-cookie-max-age)}
:app.http.awsns/handler
{:tokens (ig/ref :app.tokens/tokens)
{:sprops (ig/ref :app.setup/props)
:pool (ig/ref :app.db/pool)
:http-client (ig/ref :app.http/client)
:executor (ig/ref [::worker :app.worker/executor])}
@ -168,13 +165,14 @@
:github (ig/ref :app.auth.oidc/github-provider)
:gitlab (ig/ref :app.auth.oidc/gitlab-provider)
:oidc (ig/ref :app.auth.oidc/generic-provider)}
:tokens (ig/ref :app.tokens/tokens)
:sprops (ig/ref :app.setup/props)
:http-client (ig/ref :app.http/client)
:pool (ig/ref :app.db/pool)
:session (ig/ref :app.http/session)
:public-uri (cf/get :public-uri)
:executor (ig/ref [::default :app.worker/executor])}
;; TODO: revisit the dependencies of this service, looks they are too much unused of them
:app.http/router
{:assets (ig/ref :app.http.assets/handlers)
:feedback (ig/ref :app.http.feedback/handler)
@ -186,7 +184,6 @@
:metrics (ig/ref :app.metrics/metrics)
:public-uri (cf/get :public-uri)
:storage (ig/ref :app.storage/storage)
:tokens (ig/ref :app.tokens/tokens)
:audit-handler (ig/ref :app.loggers.audit/http-handler)
:rpc-routes (ig/ref :app.rpc/routes)
:doc-routes (ig/ref :app.rpc.doc/routes)
@ -218,7 +215,7 @@
:app.rpc/methods
{:pool (ig/ref :app.db/pool)
:session (ig/ref :app.http/session)
:tokens (ig/ref :app.tokens/tokens)
:sprops (ig/ref :app.setup/props)
:metrics (ig/ref :app.metrics/metrics)
:storage (ig/ref :app.storage/storage)
:msgbus (ig/ref :app.msgbus/msgbus)
@ -293,8 +290,8 @@
{:pool (ig/ref :app.db/pool)
:key (cf/get :secret-key)}
:app.setup/keys
{:props (ig/ref :app.setup/props)}
;; :app.setup/keys
;; {:props (ig/ref :app.setup/props)}
:app.loggers.zmq/receiver
{:endpoint (cf/get :loggers-zmq-uri)}
@ -309,7 +306,7 @@
:app.loggers.audit/archive-task
{:uri (cf/get :audit-log-archive-uri)
:tokens (ig/ref :app.tokens/tokens)
:sprops (ig/ref :app.setup/props)
:pool (ig/ref :app.db/pool)
:http-client (ig/ref :app.http/client)}

View file

@ -258,12 +258,12 @@
(s/def ::public-uri ::us/not-empty-string)
(s/def ::session map?)
(s/def ::storage some?)
(s/def ::tokens fn?)
(s/def ::sprops map?)
(defmethod ig/pre-init-spec ::methods [_]
(s/keys :req-un [::storage
::session
::tokens
::sprops
::audit
::executors
::public-uri

View file

@ -17,6 +17,7 @@
[app.rpc.mutations.teams :as teams]
[app.rpc.queries.profile :as profile]
[app.rpc.rlimit :as rlimit]
[app.tokens :as tokens]
[app.util.services :as sv]
[app.util.time :as dt]
[buddy.hashers :as hashers]
@ -80,7 +81,7 @@
;; ---- COMMAND: login with password
(defn login-with-password
[{:keys [pool session tokens] :as cfg} {:keys [email password] :as params}]
[{:keys [pool session sprops] :as cfg} {:keys [email password] :as params}]
(when-not (contains? cf/flags :login)
(ex/raise :type :restriction
@ -114,7 +115,7 @@
(profile/decode-profile-row))
invitation (when-let [token (:invitation-token params)]
(tokens :verify {:token token :iss :team-invitation}))
(tokens/verify sprops {:token token :iss :team-invitation}))
;; If invitation member-id does not matches the profile-id, we just proceed to ignore the
;; invitation because invitations matches exactly; and user can't loging with other email and
@ -156,9 +157,9 @@
;; ---- COMMAND: Recover Profile
(defn recover-profile
[{:keys [pool tokens] :as cfg} {:keys [token password]}]
[{:keys [pool sprops] :as cfg} {:keys [token password]}]
(letfn [(validate-token [token]
(let [tdata (tokens :verify {:token token :iss :password-recovery})]
(let [tdata (tokens/verify sprops {:token token :iss :password-recovery})]
(:profile-id tdata)))
(update-password [conn profile-id]
@ -184,12 +185,12 @@
;; ---- COMMAND: Prepare Register
(defn prepare-register
[{:keys [pool tokens] :as cfg} params]
[{:keys [pool sprops] :as cfg} params]
(when-not (contains? cf/flags :registration)
(if-not (contains? params :invitation-token)
(ex/raise :type :restriction
:code :registration-disabled)
(let [invitation (tokens :verify {:token (:invitation-token params) :iss :team-invitation})]
(let [invitation (tokens/verify sprops {:token (:invitation-token params) :iss :team-invitation})]
(when-not (= (:email params) (:member-email invitation))
(ex/raise :type :restriction
:code :email-does-not-match-invitation
@ -222,7 +223,7 @@
:iss :prepared-register
:exp (dt/in-future "48h")}
token (tokens :generate params)]
token (tokens/generate sprops params)]
(with-meta {:token token}
{::audit/profile-id uuid/zero})))
@ -297,8 +298,8 @@
(assoc :default-project-id (:default-project-id team)))))
(defn register-profile
[{:keys [conn tokens session] :as cfg} {:keys [token] :as params}]
(let [claims (tokens :verify {:token token :iss :prepared-register})
[{:keys [conn sprops session] :as cfg} {:keys [token] :as params}]
(let [claims (tokens/verify sprops {:token token :iss :prepared-register})
params (merge params claims)]
(check-profile-existence! conn params)
(let [is-active (or (:is-active params)
@ -308,14 +309,14 @@
(create-profile-relations conn)
(profile/decode-profile-row))
invitation (when-let [token (:invitation-token params)]
(tokens :verify {:token token :iss :team-invitation}))]
(tokens/verify sprops {:token token :iss :team-invitation}))]
(cond
;; If invitation token comes in params, this is because the user comes from team-invitation process;
;; in this case, regenerate token and send back to the user a new invitation token (and mark current
;; session as logged). This happens only if the invitation email matches with the register email.
(and (some? invitation) (= (:email profile) (:member-email invitation)))
(let [claims (assoc invitation :member-id (:id profile))
token (tokens :generate claims)
token (tokens/generate sprops claims)
resp {:invitation-token token}]
(with-meta resp
{:transform-response ((:create session) (:id profile))
@ -341,14 +342,15 @@
;; In all other cases, send a verification email.
:else
(let [vtoken (tokens :generate
{:iss :verify-email
:exp (dt/in-future "48h")
:profile-id (:id profile)
:email (:email profile)})
ptoken (tokens :generate-predefined
{:iss :profile-identity
:profile-id (:id profile)})]
(let [vtoken (tokens/generate sprops
{:iss :verify-email
:exp (dt/in-future "48h")
:profile-id (:id profile)
:email (:email profile)})
ptoken (tokens/generate sprops
{:iss :profile-identity
:profile-id (:id profile)
:exp (dt/in-future {:days 30})})]
(eml/send! {::eml/conn conn
::eml/factory eml/register
:public-uri (:public-uri cfg)
@ -376,18 +378,19 @@
;; ---- COMMAND: Request Profile Recovery
(defn request-profile-recovery
[{:keys [pool tokens] :as cfg} {:keys [email] :as params}]
[{:keys [pool sprops] :as cfg} {:keys [email] :as params}]
(letfn [(create-recovery-token [{:keys [id] :as profile}]
(let [token (tokens :generate
{:iss :password-recovery
:exp (dt/in-future "15m")
:profile-id id})]
(let [token (tokens/generate sprops
{:iss :password-recovery
:exp (dt/in-future "15m")
:profile-id id})]
(assoc profile :token token)))
(send-email-notification [conn profile]
(let [ptoken (tokens :generate-predefined
{:iss :profile-identity
:profile-id (:id profile)})]
(let [ptoken (tokens/generate sprops
{:iss :profile-identity
:profile-id (:id profile)
:exp (dt/in-future {:days 30})})]
(eml/send! {::eml/conn conn
::eml/factory eml/password-recovery
:public-uri (:public-uri cfg)

View file

@ -19,6 +19,7 @@
[app.rpc.queries.profile :as profile]
[app.rpc.rlimit :as rlimit]
[app.storage :as sto]
[app.tokens :as tokens]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
@ -183,15 +184,16 @@
{:changed true})
(defn- request-email-change
[{:keys [conn tokens] :as cfg} {:keys [profile email] :as params}]
(let [token (tokens :generate
{:iss :change-email
:exp (dt/in-future "15m")
:profile-id (:id profile)
:email email})
ptoken (tokens :generate-predefined
{:iss :profile-identity
:profile-id (:id profile)})]
[{:keys [conn sprops] :as cfg} {:keys [profile email] :as params}]
(let [token (tokens/generate sprops
{:iss :change-email
:exp (dt/in-future "15m")
:profile-id (:id profile)
:email email})
ptoken (tokens/generate sprops
{:iss :profile-identity
:profile-id (:id profile)
:exp (dt/in-future {:days 30})})]
(when (not= email (:email profile))
(cmd.auth/check-profile-existence! conn params))

View file

@ -22,6 +22,7 @@
[app.rpc.queries.teams :as teams]
[app.rpc.rlimit :as rlimit]
[app.storage :as sto]
[app.tokens :as tokens]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
@ -398,20 +399,21 @@
update set role = ?, valid_until = ?, updated_at = now();")
(defn- create-team-invitation
[{:keys [conn tokens team profile role email] :as cfg}]
[{:keys [conn sprops team profile role email] :as cfg}]
(let [member (profile/retrieve-profile-data-by-email conn email)
token-exp (dt/in-future "168h") ;; 7 days
itoken (tokens :generate
{:iss :team-invitation
:exp token-exp
:profile-id (:id profile)
:role role
:team-id (:id team)
:member-email (:email member email)
:member-id (:id member)})
ptoken (tokens :generate-predefined
{:iss :profile-identity
:profile-id (:id profile)})]
itoken (tokens/generate sprops
{:iss :team-invitation
:exp token-exp
:profile-id (:id profile)
:role role
:team-id (:id team)
:member-email (:email member email)
:member-id (:id member)})
ptoken (tokens/generate sprops
{:iss :profile-identity
:profile-id (:id profile)
:exp (dt/in-future {:days 30})})]
(when (contains? cf/flags :log-invitation-tokens)
(l/trace :hint "invitation token" :token itoken))

View file

@ -12,6 +12,8 @@
[app.loggers.audit :as audit]
[app.rpc.mutations.teams :as teams]
[app.rpc.queries.profile :as profile]
[app.tokens :as tokens]
[app.tokens.spec.team-invitation :as-alias spec.team-invitation]
[app.util.services :as sv]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]))
@ -23,9 +25,9 @@
:opt-un [::profile-id]))
(sv/defmethod ::verify-token {:auth false}
[{:keys [pool tokens] :as cfg} {:keys [token] :as params}]
[{:keys [pool sprops] :as cfg} {:keys [token] :as params}]
(db/with-atomic [conn pool]
(let [claims (tokens :verify {:token token})
(let [claims (tokens/verify sprops {:token token})
cfg (assoc cfg :conn conn)]
(process-token cfg params claims))))
@ -76,19 +78,19 @@
(s/def ::iss keyword?)
(s/def ::exp ::us/inst)
(s/def :internal.tokens.team-invitation/profile-id ::us/uuid)
(s/def :internal.tokens.team-invitation/role ::us/keyword)
(s/def :internal.tokens.team-invitation/team-id ::us/uuid)
(s/def :internal.tokens.team-invitation/member-email ::us/email)
(s/def :internal.tokens.team-invitation/member-id (s/nilable ::us/uuid))
(s/def ::spec.team-invitation/profile-id ::us/uuid)
(s/def ::spec.team-invitation/role ::us/keyword)
(s/def ::spec.team-invitation/team-id ::us/uuid)
(s/def ::spec.team-invitation/member-email ::us/email)
(s/def ::spec.team-invitation/member-id (s/nilable ::us/uuid))
(s/def ::team-invitation-claims
(s/keys :req-un [::iss ::exp
:internal.tokens.team-invitation/profile-id
:internal.tokens.team-invitation/role
:internal.tokens.team-invitation/team-id
:internal.tokens.team-invitation/member-email]
:opt-un [:internal.tokens.team-invitation/member-id]))
::spec.team-invitation/profile-id
::spec.team-invitation/role
::spec.team-invitation/team-id
::spec.team-invitation/member-email]
:opt-un [::spec.team-invitation/member-id]))
(defn- accept-invitation
[{:keys [conn] :as cfg} {:keys [member-id team-id role member-email] :as claims}]

View file

@ -11,6 +11,7 @@
[app.common.uuid :as uuid]
[app.db :as db]
[app.setup.builtin-templates]
[app.setup.keys :as keys]
[buddy.core.codecs :as bc]
[buddy.core.nonce :as bn]
[clojure.spec.alpha :as s]
@ -59,6 +60,8 @@
"all sessions on each restart, it is hightly recommeded setting up the "
"PENPOT_SECRET_KEY environment variable")))
(let [stored (-> (retrieve-all conn)
(assoc :secret-key (or key (generate-random-key))))]
(update stored :instance-id handle-instance-id conn (db/read-only? pool)))))
(let [secret (or key (generate-random-key))]
(-> (retrieve-all conn)
(assoc :secret-key secret)
(assoc :tokens-key (keys/derive secret :salt "tokens" :size 32))
(update :instance-id handle-instance-id conn (db/read-only? pool))))))

View file

@ -6,24 +6,17 @@
(ns app.setup.keys
"Keys derivation service."
(:refer-clojure :exclude [derive])
(:require
[app.common.spec :as us]
[buddy.core.kdf :as bk]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
(s/def ::secret-key ::us/string)
(s/def ::props (s/keys :req-un [::secret-key]))
(defmethod ig/pre-init-spec :app.setup/keys [_]
(s/keys :req-un [::props]))
(defmethod ig/init-key :app.setup/keys
[_ {:keys [props] :as cfg}]
(fn [& {:keys [salt _]}]
(let [engine (bk/engine {:key (:secret-key props)
:salt salt
:alg :hkdf
:digest :blake2b-512})]
(bk/get-bytes engine 32))))
[buddy.core.kdf :as bk]))
(defn derive
"Derive a key from secret-key"
[secret-key & {:keys [salt size]}]
(us/assert! ::us/not-empty-string secret-key)
(let [engine (bk/engine {:key secret-key
:salt salt
:alg :hkdf
:digest :blake2b-512})]
(bk/get-bytes engine size)))

View file

@ -5,28 +5,27 @@
;; Copyright (c) UXBOX Labs SL
(ns app.tokens
"Tokens generation service."
"Tokens generation API."
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.transit :as t]
[app.util.time :as dt]
[buddy.sign.jwe :as jwe]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
[buddy.sign.jwe :as jwe]))
(defn- generate
[cfg claims]
(defn generate
[{:keys [tokens-key]} claims]
(us/assert! ::us/not-empty-string tokens-key)
(let [payload (-> claims
(assoc :iat (dt/now))
(d/without-nils)
(t/encode))]
(jwe/encrypt payload (::secret cfg) {:alg :a256kw :enc :a256gcm})))
(jwe/encrypt payload tokens-key {:alg :a256kw :enc :a256gcm})))
(defn- verify
[cfg {:keys [token] :as params}]
(let [payload (jwe/decrypt token (::secret cfg) {:alg :a256kw :enc :a256gcm})
(defn verify
[{:keys [tokens-key]} {:keys [token] :as params}]
(let [payload (jwe/decrypt token tokens-key {:alg :a256kw :enc :a256gcm})
claims (t/decode payload)]
(when (and (dt/instant? (:exp claims))
(dt/is-before? (:exp claims) (dt/now)))
@ -45,30 +44,7 @@
:params params))
claims))
(defn- generate-predefined
[cfg {:keys [iss profile-id] :as params}]
(case iss
:profile-identity
(do
(us/verify uuid? profile-id)
(generate cfg (assoc params
:exp (dt/in-future {:days 30}))))
(ex/raise :type :internal
:code :not-implemented
:hint "no predefined token")))
(s/def ::keys fn?)
(defmethod ig/pre-init-spec ::tokens [_]
(s/keys :req-un [::keys]))
(defmethod ig/init-key ::tokens
[_ {:keys [keys] :as cfg}]
(let [secret (keys :salt "tokens" :size 32)
cfg (assoc cfg ::secret secret)]
(fn [action params]
(case action
:generate-predefined (generate-predefined cfg params)
:verify (verify cfg params)
:generate (generate cfg params)))))

View file

@ -116,6 +116,7 @@
(some? position-modifier)
(gpt/transform position-modifier))
content (:content draft)
pos-x (* (:x position) zoom)
pos-y (* (:y position) zoom)