fix: enforce consistent password policy across all auth flows

Replace duplicated inline validation with a shared validatePassword()
utility that checks minimum length (8), rejects repetitive and common
passwords, and requires uppercase, lowercase, a digit, and a special
character.

- Add server/src/services/passwordPolicy.ts as single source of truth
- Apply to registration, password change, and admin create/edit user
  (admin routes previously had zero validation)
- Fix client min-length mismatch (6 vs 8) in RegisterPage and LoginPage
- Add client-side password length guard to AdminPage forms
- Update register.passwordTooShort and settings.passwordWeak i18n keys
  in all 12 locales to reflect the corrected requirements
This commit is contained in:
jubnl
2026-04-01 07:02:53 +02:00
parent ce8d498f2d
commit e03505dca2
18 changed files with 77 additions and 39 deletions
+11 -1
View File
@@ -253,6 +253,10 @@ export default function AdminPage(): React.ReactElement {
toast.error(t('admin.toast.fieldsRequired'))
return
}
if (createForm.password.trim().length < 8) {
toast.error(t('settings.passwordTooShort'))
return
}
try {
const data = await adminApi.createUser(createForm)
setUsers(prev => [data.user, ...prev])
@@ -308,7 +312,13 @@ export default function AdminPage(): React.ReactElement {
email: editForm.email.trim() || undefined,
role: editForm.role,
}
if (editForm.password.trim()) payload.password = editForm.password.trim()
if (editForm.password.trim()) {
if (editForm.password.trim().length < 8) {
toast.error(t('settings.passwordTooShort'))
return
}
payload.password = editForm.password.trim()
}
const data = await adminApi.updateUser(editingUser.id, payload)
setUsers(prev => prev.map(u => u.id === editingUser.id ? data.user : u))
setEditingUser(null)
+1 -1
View File
@@ -150,7 +150,7 @@ export default function LoginPage(): React.ReactElement {
}
if (mode === 'register') {
if (!username.trim()) { setError('Username is required'); setIsLoading(false); return }
if (password.length < 6) { setError('Password must be at least 6 characters'); setIsLoading(false); return }
if (password.length < 8) { setError('Password must be at least 8 characters'); setIsLoading(false); return }
await register(username, email, password, inviteToken || undefined)
} else {
const result = await login(email, password)
+1 -1
View File
@@ -26,7 +26,7 @@ export default function RegisterPage(): React.ReactElement {
return
}
if (password.length < 6) {
if (password.length < 8) {
setError(t('register.passwordTooShort'))
return
}