diff --git a/backend/scripts/_env b/backend/scripts/_env index 3155974924..0026d9f901 100644 --- a/backend/scripts/_env +++ b/backend/scripts/_env @@ -1,7 +1,12 @@ #!/usr/bin/env bash -export PENPOT_MANAGEMENT_API_KEY=super-secret-management-api-key +export PENPOT_NITRATE_SHARED_KEY=super-secret-nitrate-api-key +export PENPOT_EXPORTER_SHARED_KEY=super-secret-exporter-api-key export PENPOT_SECRET_KEY=super-secret-devenv-key + +# DEPRECATED: only used for subscriptions +export PENPOT_MANAGEMENT_API_KEY=super-secret-management-api-key + export PENPOT_HOST=devenv export PENPOT_PUBLIC_URI=https://localhost:3449 diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 7ba69d7710..6b3f784170 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -102,6 +102,8 @@ [:http-server-io-threads {:optional true} ::sm/int] [:http-server-max-worker-threads {:optional true} ::sm/int] + [:exporter-shared-key {:optional true} :string] + [:nitrate-shared-key {:optional true} :string] [:management-api-key {:optional true} :string] [:telemetry-uri {:optional true} :string] diff --git a/backend/src/app/http/management.clj b/backend/src/app/http/management.clj index b32b7f4077..6f461dbc1e 100644 --- a/backend/src/app/http/management.clj +++ b/backend/src/app/http/management.clj @@ -13,13 +13,13 @@ [app.common.time :as ct] [app.config :as cf] [app.db :as db] - [app.http.middleware :as mw] [app.main :as-alias main] [app.rpc.commands.profile :as cmd.profile] [app.setup :as-alias setup] [app.tokens :as tokens] [app.worker :as-alias wrk] [integrant.core :as ig] + [yetti.request :as yreq] [yetti.response :as-alias yres])) ;; ---- ROUTES @@ -49,28 +49,40 @@ (fn [cfg request] (db/tx-run! cfg handler request)))))}) +(def ^:private shared-key-auth + {:name ::shared-key-auth + :compile + (fn [_ _] + (fn [handler key] + (if key + (fn [request] + (if-let [key' (yreq/get-header request "x-shared-key")] + (if (= key key') + (handler request) + {::yres/status 403}) + {::yres/status 403})) + (fn [_ _] + {::yres/status 403}))))}) + (defmethod ig/init-key ::routes - [_ {:keys [::setup/props] :as cfg}] + [_ cfg] - (let [management-key (or (cf/get :management-api-key) - (get props :management-key))] + ["" {:middleware [[shared-key-auth (cf/get :management-api-key)] + [default-system cfg] + [transaction]]} + ["/authenticate" + {:handler authenticate + :allowed-methods #{:post}}] - ["" {:middleware [[mw/shared-key-auth management-key] - [default-system cfg] - [transaction]]} - ["/authenticate" - {:handler authenticate - :allowed-methods #{:post}}] + ["/get-customer" + {:handler get-customer + :transaction true + :allowed-methods #{:post}}] - ["/get-customer" - {:handler get-customer - :transaction true - :allowed-methods #{:post}}] - - ["/update-customer" - {:handler update-customer - :allowed-methods #{:post} - :transaction true}]])) + ["/update-customer" + {:handler update-customer + :allowed-methods #{:post} + :transaction true}]]) ;; ---- HELPERS diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index 7d4e29b721..48f7c36149 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -16,7 +16,6 @@ [app.http.errors :as errors] [app.tokens :as tokens] [app.util.pointer-map :as pmap] - [buddy.core.codecs :as bc] [cuerdas.core :as str] [yetti.adapter :as yt] [yetti.middleware :as ymw] @@ -301,16 +300,20 @@ :compile (constantly wrap-auth)}) (defn- wrap-shared-key-auth - [handler shared-key] - (if shared-key - (let [shared-key (if (string? shared-key) - shared-key - (bc/bytes->b64-str shared-key true))] - (fn [request] - (let [key (yreq/get-header request "x-shared-key")] - (if (= key shared-key) - (handler (assoc request ::http/auth-with-shared-key true)) - {::yres/status 403})))) + [handler keys] + (if (seq keys) + (fn [request] + (if-let [[key-id key] (some-> (yreq/get-header request "x-shared-key") + (str/split #"\s+" 2))] + (let [key-id (str/lower key-id)] + (if (and (string? key) + (contains? keys key-id) + (= key (get keys key-id))) + (-> request + (assoc ::http/auth-key-id key-id) + (handler)) + {::yres/status 403})) + {::yres/status 403})) (fn [_ _] {::yres/status 403}))) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index b620cdd6e6..859b84de7d 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -140,10 +140,14 @@ client-version (get-client-version request) client-user-agent (get-client-user-agent request) session-id (get-external-session-id request) - token-id (::actoken/id request)] + key-id (::http/auth-key-id request) + token-id (::actoken/id request) + token-type (::actoken/type request)] (d/without-nils {:external-session-id session-id + :initiator (or key-id "app") :access-token-id (some-> token-id str) + :access-token-type (some-> token-type str) :client-event-origin client-event-origin :client-user-agent client-user-agent :client-version client-version diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 1923b5f054..e104fba625 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -275,8 +275,7 @@ ::email/whitelist (ig/ref ::email/whitelist)} ::mgmt/routes - {::db/pool (ig/ref ::db/pool) - ::setup/props (ig/ref ::setup/props)} + {::db/pool (ig/ref ::db/pool)} :app.http/router {::session/manager (ig/ref ::session/manager) @@ -341,7 +340,8 @@ ::email/whitelist (ig/ref ::email/whitelist)} :app.nitrate/client - {::http.client/client (ig/ref ::http.client/client)} + {::http.client/client (ig/ref ::http.client/client) + ::setup/shared-keys (ig/ref ::setup/shared-keys)} :app.rpc/management-methods {::http.client/client (ig/ref ::http.client/client) @@ -357,13 +357,13 @@ ::setup/props (ig/ref ::setup/props)} ::rpc/routes - {::rpc/methods (ig/ref :app.rpc/methods) + {::rpc/methods (ig/ref :app.rpc/methods) ::rpc/management-methods (ig/ref :app.rpc/management-methods) ;; FIXME: revisit if db/pool is necessary here ::db/pool (ig/ref ::db/pool) ::session/manager (ig/ref ::session/manager) - ::setup/props (ig/ref ::setup/props)} + ::setup/shared-keys (ig/ref ::setup/shared-keys)} ::wrk/registry {::mtx/metrics (ig/ref ::mtx/metrics) @@ -451,6 +451,11 @@ ;; module requires the migrations to run before initialize. ::migrations (ig/ref :app.migrations/migrations)} + ::setup/shared-keys + {::setup/props (ig/ref ::setup/props) + :nitrate (cf/get :nitrate-shared-key) + :exporter (cf/get :exporter-shared-key)} + ::setup/clock {} diff --git a/backend/src/app/nitrate.clj b/backend/src/app/nitrate.clj index b04e834975..14c1da0e99 100644 --- a/backend/src/app/nitrate.clj +++ b/backend/src/app/nitrate.clj @@ -1,9 +1,3 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - (ns app.nitrate "Module that make calls to the external nitrate aplication" (:require @@ -17,18 +11,17 @@ [clojure.core :as c] [integrant.core :as ig])) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; HELPERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- request-builder - [cfg method uri management-key profile-id] + [cfg method uri shared-key profile-id] (fn [] (http/req! cfg {:method method :headers {"content-type" "application/json" "accept" "application/json" - "x-shared-key" management-key + "x-shared-key" shared-key "x-profile-id" (str profile-id)} :uri uri :version :http1.1}))) @@ -54,9 +47,9 @@ (defn- with-validate [handler uri schema] (fn [] - (let [coercer-http (sm/coercer schema - :type :validation - :hint (str "invalid data received calling " uri))] + (let [coercer-http (sm/coercer schema + :type :validation + :hint (str "invalid data received calling " uri))] (try (coercer-http (-> (handler) :body json/decode)) (catch Exception e @@ -65,8 +58,9 @@ nil))))) (defn- request-to-nitrate - [{:keys [::management-key] :as cfg} method uri schema {:keys [::rpc/profile-id] :as params}] - (let [full-http-call (-> (request-builder cfg method uri management-key profile-id) + [cfg method uri schema {:keys [::rpc/profile-id] :as params}] + (let [shared-key (-> cfg ::setup/shared-keys :nitrate) + full-http-call (-> (request-builder cfg method uri shared-key profile-id) (with-retries 3) (with-validate uri schema))] (full-http-call))) @@ -103,26 +97,15 @@ (let [baseuri (cf/get :nitrate-backend-uri)] (request-to-nitrate cfg :get (str baseuri "/api/users/" (str profile-id)) schema:user params))) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; INITIALIZATION ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defmethod ig/init-key ::client - [_ {:keys [::setup/props] :as cfg}] - (if (contains? cf/flags :nitrate) - (let [management-key (or (cf/get :management-api-key) - (get props :management-key)) - cfg (assoc cfg ::management-key management-key)] - {:get-team-org (partial get-team-org cfg) - :is-valid-user (partial is-valid-user cfg)}) - {})) - -(defmethod ig/halt-key! ::client - [_ {:keys []}] - (do :stuff)) - + [_ cfg] + (when (contains? cf/flags :nitrate) + {:get-team-org (partial get-team-org cfg) + :is-valid-user (partial is-valid-user cfg)})) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; UTILS @@ -144,4 +127,4 @@ [cfg team params] (let [params (assoc (or params {}) :team-id (:id team)) org (call cfg :get-team-org params)] - (assoc team :organization-id (:id org) :organization-name (:name org)))) \ No newline at end of file + (assoc team :organization-id (:id org) :organization-name (:name org)))) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 7e286360ad..a9cef88868 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -92,11 +92,11 @@ (fn [{:keys [params path-params method] :as request}] (let [handler-name (:type path-params) etag (yreq/get-header request "if-none-match") + + key-id (get request ::http/auth-key-id) profile-id (or (::session/profile-id request) (::actoken/profile-id request) - (if (::http/auth-with-shared-key request) - uuid/zero - nil)) + (if key-id uuid/zero nil)) ip-addr (inet/parse-request request) @@ -298,11 +298,12 @@ (defn- resolve-management-methods [cfg] - (let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing)] - (->> (sv/scan-ns - 'app.rpc.management.subscription - 'app.rpc.management.nitrate - 'app.rpc.management.exporter) + (let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing) + mods (cond->> (list 'app.rpc.management.exporter) + (contains? cf/flags :nitrate) + (cons 'app.rpc.management.nitrate))] + + (->> (apply sv/scan-ns mods) (map (partial process-method cfg "management" wrap-management)) (into {})))) @@ -346,23 +347,20 @@ (defmethod ig/assert-key ::routes [_ params] + (assert (map? (::setup/shared-keys params))) (assert (db/pool? (::db/pool params)) "expect valid database pool") - (assert (some? (::setup/props params))) (assert (session/manager? (::session/manager params)) "expect valid session manager") (assert (valid-methods? (::methods params)) "expect valid methods map") (assert (valid-methods? (::management-methods params)) "expect valid methods map")) (defmethod ig/init-key ::routes - [_ {:keys [::methods ::management-methods ::setup/props] :as cfg}] - - (let [public-uri (cf/get :public-uri) - management-key (or (cf/get :management-api-key) - (get props :management-key))] + [_ {:keys [::methods ::management-methods ::setup/shared-keys] :as cfg}] + (let [public-uri (cf/get :public-uri)] ["/api" ["/management" ["/methods/:type" - {:middleware [[mw/shared-key-auth management-key] + {:middleware [[mw/shared-key-auth shared-keys] [session/authz cfg]] :handler (make-rpc-handler management-methods)}] diff --git a/backend/src/app/rpc/management/nitrate.clj b/backend/src/app/rpc/management/nitrate.clj index 86ef1db999..42e0e10375 100644 --- a/backend/src/app/rpc/management/nitrate.clj +++ b/backend/src/app/rpc/management/nitrate.clj @@ -5,15 +5,13 @@ ;; Copyright (c) KALEIDOS INC (ns app.rpc.management.nitrate - "Internal Nitrate HTTP API. - Provides authenticated access to organization management and token validation endpoints. - All requests must include a valid shared key token in the `x-shared-key` header, and - a cookie `auth-token` with the user token. - They will return `401 Unauthorized` if the shared key or user token are invalid." + "Internal Nitrate HTTP RPC API. Provides authenticated access to + organization management and token validation endpoints." (:require [app.common.schema :as sm] + [app.common.types.profile :refer [schema:profile]] + [app.common.types.team :refer [schema:team]] [app.common.uuid :as uuid] - [app.config :as cf] [app.db :as db] [app.msgbus :as mbus] [app.rpc :as-alias rpc] @@ -23,22 +21,14 @@ [app.util.services :as sv])) ;; ---- API: authenticate -(def ^:private schema:profile - [:map - [:id ::sm/uuid] - [:name :string] - [:email :string] - [:photo-url :string]]) (sv/defmethod ::authenticate - "Authenticate an user - @api GET /authenticate - @returns - 200 OK: Returns the authenticated user." - {::doc/added "2.12" + "Authenticate the current user" + {::doc/added "2.14" + ::sm/params [:map] ::sm/result schema:profile} [cfg {:keys [::rpc/profile-id] :as params}] - (let [profile (profile/get-profile cfg profile-id)] + (let [profile (profile/get-profile cfg profile-id)] {:id (get profile :id) :name (get profile :fullname) :email (get profile :email) @@ -51,30 +41,22 @@ FROM team AS t JOIN team_profile_rel AS tpr ON t.id = tpr.team_id WHERE tpr.profile_id = ? - AND tpr.is_owner = 't' - AND t.is_default = 'f' - AND t.deleted_at is null;") - -(def ^:private schema:team - [:map - [:id ::sm/uuid] - [:name :string]]) + AND tpr.is_owner IS TRUE + AND t.is_default IS FALSE + AND t.deleted_at IS NULL;") (def ^:private schema:get-teams-result [:vector schema:team]) (sv/defmethod ::get-teams - "List teams for which current user is owner. - @api GET /get-teams - @returns - 200 OK: Returns the list of teams for the user." - {::doc/added "2.12" + "List teams for which current user is owner" + {::doc/added "2.14" + ::sm/params [:map] ::sm/result schema:get-teams-result} [cfg {:keys [::rpc/profile-id]}] - (when (contains? cf/flags :nitrate) - (let [current-user-id (-> (profile/get-profile cfg profile-id) :id)] - (->> (db/exec! cfg [sql:get-teams current-user-id]) - (map #(select-keys % [:id :name])))))) + (let [current-user-id (-> (profile/get-profile cfg profile-id) :id)] + (->> (db/exec! cfg [sql:get-teams current-user-id]) + (map #(select-keys % [:id :name]))))) ;; ---- API: notify-team-change @@ -83,30 +65,18 @@ [:id ::sm/uuid] [:organization-id ::sm/text]]) - (sv/defmethod ::notify-team-change - "Notify to Penpot a team change from nitrate - @api POST /notify-team-change - @returns - 200 OK" - {::doc/added "2.12" + "Notify to Penpot a team change from nitrate" + {::doc/added "2.14" ::sm/params schema:notify-team-change ::rpc/auth false} [cfg {:keys [id organization-id organization-name]}] - (when (contains? cf/flags :nitrate) - (let [msgbus (::mbus/msgbus cfg)] - (mbus/pub! msgbus - ;;TODO There is a bug on dashboard with teams notifications. - ;;For now we send it to uuid/zero instead of team-id - :topic uuid/zero - :message {:type :team-org-change - :team-id id - :organization-id organization-id - :organization-name organization-name})))) - - - - - - - + (let [msgbus (::mbus/msgbus cfg)] + (mbus/pub! msgbus + ;;TODO There is a bug on dashboard with teams notifications. + ;;For now we send it to uuid/zero instead of team-id + :topic uuid/zero + :message {:type :team-org-change + :team-id id + :organization-id organization-id + :organization-name organization-name}))) diff --git a/backend/src/app/rpc/management/subscription.clj b/backend/src/app/rpc/management/subscription.clj deleted file mode 100644 index fd0c216104..0000000000 --- a/backend/src/app/rpc/management/subscription.clj +++ /dev/null @@ -1,183 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.rpc.management.subscription - (:require - [app.common.logging :as l] - [app.common.schema :as sm] - [app.common.schema.generators :as sg] - [app.common.time :as ct] - [app.db :as db] - [app.rpc :as-alias rpc] - [app.rpc.commands.profile :as profile] - [app.rpc.doc :as doc] - [app.util.services :as sv])) - -;; ---- RPC METHOD: AUTHENTICATE - -(def ^:private - schema:authenticate-params - [:map {:title "authenticate-params"}]) - -(def ^:private - schema:authenticate-result - [:map {:title "authenticate-result"} - [:profile-id ::sm/uuid]]) - -(sv/defmethod ::auth - {::doc/added "2.12" - ::sm/params schema:authenticate-params - ::sm/result schema:authenticate-result} - [_ {:keys [::rpc/profile-id]}] - {:profile-id profile-id}) - -;; ---- RPC METHOD: GET-CUSTOMER - -;; FIXME: move to app.common.time -(def ^:private schema:timestamp - (sm/type-schema - {:type ::timestamp - :pred ct/inst? - :type-properties - {:title "inst" - :description "The same as :app.common.time/inst but encodes to epoch" - :error/message "should be an instant" - :gen/gen (->> (sg/small-int) - (sg/fmap (fn [v] (ct/inst v)))) - :decode/string #(some-> % ct/inst) - :encode/string #(some-> % inst-ms) - :decode/json #(some-> % ct/inst) - :encode/json #(some-> % inst-ms)}})) - -(def ^:private schema:subscription - [:map {:title "Subscription"} - [:id ::sm/text] - [:customer-id ::sm/text] - [:type [:enum - "unlimited" - "professional" - "enterprise"]] - [:status [:enum - "active" - "canceled" - "incomplete" - "incomplete_expired" - "past_due" - "paused" - "trialing" - "unpaid"]] - - [:billing-period [:enum - "month" - "day" - "week" - "year"]] - [:quantity :int] - [:description [:maybe ::sm/text]] - [:created-at schema:timestamp] - [:start-date [:maybe schema:timestamp]] - [:ended-at [:maybe schema:timestamp]] - [:trial-end [:maybe schema:timestamp]] - [:trial-start [:maybe schema:timestamp]] - [:cancel-at [:maybe schema:timestamp]] - [:canceled-at [:maybe schema:timestamp]] - [:current-period-end [:maybe schema:timestamp]] - [:current-period-start [:maybe schema:timestamp]] - [:cancel-at-period-end :boolean] - - [:cancellation-details - [:map {:title "CancellationDetails"} - [:comment [:maybe ::sm/text]] - [:reason [:maybe ::sm/text]] - [:feedback [:maybe - [:enum - "customer_service" - "low_quality" - "missing_feature" - "other" - "switched_service" - "too_complex" - "too_expensive" - "unused"]]]]]]) - -(def ^:private sql:get-customer-slots - "WITH teams AS ( - SELECT tpr.team_id AS id, - tpr.profile_id AS profile_id - FROM team_profile_rel AS tpr - WHERE tpr.is_owner IS true - AND tpr.profile_id = ? - ), teams_with_slots AS ( - SELECT tpr.team_id AS id, - count(*) AS total - FROM team_profile_rel AS tpr - WHERE tpr.team_id IN (SELECT id FROM teams) - AND tpr.can_edit IS true - GROUP BY 1 - ORDER BY 2 - ) - SELECT max(total) AS total FROM teams_with_slots;") - -(defn- get-customer-slots - [cfg profile-id] - (let [result (db/exec-one! cfg [sql:get-customer-slots profile-id])] - (:total result))) - -(def ^:private schema:get-customer-params - [:map]) - -(def ^:private schema:get-customer-result - [:map - [:id ::sm/uuid] - [:name :string] - [:num-editors ::sm/int] - [:subscription {:optional true} schema:subscription]]) - -(sv/defmethod ::get-customer - {::doc/added "2.12" - ::sm/params schema:get-customer-params - ::sm/result schema:get-customer-result} - [cfg {:keys [::rpc/profile-id]}] - (let [profile (profile/get-profile cfg profile-id)] - {:id (get profile :id) - :name (get profile :fullname) - :email (get profile :email) - :num-editors (get-customer-slots cfg profile-id) - :subscription (-> profile :props :subscription)})) - - -;; ---- RPC METHOD: GET-CUSTOMER - -(def ^:private schema:update-customer-params - [:map - [:subscription [:maybe schema:subscription]]]) - -(def ^:private schema:update-customer-result - [:map]) - -(sv/defmethod ::update-customer - {::doc/added "2.12" - ::sm/params schema:update-customer-params - ::sm/result schema:update-customer-result} - [cfg {:keys [::rpc/profile-id subscription]}] - (let [{:keys [props] :as profile} - (profile/get-profile cfg profile-id ::db/for-update true) - - props - (assoc props :subscription subscription)] - - (l/dbg :hint "update customer" - :profile-id (str profile-id) - :subscription-type (get subscription :type) - :subscription-status (get subscription :status) - :subscription-quantity (get subscription :quantity)) - - (db/update! cfg :profile - {:props (db/tjson props)} - {:id profile-id} - {::db/return-keys false}) - - nil)) diff --git a/backend/src/app/setup.clj b/backend/src/app/setup.clj index 03bed90182..2ef0c49b66 100644 --- a/backend/src/app/setup.clj +++ b/backend/src/app/setup.clj @@ -17,6 +17,7 @@ [app.setup.templates] [buddy.core.codecs :as bc] [buddy.core.nonce :as bn] + [cuerdas.core :as str] [integrant.core :as ig])) (defn- generate-random-key @@ -88,7 +89,38 @@ (-> (get-all-props conn) (assoc :secret-key secret) (assoc :tokens-key (keys/derive secret :salt "tokens")) - (assoc :management-key (keys/derive secret :salt "management")) (update :instance-id handle-instance-id conn (db/read-only? pool))))))) (sm/register! ::props [:map-of :keyword ::sm/any]) + + +(defmethod ig/init-key ::shared-keys + [_ {:keys [::props] :as cfg}] + (let [secret (get props :secret-key)] + (d/without-nils + {"exporter" + (let [key (or (get cfg :exporter) + (-> (keys/derive secret :salt "exporter") + (bc/bytes->b64-str true)))] + (if (or (str/empty? key) + (str/blank? key)) + (do + (l/wrn :hint "exporter key is disabled because empty string found") + nil) + (do + (l/inf :hint "exporter key initialized" :key (d/obfuscate-string key)) + key))) + + "nitrate" + (let [key (or (get cfg :nitrate) + (-> (keys/derive secret :salt "nitrate") + (bc/bytes->b64-str true)))] + (if (or (str/empty? key) + (str/blank? key)) + (do + (l/wrn :hint "nitrate key is disabled because empty string found") + nil) + (do + (l/inf :hint "nitrate key initialized" :key (d/obfuscate-string key)) + key)))}))) + diff --git a/backend/test/backend_tests/http_middleware_test.clj b/backend/test/backend_tests/http_middleware_test.clj index 2d4b8ac029..2c313fef07 100644 --- a/backend/test/backend_tests/http_middleware_test.clj +++ b/backend/test/backend_tests/http_middleware_test.clj @@ -86,7 +86,7 @@ (t/deftest shared-key-auth (let [handler (#'app.http.middleware/wrap-shared-key-auth (fn [req] {::yres/status 200}) - "secret-key")] + {"test1" "secret-key"})] (let [response (handler (->DummyRequest {} {}))] (t/is (= 403 (::yres/status response)))) @@ -95,6 +95,9 @@ (t/is (= 403 (::yres/status response)))) (let [response (handler (->DummyRequest {"x-shared-key" "secret-key"} {}))] + (t/is (= 403 (::yres/status response)))) + + (let [response (handler (->DummyRequest {"x-shared-key" "test1 secret-key"} {}))] (t/is (= 200 (::yres/status response)))))) (t/deftest access-token-authz diff --git a/common/src/app/common/types/team.cljc b/common/src/app/common/types/team.cljc index f8fe889c4f..ad9bac999c 100644 --- a/common/src/app/common/types/team.cljc +++ b/common/src/app/common/types/team.cljc @@ -19,3 +19,10 @@ (def schema:role [::sm/one-of {:title "TeamRole"} valid-roles]) + +;; FIXME: specify more fields +(def schema:team + [:map {:title "Team"} + [:id ::sm/uuid] + [:name :string]]) + diff --git a/exporter/src/app/config.cljs b/exporter/src/app/config.cljs index 03f2b56b8a..2c421689c2 100644 --- a/exporter/src/app/config.cljs +++ b/exporter/src/app/config.cljs @@ -12,11 +12,14 @@ ["node:process" :as process] [app.common.data :as d] [app.common.flags :as flags] + [app.common.logging :as l] [app.common.schema :as sm] [app.common.version :as v] [cljs.core :as c] [cuerdas.core :as str])) +(l/set-level! :info) + (def ^:private defaults {:public-uri "http://localhost:3449" :tenant "default" @@ -30,7 +33,7 @@ [:map {:title "config"} [:secret-key :string] [:public-uri {:optional true} ::sm/uri] - [:management-api-key {:optional true} :string] + [:exporter-shared-key {:optional true} :string] [:host {:optional true} :string] [:tenant {:optional true} :string] [:flags {:optional true} [::sm/set :keyword]] @@ -98,8 +101,10 @@ (c/get config key default))) (def management-key - (or (c/get config :management-api-key) - (let [secret-key (c/get config :secret-key) - derived-key (crypto/hkdfSync "blake2b512" secret-key, "management" "" 32)] - (-> (.from buffer/Buffer derived-key) - (.toString "base64url"))))) + (let [key (or (c/get config :exporter-shared-key) + (let [secret-key (c/get config :secret-key) + derived-key (crypto/hkdfSync "blake2b512" secret-key, "exporter" "" 32)] + (-> (.from buffer/Buffer derived-key) + (.toString "base64url"))))] + (l/inf :hint "exporter key initialized" :key (d/obfuscate-string key)) + key)) diff --git a/exporter/src/app/handlers/resources.cljs b/exporter/src/app/handlers/resources.cljs index 5394e673a8..f0f655c498 100644 --- a/exporter/src/app/handlers/resources.cljs +++ b/exporter/src/app/handlers/resources.cljs @@ -73,7 +73,7 @@ (p/mcat (fn [blob] (let [fdata (new http/FormData) agent (new http/Agent #js {:connect #js {:rejectUnauthorized false}}) - headers #js {"X-Shared-Key" cf/management-key + headers #js {"X-Shared-Key" (str "exporter " cf/management-key) "Authorization" (str "Bearer " auth-token)} request #js {:headers headers