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:
parent
44f4d9c50c
commit
d6d9d25fce
16 changed files with 167 additions and 183 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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}]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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}]
|
||||
|
|
|
@ -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))))))
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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)))))
|
||||
|
|
|
@ -116,6 +116,7 @@
|
|||
(some? position-modifier)
|
||||
(gpt/transform position-modifier))
|
||||
content (:content draft)
|
||||
|
||||
pos-x (* (:x position) zoom)
|
||||
pos-y (* (:y position) zoom)
|
||||
|
||||
|
|
Loading…
Reference in a new issue