mirror of
https://github.com/penpot/penpot.git
synced 2026-01-27 07:42:03 -05:00
Compare commits
6 Commits
develop
...
niwinz-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f513c1d10 | ||
|
|
cafc4567b3 | ||
|
|
9ea7d0e243 | ||
|
|
da029de7e4 | ||
|
|
10c08c88c7 | ||
|
|
877b6630f3 |
@@ -1,7 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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
|
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_HOST=devenv
|
||||||
export PENPOT_PUBLIC_URI=https://localhost:3449
|
export PENPOT_PUBLIC_URI=https://localhost:3449
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,8 @@
|
|||||||
[:http-server-io-threads {:optional true} ::sm/int]
|
[:http-server-io-threads {:optional true} ::sm/int]
|
||||||
[:http-server-max-worker-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]
|
[:management-api-key {:optional true} :string]
|
||||||
|
|
||||||
[:telemetry-uri {:optional true} :string]
|
[:telemetry-uri {:optional true} :string]
|
||||||
|
|||||||
@@ -13,13 +13,13 @@
|
|||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.http.middleware :as mw]
|
|
||||||
[app.main :as-alias main]
|
[app.main :as-alias main]
|
||||||
[app.rpc.commands.profile :as cmd.profile]
|
[app.rpc.commands.profile :as cmd.profile]
|
||||||
[app.setup :as-alias setup]
|
[app.setup :as-alias setup]
|
||||||
[app.tokens :as tokens]
|
[app.tokens :as tokens]
|
||||||
[app.worker :as-alias wrk]
|
[app.worker :as-alias wrk]
|
||||||
[integrant.core :as ig]
|
[integrant.core :as ig]
|
||||||
|
[yetti.request :as yreq]
|
||||||
[yetti.response :as-alias yres]))
|
[yetti.response :as-alias yres]))
|
||||||
|
|
||||||
;; ---- ROUTES
|
;; ---- ROUTES
|
||||||
@@ -49,28 +49,40 @@
|
|||||||
(fn [cfg request]
|
(fn [cfg request]
|
||||||
(db/tx-run! cfg handler 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
|
(defmethod ig/init-key ::routes
|
||||||
[_ {:keys [::setup/props] :as cfg}]
|
[_ cfg]
|
||||||
|
|
||||||
(let [management-key (or (cf/get :management-api-key)
|
["" {:middleware [[shared-key-auth (cf/get :management-api-key)]
|
||||||
(get props :management-key))]
|
[default-system cfg]
|
||||||
|
[transaction]]}
|
||||||
|
["/authenticate"
|
||||||
|
{:handler authenticate
|
||||||
|
:allowed-methods #{:post}}]
|
||||||
|
|
||||||
["" {:middleware [[mw/shared-key-auth management-key]
|
["/get-customer"
|
||||||
[default-system cfg]
|
{:handler get-customer
|
||||||
[transaction]]}
|
:transaction true
|
||||||
["/authenticate"
|
:allowed-methods #{:post}}]
|
||||||
{:handler authenticate
|
|
||||||
:allowed-methods #{:post}}]
|
|
||||||
|
|
||||||
["/get-customer"
|
["/update-customer"
|
||||||
{:handler get-customer
|
{:handler update-customer
|
||||||
:transaction true
|
:allowed-methods #{:post}
|
||||||
:allowed-methods #{:post}}]
|
:transaction true}]])
|
||||||
|
|
||||||
["/update-customer"
|
|
||||||
{:handler update-customer
|
|
||||||
:allowed-methods #{:post}
|
|
||||||
:transaction true}]]))
|
|
||||||
|
|
||||||
;; ---- HELPERS
|
;; ---- HELPERS
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
[app.http.errors :as errors]
|
[app.http.errors :as errors]
|
||||||
[app.tokens :as tokens]
|
[app.tokens :as tokens]
|
||||||
[app.util.pointer-map :as pmap]
|
[app.util.pointer-map :as pmap]
|
||||||
[buddy.core.codecs :as bc]
|
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[yetti.adapter :as yt]
|
[yetti.adapter :as yt]
|
||||||
[yetti.middleware :as ymw]
|
[yetti.middleware :as ymw]
|
||||||
@@ -301,16 +300,20 @@
|
|||||||
:compile (constantly wrap-auth)})
|
:compile (constantly wrap-auth)})
|
||||||
|
|
||||||
(defn- wrap-shared-key-auth
|
(defn- wrap-shared-key-auth
|
||||||
[handler shared-key]
|
[handler keys]
|
||||||
(if shared-key
|
(if (seq keys)
|
||||||
(let [shared-key (if (string? shared-key)
|
(fn [request]
|
||||||
shared-key
|
(if-let [[key-id key] (some-> (yreq/get-header request "x-shared-key")
|
||||||
(bc/bytes->b64-str shared-key true))]
|
(str/split #"\s+" 2))]
|
||||||
(fn [request]
|
(let [key-id (str/lower key-id)]
|
||||||
(let [key (yreq/get-header request "x-shared-key")]
|
(if (and (string? key)
|
||||||
(if (= key shared-key)
|
(contains? keys key-id)
|
||||||
(handler (assoc request ::http/auth-with-shared-key true))
|
(= key (get keys key-id)))
|
||||||
{::yres/status 403}))))
|
(-> request
|
||||||
|
(assoc ::http/auth-key-id key-id)
|
||||||
|
(handler))
|
||||||
|
{::yres/status 403}))
|
||||||
|
{::yres/status 403}))
|
||||||
(fn [_ _]
|
(fn [_ _]
|
||||||
{::yres/status 403})))
|
{::yres/status 403})))
|
||||||
|
|
||||||
|
|||||||
@@ -140,10 +140,14 @@
|
|||||||
client-version (get-client-version request)
|
client-version (get-client-version request)
|
||||||
client-user-agent (get-client-user-agent request)
|
client-user-agent (get-client-user-agent request)
|
||||||
session-id (get-external-session-id 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
|
(d/without-nils
|
||||||
{:external-session-id session-id
|
{:external-session-id session-id
|
||||||
|
:initiator (or key-id "app")
|
||||||
:access-token-id (some-> token-id str)
|
:access-token-id (some-> token-id str)
|
||||||
|
:access-token-type (some-> token-type str)
|
||||||
:client-event-origin client-event-origin
|
:client-event-origin client-event-origin
|
||||||
:client-user-agent client-user-agent
|
:client-user-agent client-user-agent
|
||||||
:client-version client-version
|
:client-version client-version
|
||||||
|
|||||||
@@ -275,8 +275,7 @@
|
|||||||
::email/whitelist (ig/ref ::email/whitelist)}
|
::email/whitelist (ig/ref ::email/whitelist)}
|
||||||
|
|
||||||
::mgmt/routes
|
::mgmt/routes
|
||||||
{::db/pool (ig/ref ::db/pool)
|
{::db/pool (ig/ref ::db/pool)}
|
||||||
::setup/props (ig/ref ::setup/props)}
|
|
||||||
|
|
||||||
:app.http/router
|
:app.http/router
|
||||||
{::session/manager (ig/ref ::session/manager)
|
{::session/manager (ig/ref ::session/manager)
|
||||||
@@ -341,7 +340,8 @@
|
|||||||
::email/whitelist (ig/ref ::email/whitelist)}
|
::email/whitelist (ig/ref ::email/whitelist)}
|
||||||
|
|
||||||
:app.nitrate/client
|
: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
|
:app.rpc/management-methods
|
||||||
{::http.client/client (ig/ref ::http.client/client)
|
{::http.client/client (ig/ref ::http.client/client)
|
||||||
@@ -357,13 +357,13 @@
|
|||||||
::setup/props (ig/ref ::setup/props)}
|
::setup/props (ig/ref ::setup/props)}
|
||||||
|
|
||||||
::rpc/routes
|
::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)
|
::rpc/management-methods (ig/ref :app.rpc/management-methods)
|
||||||
|
|
||||||
;; FIXME: revisit if db/pool is necessary here
|
;; FIXME: revisit if db/pool is necessary here
|
||||||
::db/pool (ig/ref ::db/pool)
|
::db/pool (ig/ref ::db/pool)
|
||||||
::session/manager (ig/ref ::session/manager)
|
::session/manager (ig/ref ::session/manager)
|
||||||
::setup/props (ig/ref ::setup/props)}
|
::setup/shared-keys (ig/ref ::setup/shared-keys)}
|
||||||
|
|
||||||
::wrk/registry
|
::wrk/registry
|
||||||
{::mtx/metrics (ig/ref ::mtx/metrics)
|
{::mtx/metrics (ig/ref ::mtx/metrics)
|
||||||
@@ -451,6 +451,11 @@
|
|||||||
;; module requires the migrations to run before initialize.
|
;; module requires the migrations to run before initialize.
|
||||||
::migrations (ig/ref :app.migrations/migrations)}
|
::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
|
::setup/clock
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
(ns app.nitrate
|
||||||
"Module that make calls to the external nitrate aplication"
|
"Module that make calls to the external nitrate aplication"
|
||||||
(:require
|
(:require
|
||||||
@@ -17,18 +11,17 @@
|
|||||||
[clojure.core :as c]
|
[clojure.core :as c]
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; HELPERS
|
;; HELPERS
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(defn- request-builder
|
(defn- request-builder
|
||||||
[cfg method uri management-key profile-id]
|
[cfg method uri shared-key profile-id]
|
||||||
(fn []
|
(fn []
|
||||||
(http/req! cfg {:method method
|
(http/req! cfg {:method method
|
||||||
:headers {"content-type" "application/json"
|
:headers {"content-type" "application/json"
|
||||||
"accept" "application/json"
|
"accept" "application/json"
|
||||||
"x-shared-key" management-key
|
"x-shared-key" shared-key
|
||||||
"x-profile-id" (str profile-id)}
|
"x-profile-id" (str profile-id)}
|
||||||
:uri uri
|
:uri uri
|
||||||
:version :http1.1})))
|
:version :http1.1})))
|
||||||
@@ -54,9 +47,9 @@
|
|||||||
|
|
||||||
(defn- with-validate [handler uri schema]
|
(defn- with-validate [handler uri schema]
|
||||||
(fn []
|
(fn []
|
||||||
(let [coercer-http (sm/coercer schema
|
(let [coercer-http (sm/coercer schema
|
||||||
:type :validation
|
:type :validation
|
||||||
:hint (str "invalid data received calling " uri))]
|
:hint (str "invalid data received calling " uri))]
|
||||||
(try
|
(try
|
||||||
(coercer-http (-> (handler) :body json/decode))
|
(coercer-http (-> (handler) :body json/decode))
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
@@ -65,8 +58,9 @@
|
|||||||
nil)))))
|
nil)))))
|
||||||
|
|
||||||
(defn- request-to-nitrate
|
(defn- request-to-nitrate
|
||||||
[{:keys [::management-key] :as cfg} method uri schema {:keys [::rpc/profile-id] :as params}]
|
[cfg method uri schema {:keys [::rpc/profile-id] :as params}]
|
||||||
(let [full-http-call (-> (request-builder cfg method uri management-key profile-id)
|
(let [shared-key (-> cfg ::setup/shared-keys :nitrate)
|
||||||
|
full-http-call (-> (request-builder cfg method uri shared-key profile-id)
|
||||||
(with-retries 3)
|
(with-retries 3)
|
||||||
(with-validate uri schema))]
|
(with-validate uri schema))]
|
||||||
(full-http-call)))
|
(full-http-call)))
|
||||||
@@ -103,26 +97,15 @@
|
|||||||
(let [baseuri (cf/get :nitrate-backend-uri)]
|
(let [baseuri (cf/get :nitrate-backend-uri)]
|
||||||
(request-to-nitrate cfg :get (str baseuri "/api/users/" (str profile-id)) schema:user params)))
|
(request-to-nitrate cfg :get (str baseuri "/api/users/" (str profile-id)) schema:user params)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; INITIALIZATION
|
;; INITIALIZATION
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(defmethod ig/init-key ::client
|
(defmethod ig/init-key ::client
|
||||||
[_ {:keys [::setup/props] :as cfg}]
|
[_ cfg]
|
||||||
(if (contains? cf/flags :nitrate)
|
(when (contains? cf/flags :nitrate)
|
||||||
(let [management-key (or (cf/get :management-api-key)
|
{:get-team-org (partial get-team-org cfg)
|
||||||
(get props :management-key))
|
:is-valid-user (partial is-valid-user cfg)}))
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; UTILS
|
;; UTILS
|
||||||
|
|||||||
@@ -92,11 +92,11 @@
|
|||||||
(fn [{:keys [params path-params method] :as request}]
|
(fn [{:keys [params path-params method] :as request}]
|
||||||
(let [handler-name (:type path-params)
|
(let [handler-name (:type path-params)
|
||||||
etag (yreq/get-header request "if-none-match")
|
etag (yreq/get-header request "if-none-match")
|
||||||
|
|
||||||
|
key-id (get request ::http/auth-key-id)
|
||||||
profile-id (or (::session/profile-id request)
|
profile-id (or (::session/profile-id request)
|
||||||
(::actoken/profile-id request)
|
(::actoken/profile-id request)
|
||||||
(if (::http/auth-with-shared-key request)
|
(if key-id uuid/zero nil))
|
||||||
uuid/zero
|
|
||||||
nil))
|
|
||||||
|
|
||||||
ip-addr (inet/parse-request request)
|
ip-addr (inet/parse-request request)
|
||||||
|
|
||||||
@@ -298,11 +298,12 @@
|
|||||||
|
|
||||||
(defn- resolve-management-methods
|
(defn- resolve-management-methods
|
||||||
[cfg]
|
[cfg]
|
||||||
(let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing)]
|
(let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing)
|
||||||
(->> (sv/scan-ns
|
mods (cond->> (list 'app.rpc.management.exporter)
|
||||||
'app.rpc.management.subscription
|
(contains? cf/flags :nitrate)
|
||||||
'app.rpc.management.nitrate
|
(cons 'app.rpc.management.nitrate))]
|
||||||
'app.rpc.management.exporter)
|
|
||||||
|
(->> (apply sv/scan-ns mods)
|
||||||
(map (partial process-method cfg "management" wrap-management))
|
(map (partial process-method cfg "management" wrap-management))
|
||||||
(into {}))))
|
(into {}))))
|
||||||
|
|
||||||
@@ -346,23 +347,20 @@
|
|||||||
|
|
||||||
(defmethod ig/assert-key ::routes
|
(defmethod ig/assert-key ::routes
|
||||||
[_ params]
|
[_ params]
|
||||||
|
(assert (map? (::setup/shared-keys params)))
|
||||||
(assert (db/pool? (::db/pool params)) "expect valid database pool")
|
(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 (session/manager? (::session/manager params)) "expect valid session manager")
|
||||||
(assert (valid-methods? (::methods params)) "expect valid methods map")
|
(assert (valid-methods? (::methods params)) "expect valid methods map")
|
||||||
(assert (valid-methods? (::management-methods params)) "expect valid methods map"))
|
(assert (valid-methods? (::management-methods params)) "expect valid methods map"))
|
||||||
|
|
||||||
(defmethod ig/init-key ::routes
|
(defmethod ig/init-key ::routes
|
||||||
[_ {:keys [::methods ::management-methods ::setup/props] :as cfg}]
|
[_ {:keys [::methods ::management-methods ::setup/shared-keys] :as cfg}]
|
||||||
|
|
||||||
(let [public-uri (cf/get :public-uri)
|
|
||||||
management-key (or (cf/get :management-api-key)
|
|
||||||
(get props :management-key))]
|
|
||||||
|
|
||||||
|
(let [public-uri (cf/get :public-uri)]
|
||||||
["/api"
|
["/api"
|
||||||
["/management"
|
["/management"
|
||||||
["/methods/:type"
|
["/methods/:type"
|
||||||
{:middleware [[mw/shared-key-auth management-key]
|
{:middleware [[mw/shared-key-auth shared-keys]
|
||||||
[session/authz cfg]]
|
[session/authz cfg]]
|
||||||
:handler (make-rpc-handler management-methods)}]
|
:handler (make-rpc-handler management-methods)}]
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,12 @@
|
|||||||
;; Copyright (c) KALEIDOS INC
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
(ns app.rpc.management.nitrate
|
(ns app.rpc.management.nitrate
|
||||||
"Internal Nitrate HTTP API.
|
"Internal Nitrate HTTP RPC API. Provides authenticated access to
|
||||||
Provides authenticated access to organization management and token validation endpoints.
|
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."
|
|
||||||
(:require
|
(:require
|
||||||
[app.common.schema :as sm]
|
[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.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
@@ -23,22 +22,14 @@
|
|||||||
[app.util.services :as sv]))
|
[app.util.services :as sv]))
|
||||||
|
|
||||||
;; ---- API: authenticate
|
;; ---- API: authenticate
|
||||||
(def ^:private schema:profile
|
|
||||||
[:map
|
|
||||||
[:id ::sm/uuid]
|
|
||||||
[:name :string]
|
|
||||||
[:email :string]
|
|
||||||
[:photo-url :string]])
|
|
||||||
|
|
||||||
(sv/defmethod ::authenticate
|
(sv/defmethod ::authenticate
|
||||||
"Authenticate an user
|
"Authenticate the current user"
|
||||||
@api GET /authenticate
|
{::doc/added "2.14"
|
||||||
@returns
|
::sm/params [:map]
|
||||||
200 OK: Returns the authenticated user."
|
|
||||||
{::doc/added "2.12"
|
|
||||||
::sm/result schema:profile}
|
::sm/result schema:profile}
|
||||||
[cfg {:keys [::rpc/profile-id] :as params}]
|
[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)
|
{:id (get profile :id)
|
||||||
:name (get profile :fullname)
|
:name (get profile :fullname)
|
||||||
:email (get profile :email)
|
:email (get profile :email)
|
||||||
@@ -51,30 +42,22 @@
|
|||||||
FROM team AS t
|
FROM team AS t
|
||||||
JOIN team_profile_rel AS tpr ON t.id = tpr.team_id
|
JOIN team_profile_rel AS tpr ON t.id = tpr.team_id
|
||||||
WHERE tpr.profile_id = ?
|
WHERE tpr.profile_id = ?
|
||||||
AND tpr.is_owner = 't'
|
AND tpr.is_owner IS TRUE
|
||||||
AND t.is_default = 'f'
|
AND t.is_default IS FALSE
|
||||||
AND t.deleted_at is null;")
|
AND t.deleted_at IS NULL;")
|
||||||
|
|
||||||
(def ^:private schema:team
|
|
||||||
[:map
|
|
||||||
[:id ::sm/uuid]
|
|
||||||
[:name :string]])
|
|
||||||
|
|
||||||
(def ^:private schema:get-teams-result
|
(def ^:private schema:get-teams-result
|
||||||
[:vector schema:team])
|
[:vector schema:team])
|
||||||
|
|
||||||
(sv/defmethod ::get-teams
|
(sv/defmethod ::get-teams
|
||||||
"List teams for which current user is owner.
|
"List teams for which current user is owner"
|
||||||
@api GET /get-teams
|
{::doc/added "2.14"
|
||||||
@returns
|
::sm/params [:map]
|
||||||
200 OK: Returns the list of teams for the user."
|
|
||||||
{::doc/added "2.12"
|
|
||||||
::sm/result schema:get-teams-result}
|
::sm/result schema:get-teams-result}
|
||||||
[cfg {:keys [::rpc/profile-id]}]
|
[cfg {:keys [::rpc/profile-id]}]
|
||||||
(when (contains? cf/flags :nitrate)
|
(let [current-user-id (-> (profile/get-profile cfg profile-id) :id)]
|
||||||
(let [current-user-id (-> (profile/get-profile cfg profile-id) :id)]
|
(->> (db/exec! cfg [sql:get-teams current-user-id])
|
||||||
(->> (db/exec! cfg [sql:get-teams current-user-id])
|
(map #(select-keys % [:id :name])))))
|
||||||
(map #(select-keys % [:id :name]))))))
|
|
||||||
|
|
||||||
;; ---- API: notify-team-change
|
;; ---- API: notify-team-change
|
||||||
|
|
||||||
@@ -83,30 +66,18 @@
|
|||||||
[:id ::sm/uuid]
|
[:id ::sm/uuid]
|
||||||
[:organization-id ::sm/text]])
|
[:organization-id ::sm/text]])
|
||||||
|
|
||||||
|
|
||||||
(sv/defmethod ::notify-team-change
|
(sv/defmethod ::notify-team-change
|
||||||
"Notify to Penpot a team change from nitrate
|
"Notify to Penpot a team change from nitrate"
|
||||||
@api POST /notify-team-change
|
{::doc/added "2.14"
|
||||||
@returns
|
|
||||||
200 OK"
|
|
||||||
{::doc/added "2.12"
|
|
||||||
::sm/params schema:notify-team-change
|
::sm/params schema:notify-team-change
|
||||||
::rpc/auth false}
|
::rpc/auth false}
|
||||||
[cfg {:keys [id organization-id organization-name]}]
|
[cfg {:keys [id organization-id organization-name]}]
|
||||||
(when (contains? cf/flags :nitrate)
|
(let [msgbus (::mbus/msgbus cfg)]
|
||||||
(let [msgbus (::mbus/msgbus cfg)]
|
(mbus/pub! msgbus
|
||||||
(mbus/pub! msgbus
|
;;TODO There is a bug on dashboard with teams notifications.
|
||||||
;;TODO There is a bug on dashboard with teams notifications.
|
;;For now we send it to uuid/zero instead of team-id
|
||||||
;;For now we send it to uuid/zero instead of team-id
|
:topic uuid/zero
|
||||||
:topic uuid/zero
|
:message {:type :team-org-change
|
||||||
:message {:type :team-org-change
|
:team-id id
|
||||||
:team-id id
|
:organization-id organization-id
|
||||||
:organization-id organization-id
|
:organization-name organization-name})))
|
||||||
:organization-name organization-name}))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
[app.setup.templates]
|
[app.setup.templates]
|
||||||
[buddy.core.codecs :as bc]
|
[buddy.core.codecs :as bc]
|
||||||
[buddy.core.nonce :as bn]
|
[buddy.core.nonce :as bn]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
|
||||||
(defn- generate-random-key
|
(defn- generate-random-key
|
||||||
@@ -88,7 +89,38 @@
|
|||||||
(-> (get-all-props conn)
|
(-> (get-all-props conn)
|
||||||
(assoc :secret-key secret)
|
(assoc :secret-key secret)
|
||||||
(assoc :tokens-key (keys/derive secret :salt "tokens"))
|
(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)))))))
|
(update :instance-id handle-instance-id conn (db/read-only? pool)))))))
|
||||||
|
|
||||||
(sm/register! ::props [:map-of :keyword ::sm/any])
|
(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)))})))
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
(t/deftest shared-key-auth
|
(t/deftest shared-key-auth
|
||||||
(let [handler (#'app.http.middleware/wrap-shared-key-auth
|
(let [handler (#'app.http.middleware/wrap-shared-key-auth
|
||||||
(fn [req] {::yres/status 200})
|
(fn [req] {::yres/status 200})
|
||||||
"secret-key")]
|
{"test1" "secret-key"})]
|
||||||
|
|
||||||
(let [response (handler (->DummyRequest {} {}))]
|
(let [response (handler (->DummyRequest {} {}))]
|
||||||
(t/is (= 403 (::yres/status response))))
|
(t/is (= 403 (::yres/status response))))
|
||||||
@@ -95,6 +95,9 @@
|
|||||||
(t/is (= 403 (::yres/status response))))
|
(t/is (= 403 (::yres/status response))))
|
||||||
|
|
||||||
(let [response (handler (->DummyRequest {"x-shared-key" "secret-key"} {}))]
|
(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/is (= 200 (::yres/status response))))))
|
||||||
|
|
||||||
(t/deftest access-token-authz
|
(t/deftest access-token-authz
|
||||||
|
|||||||
@@ -19,3 +19,10 @@
|
|||||||
|
|
||||||
(def schema:role
|
(def schema:role
|
||||||
[::sm/one-of {:title "TeamRole"} valid-roles])
|
[::sm/one-of {:title "TeamRole"} valid-roles])
|
||||||
|
|
||||||
|
;; FIXME: specify more fields
|
||||||
|
(def schema:team
|
||||||
|
[:map {:title "Team"}
|
||||||
|
[:id ::sm/uuid]
|
||||||
|
[:name :string]])
|
||||||
|
|
||||||
|
|||||||
@@ -398,7 +398,6 @@ COPY files/Caddyfile /home/
|
|||||||
COPY files/selfsigned.crt /home/
|
COPY files/selfsigned.crt /home/
|
||||||
COPY files/selfsigned.key /home/
|
COPY files/selfsigned.key /home/
|
||||||
COPY files/start-tmux.sh /home/start-tmux.sh
|
COPY files/start-tmux.sh /home/start-tmux.sh
|
||||||
COPY files/start-tmux-back.sh /home/start-tmux-back.sh
|
|
||||||
COPY files/entrypoint.sh /home/entrypoint.sh
|
COPY files/entrypoint.sh /home/entrypoint.sh
|
||||||
COPY files/init.sh /home/init.sh
|
COPY files/init.sh /home/init.sh
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
localhost:3449 {
|
localhost:3449 {
|
||||||
reverse_proxy localhost:4449
|
reverse_proxy localhost:4449
|
||||||
tls /home/selfsigned.crt /home/selfsigned.key
|
tls /home/selfsigned.crt /home/selfsigned.key
|
||||||
|
header -Strict-Transport-Security
|
||||||
}
|
}
|
||||||
|
|
||||||
http://localhost:3450 {
|
http://localhost:3450 {
|
||||||
|
|||||||
@@ -12,11 +12,14 @@
|
|||||||
["node:process" :as process]
|
["node:process" :as process]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.flags :as flags]
|
[app.common.flags :as flags]
|
||||||
|
[app.common.logging :as l]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.version :as v]
|
[app.common.version :as v]
|
||||||
[cljs.core :as c]
|
[cljs.core :as c]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
(l/set-level! :info)
|
||||||
|
|
||||||
(def ^:private defaults
|
(def ^:private defaults
|
||||||
{:public-uri "http://localhost:3449"
|
{:public-uri "http://localhost:3449"
|
||||||
:tenant "default"
|
:tenant "default"
|
||||||
@@ -30,7 +33,7 @@
|
|||||||
[:map {:title "config"}
|
[:map {:title "config"}
|
||||||
[:secret-key :string]
|
[:secret-key :string]
|
||||||
[:public-uri {:optional true} ::sm/uri]
|
[:public-uri {:optional true} ::sm/uri]
|
||||||
[:management-api-key {:optional true} :string]
|
[:exporter-shared-key {:optional true} :string]
|
||||||
[:host {:optional true} :string]
|
[:host {:optional true} :string]
|
||||||
[:tenant {:optional true} :string]
|
[:tenant {:optional true} :string]
|
||||||
[:flags {:optional true} [::sm/set :keyword]]
|
[:flags {:optional true} [::sm/set :keyword]]
|
||||||
@@ -98,8 +101,10 @@
|
|||||||
(c/get config key default)))
|
(c/get config key default)))
|
||||||
|
|
||||||
(def management-key
|
(def management-key
|
||||||
(or (c/get config :management-api-key)
|
(let [key (or (c/get config :exporter-shared-key)
|
||||||
(let [secret-key (c/get config :secret-key)
|
(let [secret-key (c/get config :secret-key)
|
||||||
derived-key (crypto/hkdfSync "blake2b512" secret-key, "management" "" 32)]
|
derived-key (crypto/hkdfSync "blake2b512" secret-key, "exporter" "" 32)]
|
||||||
(-> (.from buffer/Buffer derived-key)
|
(-> (.from buffer/Buffer derived-key)
|
||||||
(.toString "base64url")))))
|
(.toString "base64url"))))]
|
||||||
|
(l/inf :hint "exporter key initialized" :key (d/obfuscate-string key))
|
||||||
|
key))
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
(p/mcat (fn [blob]
|
(p/mcat (fn [blob]
|
||||||
(let [fdata (new http/FormData)
|
(let [fdata (new http/FormData)
|
||||||
agent (new http/Agent #js {:connect #js {:rejectUnauthorized false}})
|
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)}
|
"Authorization" (str "Bearer " auth-token)}
|
||||||
|
|
||||||
request #js {:headers headers
|
request #js {:headers headers
|
||||||
|
|||||||
Reference in New Issue
Block a user