diff --git a/backend/src/app/auth/oidc.clj b/backend/src/app/auth/oidc.clj index 39572bb18..d660eef25 100644 --- a/backend/src/app/auth/oidc.clj +++ b/backend/src/app/auth/oidc.clj @@ -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 diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 7bef64e78..6347e8e6b 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -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 diff --git a/backend/src/app/http/awsns.clj b/backend/src/app/http/awsns.clj index dd5bc35a3..c14be0b26 100644 --- a/backend/src/app/http/awsns.clj +++ b/backend/src/app/http/awsns.clj @@ -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 diff --git a/backend/src/app/http/client.clj b/backend/src/app/http/client.clj index 8718606b1..e9cddbff5 100644 --- a/backend/src/app/http/client.clj +++ b/backend/src/app/http/client.clj @@ -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." diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index f3e401641..6241b680e 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -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}] diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index dc1289a09..ac9de126b 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -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) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 47054d5d4..a29e5acd8 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -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)} diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 5608ffcbd..548dda581 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -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 diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index 6c9afdb19..625bf6cf5 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -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) diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index dea366f5d..f8c0582c9 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -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)) diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index 7be2a96aa..c9ceae6ff 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -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)) diff --git a/backend/src/app/rpc/mutations/verify_token.clj b/backend/src/app/rpc/mutations/verify_token.clj index a8016c0bb..3211f3bec 100644 --- a/backend/src/app/rpc/mutations/verify_token.clj +++ b/backend/src/app/rpc/mutations/verify_token.clj @@ -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}] diff --git a/backend/src/app/setup.clj b/backend/src/app/setup.clj index 7bc0960ba..1fe5dc764 100644 --- a/backend/src/app/setup.clj +++ b/backend/src/app/setup.clj @@ -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)))))) diff --git a/backend/src/app/setup/keys.clj b/backend/src/app/setup/keys.clj index 372b83a20..468081304 100644 --- a/backend/src/app/setup/keys.clj +++ b/backend/src/app/setup/keys.clj @@ -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))) diff --git a/backend/src/app/tokens.clj b/backend/src/app/tokens.clj index dc68c6897..ff4fd998a 100644 --- a/backend/src/app/tokens.clj +++ b/backend/src/app/tokens.clj @@ -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))))) diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 31fe47767..cddc4c23a 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -116,6 +116,7 @@ (some? position-modifier) (gpt/transform position-modifier)) content (:content draft) + pos-x (* (:x position) zoom) pos-y (* (:y position) zoom)