🐛 Do not apply the new-password policy to the old-password field (#10661)

The change-password form validated the existing password against the 8-character policy, locking out accounts whose current password predates it.

Closes #10626

Signed-off-by: Akshit Nassa <akshitnassa412@gmail.com>
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Co-authored-by: Akshit Nassa <akshitnassa412@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
AK 2026-07-23 07:31:38 -04:00 committed by GitHub
parent ed85d1d1af
commit 19bdd7081b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 83 additions and 2 deletions

View File

@ -48,12 +48,14 @@
:on-error (partial on-error form)})]
(st/emit! (udu/update-password params))))
(def ^:private schema:password-form
(def schema:password-form
[:and
[:map {:title "PasswordForm"}
[:password-1 ::sm/password]
[:password-2 ::sm/password]
[:password-old ::sm/password]]
;; The old password is validated by the backend, so it only needs to be
;; present here; it may predate the current minimum length policy.
[:password-old [::sm/text {:max 500}]]]
[:fn {:error/code "errors.password-invalid-confirmation"
:error/field :password-2}
(fn [{:keys [password-1 password-2]}]

View File

@ -60,6 +60,7 @@
[frontend-tests.ui.ds-controls-numeric-input-test]
[frontend-tests.ui.layout-container-multiple-test]
[frontend-tests.ui.measures-menu-props-test]
[frontend-tests.ui.settings-password-schema-test]
[frontend-tests.util-clipboard-test]
[frontend-tests.util-object-test]
[frontend-tests.util-range-tree-test]
@ -136,6 +137,7 @@
'frontend-tests.ui.ds-controls-numeric-input-test
'frontend-tests.ui.layout-container-multiple-test
'frontend-tests.ui.measures-menu-props-test
'frontend-tests.ui.settings-password-schema-test
'frontend-tests.util-clipboard-test
'frontend-tests.util-object-test
'frontend-tests.util-range-tree-test

View File

@ -0,0 +1,77 @@
;; 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 frontend-tests.ui.settings-password-schema-test
(:require
[app.common.schema :as sm]
[app.main.ui.settings.password :as passwd]
[cljs.test :as t :include-macros true]
[malli.core :as m]))
(def ^:private new-password "a-long-enough-password")
(defn- params
[password-old]
{:password-old password-old
:password-1 new-password
:password-2 new-password})
(defn- valid?
[data]
(sm/validate passwd/schema:password-form data))
(defn- error-codes
"Maps every schema problem to [field error-code], mirroring how
app.common.schema.messages resolves the message shown next to an input."
[data]
(->> (:errors (sm/explain passwd/schema:password-form data))
(map (fn [{:keys [in schema]}]
(let [props (m/properties schema)
tprops (m/type-properties schema)]
[(or (:error/field props) (first in))
(or (:error/code props) (:error/code tprops))])))
(into {})))
(t/deftest short-old-password-is-accepted
(t/testing "an existing password shorter than the 8 char policy can still be typed in"
(t/is (true? (valid? (params "short"))))
(t/is (empty? (error-codes (params "short"))))))
(t/deftest single-char-old-password-is-accepted
(t/is (true? (valid? (params "x")))))
(t/deftest unicode-old-password-is-accepted
(t/is (true? (valid? (params "🔑é"))))
(t/is (true? (valid? (params " hunter2 ")))))
(t/deftest empty-old-password-is-rejected
(t/is (false? (valid? (params ""))))
(t/is (contains? (error-codes (params "")) :password-old)))
(t/deftest blank-old-password-is-rejected
(t/is (false? (valid? (params " "))))
(t/is (contains? (error-codes (params " ")) :password-old)))
(t/deftest missing-old-password-is-rejected
(t/is (false? (valid? (dissoc (params "short") :password-old)))))
(t/deftest overlong-old-password-is-rejected
(t/is (false? (valid? (params (apply str (repeat 501 "a")))))))
(t/deftest short-new-password-is-rejected
(t/testing "the 8 char policy still applies to the new password"
(let [data {:password-old "short" :password-1 "abc" :password-2 "abc"}]
(t/is (false? (valid? data)))
(t/is (= "errors.password-too-short"
(get (error-codes data) :password-1))))))
(t/deftest confirmation-mismatch-is-rejected
(let [data {:password-old "short"
:password-1 new-password
:password-2 "another-long-password"}]
(t/is (false? (valid? data)))
(t/is (= "errors.password-invalid-confirmation"
(get (error-codes data) :password-2)))))