mirror of
https://github.com/penpot/penpot.git
synced 2026-08-07 13:29:07 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
614d619173
@ -48,6 +48,7 @@
|
|||||||
|
|
||||||
buddy/buddy-hashers {:mvn/version "2.0.167"}
|
buddy/buddy-hashers {:mvn/version "2.0.167"}
|
||||||
buddy/buddy-sign {:mvn/version "3.6.1-359"}
|
buddy/buddy-sign {:mvn/version "3.6.1-359"}
|
||||||
|
org.passay/passay {:mvn/version "1.6.6"}
|
||||||
|
|
||||||
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.4"}
|
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.4"}
|
||||||
|
|
||||||
|
|||||||
53
backend/src/app/auth/passwords.clj
Normal file
53
backend/src/app/auth/passwords.clj
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
;; 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 Sucursal en España SL
|
||||||
|
|
||||||
|
(ns app.auth.passwords
|
||||||
|
"Password strength validation using Passay library."
|
||||||
|
(:require
|
||||||
|
[app.common.exceptions :as ex])
|
||||||
|
(:import
|
||||||
|
[org.passay CharacterCharacteristicsRule CharacterRule EnglishCharacterData PasswordData]))
|
||||||
|
|
||||||
|
(defonce ^:private passay-code->translation-key
|
||||||
|
{"INSUFFICIENT_LOWERCASE" "errors.weak-password.insufficient-lowercase"
|
||||||
|
"INSUFFICIENT_UPPERCASE" "errors.weak-password.insufficient-uppercase"
|
||||||
|
"INSUFFICIENT_DIGIT" "errors.weak-password.insufficient-digits"
|
||||||
|
"INSUFFICIENT_SPECIAL" "errors.weak-password.insufficient-special"})
|
||||||
|
|
||||||
|
(defonce ^:private character-characteristics-rule
|
||||||
|
(doto (CharacterCharacteristicsRule.)
|
||||||
|
(.setRules [(CharacterRule. EnglishCharacterData/LowerCase 1)
|
||||||
|
(CharacterRule. EnglishCharacterData/UpperCase 1)
|
||||||
|
(CharacterRule. EnglishCharacterData/Digit 1)
|
||||||
|
(CharacterRule. EnglishCharacterData/Special 1)])
|
||||||
|
(.setNumberOfCharacteristics 4)))
|
||||||
|
|
||||||
|
(defn validate-password
|
||||||
|
"Validates password strength.
|
||||||
|
Returns nil if valid, or raises exception if invalid.
|
||||||
|
Checks:
|
||||||
|
- Minimum length of 8 characters
|
||||||
|
- At least 1 lowercase letter
|
||||||
|
- At least 1 uppercase letter
|
||||||
|
- At least 1 digit
|
||||||
|
- At least 1 special character"
|
||||||
|
[password]
|
||||||
|
(when (< (count password) 8)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :weak-password
|
||||||
|
:hint "password must be at least 8 characters"
|
||||||
|
:details ["errors.weak-password.too-short"]))
|
||||||
|
|
||||||
|
(let [password-data (PasswordData. password)
|
||||||
|
char-result (.validate character-characteristics-rule password-data)]
|
||||||
|
(when-not (.isValid char-result)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :weak-password
|
||||||
|
:hint "password must contain at least 1 lowercase letter, 1 uppercase letter, 1 digit, and 1 special character"
|
||||||
|
:details (->> (.getDetails char-result)
|
||||||
|
(mapv #(.getErrorCode %))
|
||||||
|
(mapv passay-code->translation-key)
|
||||||
|
(filterv some?))))))
|
||||||
@ -8,6 +8,7 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.auth :as auth]
|
[app.auth :as auth]
|
||||||
[app.auth.oidc :as oidc]
|
[app.auth.oidc :as oidc]
|
||||||
|
[app.auth.passwords :as passwords]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.features :as cfeat]
|
[app.common.features :as cfeat]
|
||||||
@ -182,6 +183,7 @@
|
|||||||
(db/update! conn :profile {:password pwd :is-active true} {:id profile-id})
|
(db/update! conn :profile {:password pwd :is-active true} {:id profile-id})
|
||||||
nil))]
|
nil))]
|
||||||
|
|
||||||
|
(passwords/validate-password password)
|
||||||
(->> (validate-token token)
|
(->> (validate-token token)
|
||||||
(update-password conn))
|
(update-password conn))
|
||||||
|
|
||||||
@ -240,6 +242,9 @@
|
|||||||
:code :email-as-password
|
:code :email-as-password
|
||||||
:hint "you can't use your email as password"))
|
:hint "you can't use your email as password"))
|
||||||
|
|
||||||
|
;; Validate password strength against common password dictionary
|
||||||
|
(passwords/validate-password (:password params))
|
||||||
|
|
||||||
(when (eml/has-bounce-reports? cfg (:email params))
|
(when (eml/has-bounce-reports? cfg (:email params))
|
||||||
(ex/raise :type :restriction
|
(ex/raise :type :restriction
|
||||||
:code :email-has-permanent-bounces
|
:code :email-has-permanent-bounces
|
||||||
|
|||||||
@ -118,7 +118,7 @@
|
|||||||
|
|
||||||
(def ^:private schema:import-binfile
|
(def ^:private schema:import-binfile
|
||||||
[:and
|
[:and
|
||||||
[:map {:title "import-binfile" :closed true}
|
[:map {:title "import-binfile"}
|
||||||
[:name [:or [:string {:max 250}]
|
[:name [:or [:string {:max 250}]
|
||||||
[:map-of ::sm/uuid [:string {:max 250}]]]]
|
[:map-of ::sm/uuid [:string {:max 250}]]]]
|
||||||
[:project-id ::sm/uuid]
|
[:project-id ::sm/uuid]
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
(ns app.rpc.commands.profile
|
(ns app.rpc.commands.profile
|
||||||
(:require
|
(:require
|
||||||
[app.auth :as auth]
|
[app.auth :as auth]
|
||||||
|
[app.auth.passwords :as passwords]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
@ -212,6 +213,9 @@
|
|||||||
:code :email-as-password
|
:code :email-as-password
|
||||||
:hint "you can't use your email as password"))
|
:hint "you can't use your email as password"))
|
||||||
|
|
||||||
|
;; Validate password strength against common password dictionary
|
||||||
|
(passwords/validate-password (:password params))
|
||||||
|
|
||||||
(update-profile-password! cfg (assoc profile :password password))
|
(update-profile-password! cfg (assoc profile :password password))
|
||||||
|
|
||||||
(->> (rph/get-request params)
|
(->> (rph/get-request params)
|
||||||
|
|||||||
@ -189,7 +189,7 @@
|
|||||||
(let [params (merge {:id (mk-uuid "profile" i)
|
(let [params (merge {:id (mk-uuid "profile" i)
|
||||||
:fullname (str "Profile " i)
|
:fullname (str "Profile " i)
|
||||||
:email (str "profile" i ".test@nodomain.com")
|
:email (str "profile" i ".test@nodomain.com")
|
||||||
:password "123123"
|
:password "Test123!"
|
||||||
:is-demo false}
|
:is-demo false}
|
||||||
params)]
|
params)]
|
||||||
(db/run! system
|
(db/run! system
|
||||||
|
|||||||
@ -42,7 +42,7 @@
|
|||||||
(let [profile (th/create-profile* 1)
|
(let [profile (th/create-profile* 1)
|
||||||
data {::th/type :login-with-password
|
data {::th/type :login-with-password
|
||||||
:email "profile1.test@nodomain.com"
|
:email "profile1.test@nodomain.com"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
|
|
||||||
#_(th/print-result! out)
|
#_(th/print-result! out)
|
||||||
@ -56,7 +56,7 @@
|
|||||||
(let [profile (th/create-profile* 1)
|
(let [profile (th/create-profile* 1)
|
||||||
data {::th/type :login-with-password
|
data {::th/type :login-with-password
|
||||||
:email "profile1.test@nodomain.com"
|
:email "profile1.test@nodomain.com"
|
||||||
:password "123123"}
|
:password "Test123!"}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(let [error (:error out)]
|
(let [error (:error out)]
|
||||||
@ -69,7 +69,7 @@
|
|||||||
(let [profile (th/create-profile* 1 {:is-active true})
|
(let [profile (th/create-profile* 1 {:is-active true})
|
||||||
data {::th/type :login-with-password
|
data {::th/type :login-with-password
|
||||||
:email "profile1.test@nodomain.com"
|
:email "profile1.test@nodomain.com"
|
||||||
:password "123123"}
|
:password "Test123!"}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
@ -403,7 +403,7 @@
|
|||||||
(let [data {::th/type :prepare-register-profile
|
(let [data {::th/type :prepare-register-profile
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:password "foobar"
|
:password "Foobar12!"
|
||||||
:utm_campaign "utma"
|
:utm_campaign "utma"
|
||||||
:mtm_campaign "mtma"}
|
:mtm_campaign "mtma"}
|
||||||
out (th/command! data)
|
out (th/command! data)
|
||||||
@ -444,7 +444,7 @@
|
|||||||
(let [data {::th/type :prepare-register-profile
|
(let [data {::th/type :prepare-register-profile
|
||||||
:email "hello@example.com"
|
:email "hello@example.com"
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)
|
out (th/command! data)
|
||||||
token (get-in out [:result :token])]
|
token (get-in out [:result :token])]
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
@ -463,7 +463,7 @@
|
|||||||
(let [data {::th/type :prepare-register-profile
|
(let [data {::th/type :prepare-register-profile
|
||||||
:email "hello@example.com"
|
:email "hello@example.com"
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)
|
out (th/command! data)
|
||||||
token (get-in out [:result :token])]
|
token (get-in out [:result :token])]
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
@ -498,7 +498,7 @@
|
|||||||
(let [data {::th/type :prepare-register-profile
|
(let [data {::th/type :prepare-register-profile
|
||||||
:email "hello@example.com"
|
:email "hello@example.com"
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)
|
out (th/command! data)
|
||||||
token (get-in out [:result :token])]
|
token (get-in out [:result :token])]
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
@ -521,7 +521,7 @@
|
|||||||
(let [data {::th/type :prepare-register-profile
|
(let [data {::th/type :prepare-register-profile
|
||||||
:email "hello@example.com"
|
:email "hello@example.com"
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)
|
out (th/command! data)
|
||||||
token (get-in out [:result :token])]
|
token (get-in out [:result :token])]
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
@ -547,7 +547,7 @@
|
|||||||
(let [data {::th/type :prepare-register-profile
|
(let [data {::th/type :prepare-register-profile
|
||||||
:email "hello@example.com"
|
:email "hello@example.com"
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)
|
out (th/command! data)
|
||||||
token (get-in out [:result :token])]
|
token (get-in out [:result :token])]
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
@ -576,7 +576,7 @@
|
|||||||
(let [data {::th/type :prepare-register-profile
|
(let [data {::th/type :prepare-register-profile
|
||||||
:email "hello@example.com"
|
:email "hello@example.com"
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)
|
out (th/command! data)
|
||||||
token (get-in out [:result :token])]
|
token (get-in out [:result :token])]
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
@ -614,7 +614,7 @@
|
|||||||
:invitation-token itoken
|
:invitation-token itoken
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
|
|
||||||
{prep-result :result prep-error :error} (th/command! prep-data)]
|
{prep-result :result prep-error :error} (th/command! prep-data)]
|
||||||
(t/is (nil? prep-error))
|
(t/is (nil? prep-error))
|
||||||
@ -659,7 +659,7 @@
|
|||||||
:invitation-token itoken
|
:invitation-token itoken
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
|
|
||||||
{prep-result :result prep-error :error} (th/command! prep-data)]
|
{prep-result :result prep-error :error} (th/command! prep-data)]
|
||||||
(t/is (nil? prep-error))
|
(t/is (nil? prep-error))
|
||||||
@ -692,7 +692,7 @@
|
|||||||
:invitation-token itoken
|
:invitation-token itoken
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
|
|
||||||
(t/is (not (th/success? out)))
|
(t/is (not (th/success? out)))
|
||||||
@ -712,7 +712,7 @@
|
|||||||
:invitation-token itoken
|
:invitation-token itoken
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
|
|
||||||
(t/is (not (th/success? out)))
|
(t/is (not (th/success? out)))
|
||||||
@ -733,7 +733,7 @@
|
|||||||
:invitation-token itoken
|
:invitation-token itoken
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
|
|
||||||
(t/is (not (th/success? out)))
|
(t/is (not (th/success? out)))
|
||||||
@ -754,7 +754,7 @@
|
|||||||
:invitation-token itoken
|
:invitation-token itoken
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
|
|
||||||
(t/is (not (th/success? out)))
|
(t/is (not (th/success? out)))
|
||||||
@ -767,7 +767,7 @@
|
|||||||
(let [data {::th/type :prepare-register-profile
|
(let [data {::th/type :prepare-register-profile
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
|
|
||||||
(t/is (not (th/success? out)))
|
(t/is (not (th/success? out)))
|
||||||
@ -780,7 +780,7 @@
|
|||||||
data {::th/type :prepare-register-profile
|
data {::th/type :prepare-register-profile
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:email (:email profile)
|
:email (:email profile)
|
||||||
:password "foobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
@ -793,7 +793,7 @@
|
|||||||
data {::th/type :prepare-register-profile
|
data {::th/type :prepare-register-profile
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:password "foobar"}]
|
:password "Foobar12!"}]
|
||||||
|
|
||||||
(th/create-global-complaint-for pool {:type :bounce :email "user@example.com"})
|
(th/create-global-complaint-for pool {:type :bounce :email "user@example.com"})
|
||||||
|
|
||||||
@ -808,7 +808,7 @@
|
|||||||
data {::th/type :prepare-register-profile
|
data {::th/type :prepare-register-profile
|
||||||
:fullname "foobar"
|
:fullname "foobar"
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:password "foobar"}]
|
:password "Foobar12!"}]
|
||||||
|
|
||||||
(th/create-global-complaint-for pool {:type :complaint :email "user@example.com"})
|
(th/create-global-complaint-for pool {:type :complaint :email "user@example.com"})
|
||||||
|
|
||||||
@ -1131,8 +1131,8 @@
|
|||||||
(let [profile (th/create-profile* 1)
|
(let [profile (th/create-profile* 1)
|
||||||
data {::th/type :update-profile-password
|
data {::th/type :update-profile-password
|
||||||
::rpc/profile-id (:id profile)
|
::rpc/profile-id (:id profile)
|
||||||
:old-password "123123"
|
:old-password "Test123!"
|
||||||
:password "foobarfoobar"}
|
:password "Foobar12!"}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (nil? (:result out)))))
|
(t/is (nil? (:result out)))))
|
||||||
@ -1143,7 +1143,7 @@
|
|||||||
data {::th/type :update-profile-password
|
data {::th/type :update-profile-password
|
||||||
::rpc/profile-id (:id profile)
|
::rpc/profile-id (:id profile)
|
||||||
:old-password "badpassword"
|
:old-password "badpassword"
|
||||||
:password "foobarfoobar"}
|
:password "Foobar12!"}
|
||||||
{:keys [result error] :as out} (th/command! data)]
|
{:keys [result error] :as out} (th/command! data)]
|
||||||
(t/is (th/ex-info? error))
|
(t/is (th/ex-info? error))
|
||||||
(t/is (th/ex-of-type? error :validation))
|
(t/is (th/ex-of-type? error :validation))
|
||||||
@ -1154,7 +1154,7 @@
|
|||||||
(let [profile (th/create-profile* 1)
|
(let [profile (th/create-profile* 1)
|
||||||
data {::th/type :update-profile-password
|
data {::th/type :update-profile-password
|
||||||
::rpc/profile-id (:id profile)
|
::rpc/profile-id (:id profile)
|
||||||
:old-password "123123"
|
:old-password "Test123!"
|
||||||
:password "profile1.test@nodomain.com"}
|
:password "profile1.test@nodomain.com"}
|
||||||
{:keys [result error] :as out} (th/command! data)]
|
{:keys [result error] :as out} (th/command! data)]
|
||||||
(t/is (th/ex-info? error))
|
(t/is (th/ex-info? error))
|
||||||
@ -1271,3 +1271,49 @@
|
|||||||
(t/is (th/ex-info? (:error out)))
|
(t/is (th/ex-info? (:error out)))
|
||||||
(t/is (th/ex-of-type? (:error out) :validation))
|
(t/is (th/ex-of-type? (:error out) :validation))
|
||||||
(t/is (th/ex-of-code? (:error out) :params-validation))))
|
(t/is (th/ex-of-code? (:error out) :params-validation))))
|
||||||
|
|
||||||
|
|
||||||
|
(t/deftest prepare-register-profile-password-too-short
|
||||||
|
(let [data {::th/type :prepare-register-profile
|
||||||
|
:email "user@example.com"
|
||||||
|
:fullname "foobar"
|
||||||
|
:password "123"}
|
||||||
|
out (th/command! data)]
|
||||||
|
(t/is (th/ex-info? (:error out)))
|
||||||
|
(t/is (th/ex-of-type? (:error out) :validation))
|
||||||
|
(t/is (th/ex-of-code? (:error out) :weak-password))))
|
||||||
|
|
||||||
|
|
||||||
|
(t/deftest prepare-register-profile-weak-password
|
||||||
|
(let [data {::th/type :prepare-register-profile
|
||||||
|
:email "user@example.com"
|
||||||
|
:fullname "foobar"
|
||||||
|
:password "password123"}
|
||||||
|
out (th/command! data)]
|
||||||
|
(t/is (th/ex-info? (:error out)))
|
||||||
|
(t/is (th/ex-of-type? (:error out) :validation))
|
||||||
|
(t/is (th/ex-of-code? (:error out) :weak-password))))
|
||||||
|
|
||||||
|
|
||||||
|
(t/deftest update-profile-password-too-short
|
||||||
|
(let [profile (th/create-profile* 1)
|
||||||
|
data {::th/type :update-profile-password
|
||||||
|
::rpc/profile-id (:id profile)
|
||||||
|
:old-password "Test123!"
|
||||||
|
:password "123"}
|
||||||
|
out (th/command! data)]
|
||||||
|
(t/is (th/ex-info? (:error out)))
|
||||||
|
(t/is (th/ex-of-type? (:error out) :validation))
|
||||||
|
(t/is (th/ex-of-code? (:error out) :weak-password))))
|
||||||
|
|
||||||
|
|
||||||
|
(t/deftest update-profile-password-weak-password
|
||||||
|
(let [profile (th/create-profile* 1)
|
||||||
|
data {::th/type :update-profile-password
|
||||||
|
::rpc/profile-id (:id profile)
|
||||||
|
:old-password "Test123!"
|
||||||
|
:password "qwerty"}
|
||||||
|
out (th/command! data)]
|
||||||
|
(t/is (th/ex-info? (:error out)))
|
||||||
|
(t/is (th/ex-of-type? (:error out) :validation))
|
||||||
|
(t/is (th/ex-of-code? (:error out) :weak-password))))
|
||||||
|
|||||||
@ -462,11 +462,14 @@
|
|||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(let [{:keys [on-error on-success]
|
(let [{:keys [on-error on-success]
|
||||||
:or {on-error rx/throw
|
:or {on-error identity
|
||||||
on-success identity}} (meta data)]
|
on-success identity}} (meta data)]
|
||||||
(->> (rp/cmd! :recover-profile data)
|
(->> (rp/cmd! :recover-profile data)
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/catch on-error)))))))
|
(rx/catch (fn [err]
|
||||||
|
(on-error err)
|
||||||
|
(rx/empty)))
|
||||||
|
(rx/ignore)))))))
|
||||||
|
|
||||||
;; --- EVENT: fetch-team-webhooks
|
;; --- EVENT: fetch-team-webhooks
|
||||||
|
|
||||||
|
|||||||
@ -28,8 +28,18 @@
|
|||||||
(= password-1 password-2))]])
|
(= password-1 password-2))]])
|
||||||
|
|
||||||
(defn- on-error
|
(defn- on-error
|
||||||
[_form _error]
|
[form error]
|
||||||
(st/emit! (ntf/error (tr "errors.invalid-recovery-token"))))
|
(let [{:keys [type code] :as edata} (ex-data error)]
|
||||||
|
(if (= [:validation :weak-password] [type code])
|
||||||
|
(let [details (:details edata)
|
||||||
|
options (when (seq details)
|
||||||
|
(mapv tr details))]
|
||||||
|
(swap! form assoc-in [:extra-errors :password-1]
|
||||||
|
{:message (tr "errors.weak-password")
|
||||||
|
:options options}))
|
||||||
|
|
||||||
|
(let [msg (tr "errors.invalid-recovery-token")]
|
||||||
|
(st/emit! (ntf/error msg))))))
|
||||||
|
|
||||||
(defn- on-success
|
(defn- on-success
|
||||||
[_]
|
[_]
|
||||||
@ -38,7 +48,7 @@
|
|||||||
|
|
||||||
(defn- on-submit
|
(defn- on-submit
|
||||||
[form _event]
|
[form _event]
|
||||||
(let [mdata {:on-error on-error
|
(let [mdata {:on-error (partial on-error form)
|
||||||
:on-success on-success}
|
:on-success on-success}
|
||||||
params {:token (get-in @form [:clean-data :token])
|
params {:token (get-in @form [:clean-data :token])
|
||||||
:password (get-in @form [:clean-data :password-2])}]
|
:password (get-in @form [:clean-data :password-2])}]
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
[app.util.storage :as storage]
|
[app.util.storage :as storage]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
;; --- PAGE: Register
|
;; --- PAGE: Register
|
||||||
@ -103,8 +104,20 @@
|
|||||||
(st/emit! (ntf/error (tr "errors.email-already-exists")))
|
(st/emit! (ntf/error (tr "errors.email-already-exists")))
|
||||||
|
|
||||||
[:validation :email-as-password]
|
[:validation :email-as-password]
|
||||||
(swap! form assoc-in [:errors :password]
|
(st/emit! (ntf/error (tr "errors.email-as-password")))
|
||||||
{:message (tr "errors.email-as-password")})
|
|
||||||
|
[:validation :weak-password]
|
||||||
|
(let [details (:details edata)
|
||||||
|
items (when (seq details)
|
||||||
|
(->> details
|
||||||
|
(map #(str "<li>" (tr %) "</li>"))
|
||||||
|
(str/join "")))
|
||||||
|
detail (when items
|
||||||
|
(str "<ul>" items "</ul>"))]
|
||||||
|
(st/emit! (ntf/show {:content (tr "errors.weak-password")
|
||||||
|
:detail detail
|
||||||
|
:type :toast
|
||||||
|
:level :error})))
|
||||||
|
|
||||||
(do
|
(do
|
||||||
(when-let [explain (get edata :explain)]
|
(when-let [explain (get edata :explain)]
|
||||||
|
|||||||
@ -180,11 +180,17 @@
|
|||||||
|
|
||||||
(cond
|
(cond
|
||||||
(and touched? (:message error) show-error)
|
(and touched? (:message error) show-error)
|
||||||
(let [message (:message error)]
|
(let [message (:message error)
|
||||||
|
options (:options error)]
|
||||||
[:div {:id (dm/str "error-" input-name)
|
[:div {:id (dm/str "error-" input-name)
|
||||||
:class (stl/css :error)
|
:class (stl/css :error)
|
||||||
:data-testid (dm/str data-testid "-error")}
|
:data-testid (dm/str data-testid "-error")}
|
||||||
message])
|
message
|
||||||
|
(when (seq options)
|
||||||
|
[:ul {:class (stl/css :error-options)}
|
||||||
|
(for [opt options]
|
||||||
|
[:li {:key opt
|
||||||
|
:class (stl/css :error-option)} opt])])])
|
||||||
|
|
||||||
;; FIXME: DEPRECATED
|
;; FIXME: DEPRECATED
|
||||||
(and touched? (:code error) show-error)
|
(and touched? (:code error) show-error)
|
||||||
|
|||||||
@ -168,6 +168,16 @@
|
|||||||
font-size: deprecated.$fs-14;
|
font-size: deprecated.$fs-14;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-options {
|
||||||
|
margin-block: var(--sp-xxs);
|
||||||
|
padding-inline-start: var(--sp-l);
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-option {
|
||||||
|
margin-block: var(--sp-xxs);
|
||||||
|
}
|
||||||
|
|
||||||
.hint {
|
.hint {
|
||||||
@include t.use-typography("body-small");
|
@include t.use-typography("body-small");
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,14 @@
|
|||||||
(swap! form assoc-in [:extra-errors :password-1]
|
(swap! form assoc-in [:extra-errors :password-1]
|
||||||
{:message (tr "errors.email-as-password")})
|
{:message (tr "errors.email-as-password")})
|
||||||
|
|
||||||
|
:weak-password
|
||||||
|
(let [details (:details data)
|
||||||
|
options (when (seq details)
|
||||||
|
(mapv tr details))]
|
||||||
|
(swap! form assoc-in [:extra-errors :password-1]
|
||||||
|
{:message (tr "errors.weak-password")
|
||||||
|
:options options}))
|
||||||
|
|
||||||
(let [msg (tr "generic.error")]
|
(let [msg (tr "generic.error")]
|
||||||
(st/emit! (ntf/error msg))))))
|
(st/emit! (ntf/error msg))))))
|
||||||
|
|
||||||
|
|||||||
@ -385,18 +385,18 @@
|
|||||||
theme-id (uuid/next)
|
theme-id (uuid/next)
|
||||||
theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light")
|
theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light")
|
||||||
emitted (atom [])
|
emitted (atom [])
|
||||||
invalid (atom [])]
|
errors (atom [])]
|
||||||
(with-redefs [u/locate-token-set (constantly nil)
|
(with-redefs [u/locate-token-set (constantly nil)
|
||||||
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme))
|
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme))
|
||||||
u/not-valid (fn [_ code value] (swap! invalid conj [code value]))
|
u/throw-validation-errors? (constantly true)
|
||||||
dwtl/update-token-theme (fn [id theme] {:id id :theme theme})
|
dwtl/update-token-theme (fn [id theme] {:id id :theme theme})
|
||||||
st/emit! (fn ([event] (swap! emitted conj event) nil)
|
st/emit! (fn ([event] (swap! emitted conj event) nil)
|
||||||
([event & _] (swap! emitted conj event) nil))]
|
([event & _] (swap! emitted conj event) nil))]
|
||||||
(let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)]
|
(let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)]
|
||||||
;; Non-id, non-proxy arguments are rejected by the schema coercer.
|
;; Non-id, non-proxy arguments are rejected by the schema coercer.
|
||||||
(.addSet theme-proxy 42)
|
(try (.addSet theme-proxy 42) (catch :default e (swap! errors conj e)))
|
||||||
(.removeSet theme-proxy nil)
|
(try (.removeSet theme-proxy nil) (catch :default e (swap! errors conj e)))
|
||||||
(t/is (empty? @emitted))
|
(t/is (empty? @emitted))
|
||||||
(t/is (= 2 (count @invalid)))
|
(t/is (= 2 (count @errors)))
|
||||||
(t/is (every? #(= :error (first %)) @invalid))))))
|
(t/is (every? #(instance? js/Error %) @errors))))))
|
||||||
|
|
||||||
|
|||||||
@ -1748,6 +1748,34 @@ msgstr "Confirmation password must match"
|
|||||||
msgid "errors.password-too-short"
|
msgid "errors.password-too-short"
|
||||||
msgstr "Password should at least be 8 characters"
|
msgstr "Password should at least be 8 characters"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password"
|
||||||
|
msgstr "Password does not meet the requirements"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.too-short"
|
||||||
|
msgstr "At least 8 characters"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.insufficient-lowercase"
|
||||||
|
msgstr "At least 1 lowercase letter"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.insufficient-uppercase"
|
||||||
|
msgstr "At least 1 uppercase letter"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.insufficient-digits"
|
||||||
|
msgstr "At least 1 digit"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.insufficient-special"
|
||||||
|
msgstr "At least 1 special character"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.in-dictionary"
|
||||||
|
msgstr "Password is too common"
|
||||||
|
|
||||||
#: src/app/main/errors.cljs:267
|
#: src/app/main/errors.cljs:267
|
||||||
msgid "errors.paste-data-validation"
|
msgid "errors.paste-data-validation"
|
||||||
msgstr "Invalid data in clipboard"
|
msgstr "Invalid data in clipboard"
|
||||||
|
|||||||
@ -1717,6 +1717,34 @@ msgstr "La contraseña de confirmación debe coincidir"
|
|||||||
msgid "errors.password-too-short"
|
msgid "errors.password-too-short"
|
||||||
msgstr "La contraseña debe tener 8 caracteres como mínimo"
|
msgstr "La contraseña debe tener 8 caracteres como mínimo"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password"
|
||||||
|
msgstr "La contraseña no cumple los requisitos"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.too-short"
|
||||||
|
msgstr "Al menos 8 caracteres"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.insufficient-lowercase"
|
||||||
|
msgstr "Al menos 1 letra minúscula"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.insufficient-uppercase"
|
||||||
|
msgstr "Al menos 1 letra mayúscula"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.insufficient-digits"
|
||||||
|
msgstr "Al menos 1 dígito"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.insufficient-special"
|
||||||
|
msgstr "Al menos 1 carácter especial"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/auth/register.cljs
|
||||||
|
msgid "errors.weak-password.in-dictionary"
|
||||||
|
msgstr "La contraseña es demasiado común"
|
||||||
|
|
||||||
#: src/app/main/errors.cljs:267
|
#: src/app/main/errors.cljs:267
|
||||||
msgid "errors.paste-data-validation"
|
msgid "errors.paste-data-validation"
|
||||||
msgstr "Datos inválidos en el portapapeles"
|
msgstr "Datos inválidos en el portapapeles"
|
||||||
|
|||||||
@ -1,30 +1,30 @@
|
|||||||
{
|
{
|
||||||
"name": "composable-test-suite",
|
"name": "composable-test-suite",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite build --watch",
|
"start": "vite build --watch",
|
||||||
"init": "pnpm run build && pnpm run start",
|
"init": "pnpm run build && pnpm run start",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"build:headless": "vite build --config vite.config.headless.ts",
|
"build:headless": "vite build --config vite.config.headless.ts",
|
||||||
"test:ci": "pnpm run build:headless && tsx ci/run-ci.ts",
|
"test:ci": "pnpm run build:headless && tsx ci/run-ci.ts",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"bootstrap": "pnpm install --ignore-workspace && pnpm run build && pnpm run start",
|
"bootstrap": "pnpm install --ignore-workspace && pnpm run build && pnpm run start",
|
||||||
"types:check": "tsc --noEmit",
|
"types:check": "tsc --noEmit",
|
||||||
"fmt": "prettier --write src ci index.html",
|
"fmt": "prettier --write src ci index.html",
|
||||||
"clean": "rm -rf dist/"
|
"clean": "rm -rf dist/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@penpot/plugin-styles": "1.4.2",
|
"@penpot/plugin-styles": "1.4.2",
|
||||||
"@penpot/plugin-types": "1.4.2"
|
"@penpot/plugin-types": "1.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"playwright": "^1.62.1",
|
"playwright": "^1.62.1",
|
||||||
"prettier": "^3.9.6",
|
"prettier": "^3.9.6",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^8.2.0",
|
"vite": "^8.2.0",
|
||||||
"vite-live-preview": "^0.4.0"
|
"vite-live-preview": "^0.4.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.20.0+sha512.9a6f330a95b66446ea088faf1521405a8a01f07fde7124cc9958dfed52d4bb436737e65b08f85f37b46fcba375092558ac51262b816844b22f63406ed166bfee"
|
"packageManager": "pnpm@11.20.0+sha512.9a6f330a95b66446ea088faf1521405a8a01f07fde7124cc9958dfed52d4bb436737e65b08f85f37b46fcba375092558ac51262b816844b22f63406ed166bfee"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import { dragHandler } from '../drag-handler.js';
|
|||||||
import modalCss from './plugin.modal.css?inline';
|
import modalCss from './plugin.modal.css?inline';
|
||||||
import { resizeModal } from '../create-modal.js';
|
import { resizeModal } from '../create-modal.js';
|
||||||
|
|
||||||
const MIN_Z_INDEX = 3;
|
const MIN_Z_INDEX = 300;
|
||||||
|
const Z_INDEX_VAR = '--z-index-set';
|
||||||
|
|
||||||
export class PluginModalElement extends HTMLElement {
|
export class PluginModalElement extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -43,7 +44,19 @@ export class PluginModalElement extends HTMLElement {
|
|||||||
return Number(modal.style.zIndex);
|
return Number(modal.style.zIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
const maxZIndex = Math.max(...zIndexModals, MIN_Z_INDEX);
|
// Read the application z-index scale via the inherited CSS custom property
|
||||||
|
// `--z-index-set` (defined on :root). Custom properties pierce shadow DOM
|
||||||
|
// boundaries, so the value is available even though the modal uses a Shadow
|
||||||
|
// root. Falls back to MIN_Z_INDEX when the variable is unset or unparseable
|
||||||
|
// (e.g. when the runtime is used outside the Penpot app shell).
|
||||||
|
const declared = getComputedStyle(this)
|
||||||
|
.getPropertyValue(Z_INDEX_VAR)
|
||||||
|
.trim();
|
||||||
|
const parsed = Number(declared);
|
||||||
|
const baseZIndex =
|
||||||
|
Number.isFinite(parsed) && parsed > 0 ? parsed : MIN_Z_INDEX;
|
||||||
|
|
||||||
|
const maxZIndex = Math.max(...zIndexModals, baseZIndex);
|
||||||
|
|
||||||
this.style.zIndex = (maxZIndex + 1).toString();
|
this.style.zIndex = (maxZIndex + 1).toString();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user