diff --git a/frontend/src/stores/config.ts b/frontend/src/stores/config.ts index 4435978..13132d2 100644 --- a/frontend/src/stores/config.ts +++ b/frontend/src/stores/config.ts @@ -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>({}); 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 { @@ -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, diff --git a/frontend/src/views/AccountView.vue b/frontend/src/views/AccountView.vue index 3fb4160..dd6fbdf 100644 --- a/frontend/src/views/AccountView.vue +++ b/frontend/src/views/AccountView.vue @@ -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 /> diff --git a/frontend/src/views/RegisterView.vue b/frontend/src/views/RegisterView.vue index d583624..190aba9 100644 --- a/frontend/src/views/RegisterView.vue +++ b/frontend/src/views/RegisterView.vue @@ -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 />

dict: "trash_retention_days": await get_setting(db, "trash_retention_days"), # Whether the sign-in screen offers "Forgot password?" (#5266). "password_reset_by_email": await mail_configured(db), + "min_password_length": MIN_PASSWORD_LEN, }