This commit is contained in:
Andrey Antukh 2026-05-08 20:32:59 +02:00
parent d58c1b88a9
commit a04a6dd34c
4 changed files with 100 additions and 49 deletions

View File

@ -31,6 +31,25 @@
jakarta.mail.Transport
java.util.Properties))
(defn clean
"Clean and normalizes email address string"
[email]
(let [email (str/lower email)
email (if (str/starts-with? email "mailto:")
(subs email 7)
email)
email (if (or (str/starts-with? email "<")
(str/ends-with? email ">"))
(str/trim email "<>")
email)]
email))
(defn get-domain
[email]
(let [email (clean email)
[_ domain] (str/split email "@" 2)]
domain))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EMAIL IMPL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -16,6 +16,7 @@
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.email :as email]
[app.http :as-alias http]
[app.http.access-token :as-alias actoken]
[app.loggers.audit.tasks :as-alias tasks]
@ -33,6 +34,23 @@
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def profile-props
[:id
:is-active
:is-muted
:auth-backend
:email
:default-team-id
:default-project-id
:fullname
:lang])
(def reserved-props
#{:session-id
:password
:old-password
:token})
(defn extract-utm-params
"Extracts additional data from params and namespace them under
`penpot` ns."
@ -47,17 +65,6 @@
(assoc (->> sk str/kebab (keyword "penpot")) v))))]
(reduce-kv process-param {} params)))
(def profile-props
[:id
:is-active
:is-muted
:auth-backend
:email
:default-team-id
:default-project-id
:fullname
:lang])
(defn profile->props
[profile]
(-> profile
@ -65,12 +72,6 @@
(merge (:props profile))
(d/without-nils)))
(def reserved-props
#{:session-id
:password
:old-password
:token})
(defn clean-props
[props]
(into {}
@ -238,15 +239,17 @@
(some? tnow)
(assoc :tracked-at tnow))))
(def ^:private xf:filter-safe-props
(def ^:private xf:filter-telemetry-props
"Transducer that keeps only map entries whose values are UUIDs."
(filter (fn [[_ v]] (uuid? v))))
(filter (fn [[k v]]
(and (simple-keyword? k)
(or (uuid? v) (boolean? v) (number? v))))))
(defn filter-safe-props
(defn filter-telemetry-props
"Return only UUID-valued keys from a props map. This preserves
object relations while maintaining anonymity."
[props]
(into {} xf:filter-safe-props props))
(into {} xf:filter-telemetry-props props))
(defn- append-audit-entry
[cfg params]
@ -256,6 +259,32 @@
(update :ip-addr db/inet))]
(db/insert! cfg :audit-log params)))
(defn- get-telemetry-context
"Get a telemetry ready safe context"
[{:keys [context]}]
(select-keys context [:initiator
:version
:client-version
:client-user-agent]))
(defn- get-telemetry-props
"A telemetry specific safe props"
[{:keys [name props] :as params}]
(cond
(or (= name "login-with-oidc")
(= name "login-with-password")
(= name "register-profile")
(= name "update-profile"))
(-> (select-keys props profile-props)
(filter-telemetry-props)
(assoc :lang (:lang props))
(assoc :auth-backend (:auth-backend props))
(assoc :email-domain (email/get-domain (:email props)))
(d/without-nils))
:else
(filter-telemetry-props props)))
(defn- handle-event!
[cfg event]
(let [tnow (ct/now)
@ -291,20 +320,15 @@
;; kept: initiator, version, client-version and client-user-agent.
;; Timestamps are truncated to day precision to avoid leaking exact
;; event timing.
(let [tday (ct/truncate tnow :days)
safe-context (-> (:context params {})
(select-keys [:initiator
:version
:client-version
:client-user-agent]))
safe-props (filter-safe-props (:props params {}))
params (-> params
(assoc :source "telemetry:backend")
(assoc :props safe-props)
(assoc :context safe-context)
(assoc :ip-addr (db/inet "0.0.0.0"))
(assoc :created-at tday)
(assoc :tracked-at tday))]
(let [event-name (get params :name)
tday (ct/truncate tnow :days)
params (-> params
(assoc :source "telemetry:backend")
(assoc :props (get-telemetry-props params))
(assoc :context (get-telemetry-context params))
(assoc :ip-addr (db/inet "0.0.0.0"))
(assoc :created-at tday)
(assoc :tracked-at tday))]
(append-audit-entry cfg params))))
(when (and (contains? cf/flags :webhooks)

View File

@ -124,8 +124,12 @@
(comp
(map adjust-timestamp)
(map (fn [event]
(let [tday (ct/truncate (::audit/created-at event) :days)
safe-props (audit/filter-safe-props (::audit/props event {}))]
(let [tday (-> (::audit/created-at event)
(ct/truncate :days))
safe-props (-> (::audit/props event {})
(audit/filter-telemetry-props))
safe-context (-> (::audit/context event)
(select-keys safe-context-keys))]
[(::audit/id event)
(::audit/name event)
"telemetry:frontend"
@ -135,7 +139,7 @@
(::audit/profile-id event)
(db/inet "0.0.0.0")
(db/tjson safe-props)
(db/tjson (filter-safe-context (::audit/context event {})))])))))
(db/tjson safe-context)])))))
(defn- handle-events
[{:keys [::db/pool] :as cfg} params]

View File

@ -61,26 +61,30 @@
(rx/of (dcm/go-to-dashboard-recent {:team-id team-id})))))))]
(ptk/reify ::logged-in
ev/Event
(-data [_]
{::ev/name "signin"
::ev/type "identify"
:email (:email profile)
:auth-backend (:auth-backend profile)
:fullname (:fullname profile)
:is-muted (:is-muted profile)
:default-team-id (:default-team-id profile)
:default-project-id (:default-project-id profile)})
ptk/WatchEvent
(watch [_ _ stream]
(cf/initialize-external-context-info)
(->> (rx/merge
(rx/of (dp/set-profile profile)
(ws/initialize)
(dtm/fetch-teams))
;; We schedule this event to be executed a bit later,
;; when the profile is already set
(->> (rx/of (ev/event {::ev/name "signin"
::ev/type "identify"
:id (:id profile)
:email (:email profile)
:auth-backend (:auth-backend profile)
:fullname (:fullname profile)
:is-muted (:is-muted profile)
:default-team-id (:default-team-id profile)
:default-project-id (:default-project-id profile)}))
(rx/observe-on :async))
(->> stream
(rx/filter (ptk/type? ::dtm/teams-fetched))
(rx/take 1)