mirror of
https://github.com/penpot/penpot.git
synced 2026-09-10 04:38:51 -04:00
Compare commits
3
Commits
develop
...
issue-11573
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35fa4d895b | ||
|
|
b36eba673f | ||
|
|
b9e67f4304 |
No files matched your search
@@ -10,6 +10,7 @@
|
||||
[app.auth :refer [derive-password-weak]]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as ct]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
@@ -25,7 +26,35 @@
|
||||
(def ^:private
|
||||
schema:create-demo-profile
|
||||
[:map
|
||||
[:skip-onboarding {:optional true} ::sm/boolean]])
|
||||
[:skip-onboarding {:optional true} ::sm/boolean]
|
||||
[:expires-in {:optional true} ::ct/duration]])
|
||||
|
||||
(def ^:private min-expires-in
|
||||
(ct/duration "5m"))
|
||||
|
||||
(defn- resolve-deletion-delay
|
||||
"Resolve the effective `:demo-purge` delay for a demo profile. Without
|
||||
`expires-in` it falls back to the global deletion delay. Otherwise the
|
||||
value is only allowed to shorten the lifetime: below the 5 minutes
|
||||
minimum or above the global delay it raises a validation error."
|
||||
[expires-in]
|
||||
(let [max-delay (cf/get-deletion-delay)]
|
||||
(cond
|
||||
(nil? expires-in)
|
||||
max-delay
|
||||
|
||||
(ct/is-before? expires-in min-expires-in)
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-expires-in
|
||||
:hint "expires-in is below the 5 minutes minimum.")
|
||||
|
||||
(ct/is-after? expires-in max-delay)
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-expires-in
|
||||
:hint "expires-in exceeds the configured deletion delay.")
|
||||
|
||||
:else
|
||||
expires-in)))
|
||||
|
||||
(sv/defmethod ::create-demo-profile
|
||||
"A command that is responsible of creating a demo purpose
|
||||
@@ -34,42 +63,44 @@
|
||||
{::rpc/auth false
|
||||
::doc/added "1.15"
|
||||
::doc/changes [["1.15" "This method is migrated from mutations to commands."]
|
||||
["2.18" "Add optional `skip-onboarding` param. When true, the profile is created with `onboarding-viewed` and `release-notes-viewed` (current version) set, skipping the onboarding flow."]]
|
||||
["2.18" "Add optional `skip-onboarding` param. When true, the profile is created with `onboarding-viewed` and `release-notes-viewed` (current version) set, skipping the onboarding flow."]
|
||||
["2.18" "Add optional `expires-in` param. When set, the demo purge is scheduled that long after creation instead of the global deletion delay. Only values between 5 minutes and the global delay are accepted."]]
|
||||
::sm/params schema:create-demo-profile}
|
||||
[cfg {:keys [skip-onboarding]}]
|
||||
[cfg {:keys [skip-onboarding expires-in]}]
|
||||
|
||||
(when-not (contains? cf/flags :demo-users)
|
||||
(ex/raise :type :validation
|
||||
:code :demo-users-not-allowed
|
||||
:hint "Demo users are disabled by config."))
|
||||
|
||||
(let [sem (uuid/next)
|
||||
email (str "demo-" sem "@demo.example.com")
|
||||
fullname (str "Demo User " sem)
|
||||
(let [deletion-delay (resolve-deletion-delay expires-in)
|
||||
sem (uuid/next)
|
||||
email (str "demo-" sem "@demo.example.com")
|
||||
fullname (str "Demo User " sem)
|
||||
|
||||
password (-> (bn/random-bytes 16)
|
||||
(bc/bytes->b64 true)
|
||||
(bc/bytes->str))
|
||||
password (-> (bn/random-bytes 16)
|
||||
(bc/bytes->b64 true)
|
||||
(bc/bytes->str))
|
||||
|
||||
params {:email email
|
||||
:fullname fullname
|
||||
:is-active true
|
||||
:is-demo true
|
||||
:password (derive-password-weak password)
|
||||
:props (cond-> {}
|
||||
skip-onboarding (assoc :onboarding-viewed true
|
||||
;; Redundant today: auth/create-profile
|
||||
;; overwrites this with the current
|
||||
;; version, kept so the skip does not
|
||||
;; depend on that default.
|
||||
:release-notes-viewed (:main cf/version)))}
|
||||
profile (db/tx-run! cfg (fn [cfg]
|
||||
(->> (auth/create-profile cfg params)
|
||||
(auth/create-profile-rels cfg))))]
|
||||
params {:email email
|
||||
:fullname fullname
|
||||
:is-active true
|
||||
:is-demo true
|
||||
:password (derive-password-weak password)
|
||||
:props (cond-> {}
|
||||
skip-onboarding (assoc :onboarding-viewed true
|
||||
;; Redundant today: auth/create-profile
|
||||
;; overwrites this with the current
|
||||
;; version, kept so the skip does not
|
||||
;; depend on that default.
|
||||
:release-notes-viewed (:main cf/version)))}
|
||||
profile (db/tx-run! cfg (fn [cfg]
|
||||
(->> (auth/create-profile cfg params)
|
||||
(auth/create-profile-rels cfg))))]
|
||||
|
||||
(wrk/submit! (-> cfg
|
||||
(assoc ::wrk/task :demo-purge)
|
||||
(assoc ::wrk/delay (cf/get-deletion-delay))
|
||||
(assoc ::wrk/delay deletion-delay)
|
||||
(assoc ::wrk/params {:profile-id (:id profile)})))
|
||||
|
||||
(with-meta {:email email
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
(ns backend-tests.rpc-demo-test
|
||||
(:require
|
||||
[app.auth :as auth]
|
||||
[app.common.time :as ct]
|
||||
[app.config :as cf]
|
||||
[app.rpc.commands.profile :as profile]
|
||||
[app.worker :as wrk]
|
||||
[backend-tests.helpers :as th]
|
||||
[clojure.test :as t]))
|
||||
|
||||
@@ -74,3 +76,48 @@
|
||||
:skip-onboarding "yes"})]
|
||||
(t/is (th/ex-of-type? error :validation))
|
||||
(t/is (th/ex-of-code? error :params-validation)))))
|
||||
|
||||
(t/deftest create-demo-profile-uses-global-delay-by-default
|
||||
(with-redefs [cf/flags (conj cf/flags :demo-users)]
|
||||
(let [captured (atom nil)]
|
||||
(with-redefs [wrk/submit! (fn [& {:keys [::wrk/task ::wrk/delay]}]
|
||||
(reset! captured {:task task :delay delay}))]
|
||||
(let [{:keys [error result]} (th/command! {::th/type :create-demo-profile})]
|
||||
(t/is (nil? error))
|
||||
(t/is (some? (:email result)))
|
||||
(t/is (= :demo-purge (:task @captured)))
|
||||
(t/is (= (cf/get-deletion-delay) (:delay @captured))))))))
|
||||
|
||||
(t/deftest create-demo-profile-accepts-short-expires-in
|
||||
(with-redefs [cf/flags (conj cf/flags :demo-users)]
|
||||
(let [captured (atom nil)]
|
||||
(with-redefs [wrk/submit! (fn [& {:keys [::wrk/task ::wrk/delay]}]
|
||||
(reset! captured {:task task :delay delay}))]
|
||||
(let [{:keys [error result]} (th/command! {::th/type :create-demo-profile
|
||||
:expires-in "10m"})]
|
||||
(t/is (nil? error))
|
||||
(t/is (some? (:email result)))
|
||||
(t/is (= :demo-purge (:task @captured)))
|
||||
(t/is (= (ct/duration "10m") (:delay @captured))))))))
|
||||
|
||||
(t/deftest create-demo-profile-rejects-expires-in-below-minimum
|
||||
(with-redefs [cf/flags (conj cf/flags :demo-users)]
|
||||
(let [{:keys [error]} (th/command! {::th/type :create-demo-profile
|
||||
:expires-in "1m"})]
|
||||
(t/is (th/ex-of-type? error :validation))
|
||||
(t/is (th/ex-of-code? error :invalid-expires-in)))))
|
||||
|
||||
(t/deftest create-demo-profile-rejects-expires-in-above-global-delay
|
||||
(with-redefs [cf/flags (conj cf/flags :demo-users)
|
||||
cf/get-deletion-delay (fn [] (ct/duration {:days 7}))]
|
||||
(let [{:keys [error]} (th/command! {::th/type :create-demo-profile
|
||||
:expires-in "200h"})]
|
||||
(t/is (th/ex-of-type? error :validation))
|
||||
(t/is (th/ex-of-code? error :invalid-expires-in)))))
|
||||
|
||||
(t/deftest create-demo-profile-rejects-non-duration-expires-in
|
||||
(with-redefs [cf/flags (conj cf/flags :demo-users)]
|
||||
(let [{:keys [error]} (th/command! {::th/type :create-demo-profile
|
||||
:expires-in "yes"})]
|
||||
(t/is (th/ex-of-type? error :validation))
|
||||
(t/is (th/ex-of-code? error :params-validation)))))
|
||||
@@ -175,8 +175,14 @@
|
||||
|
||||
#?(:clj
|
||||
(defn parse-duration
|
||||
"Parse a value into a Duration. Total: returns the input unchanged
|
||||
when it cannot be parsed, so schema decoding never throws and
|
||||
invalid values fail validation with a clean params error instead."
|
||||
[s]
|
||||
(duration s)))
|
||||
(try
|
||||
(duration s)
|
||||
(catch Exception _
|
||||
s))))
|
||||
|
||||
#?(:clj
|
||||
(defn format-duration
|
||||
|
||||
@@ -14,3 +14,17 @@
|
||||
dtb (dt/inst 20000)]
|
||||
(t/is (false? (dt/is-after? dta dtb)))
|
||||
(t/is (true? (dt/is-before? dta dtb)))))
|
||||
|
||||
#?(:clj
|
||||
(t/deftest parse-duration-test
|
||||
(t/is (dt/duration? (dt/parse-duration "10m")))
|
||||
(t/is (= (dt/duration "10m") (dt/parse-duration "10m")))
|
||||
(t/is (= (dt/duration "1h") (dt/parse-duration "1h")))
|
||||
|
||||
;; Invalid values are returned unchanged instead of throwing, so
|
||||
;; they fail the `duration?` schema predicate with a clean
|
||||
;; validation error downstream.
|
||||
(t/is (= "yes" (dt/parse-duration "yes")))
|
||||
(t/is (not (dt/duration? (dt/parse-duration "yes"))))
|
||||
(t/is (= true (dt/parse-duration true)))
|
||||
(t/is (not (dt/duration? (dt/parse-duration true))))))
|
||||
Reference in new issue
Block a user