The web's password forms read the server's minimum length

The server's MIN_PASSWORD_LEN was 8, and the web wrote 8 out five times: two
checks and three placeholders. The constant moves beside the other policy numbers
in settings.py (auth.py imports it), /api/config serves it as
min_password_length, and the config store hands it to Register, Reset and
Account. 8 stays only as the fallback until the config answers.

DRY pass #2, batch 3 (#5372).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 14:31:22 -04:00
co-authored by Claude Opus 5.5
parent 1c4bf56058
commit bfe2ed783f
6 changed files with 20 additions and 9 deletions
+7
View File
@@ -46,6 +46,9 @@ export interface PublicConfig {
// Whether this server can email a reset link, so sign-in offers "Forgot password?"
// (#5266). Absent on an older server and on the offline desktop: no.
password_reset_by_email?: boolean;
// The shortest password an account may have (the server's MIN_PASSWORD_LEN).
// Absent on an older server and on the offline desktop, which has no accounts.
min_password_length?: number;
// Every client this server holds, keyed by platform id. Absent on a server that
// holds none, and absent on the desktop's own offline config — the Tauri build
// answers `config_get` locally and has no clients to hand out.
@@ -66,6 +69,8 @@ export const useConfigStore = defineStore("config", () => {
// that never had the field, both correctly offer no downloads.
const clients = ref<Record<string, ClientRelease>>({});
const passwordResetByEmail = ref(false);
// The server's own number; 8 only until /api/config answers, or if it never does.
const minPasswordLength = ref(8);
const loaded = ref(false);
async function load(): Promise<void> {
@@ -79,6 +84,7 @@ export const useConfigStore = defineStore("config", () => {
trashRetentionDays.value = cfg.trash_retention_days ?? 30;
clients.value = cfg.clients ?? {};
passwordResetByEmail.value = cfg.password_reset_by_email ?? false;
minPasswordLength.value = cfg.min_password_length ?? 8;
} catch {
// Keep defaults if the config endpoint is unreachable.
} finally {
@@ -99,6 +105,7 @@ export const useConfigStore = defineStore("config", () => {
trashRetentionDays,
clients,
passwordResetByEmail,
minPasswordLength,
loaded,
load,
reload,
+1 -1
View File
@@ -264,7 +264,7 @@ onMounted(() => {
type="password"
label="New password"
autocomplete="new-password"
placeholder="At least 8 characters"
:placeholder="`At least ${config.minPasswordLength} characters`"
:error="passwordError"
required
/>
+3 -3
View File
@@ -24,8 +24,8 @@ const loading = ref(false);
async function submit() {
error.value = "";
if (password.value.length < 8) {
error.value = "Password must be at least 8 characters.";
if (password.value.length < config.minPasswordLength) {
error.value = `Password must be at least ${config.minPasswordLength} characters.`;
return;
}
loading.value = true;
@@ -80,7 +80,7 @@ async function submit() {
label="Password"
type="password"
autocomplete="new-password"
placeholder="At least 8 characters"
:placeholder="`At least ${config.minPasswordLength} characters`"
required
/>
<p
+3 -3
View File
@@ -25,8 +25,8 @@ const loading = ref(false);
async function submit() {
error.value = "";
if (password.value.length < 8) {
error.value = "Password must be at least 8 characters.";
if (password.value.length < config.minPasswordLength) {
error.value = `Password must be at least ${config.minPasswordLength} characters.`;
return;
}
if (password.value !== confirm.value) {
@@ -77,7 +77,7 @@ async function submit() {
label="New password"
type="password"
autocomplete="new-password"
placeholder="At least 8 characters"
:placeholder="`At least ${config.minPasswordLength} characters`"
required
/>
<BaseInput