feat(auth): data-driven password requirement checkers
Release / build-and-push (push) Successful in 3m4s

GET /api/auth/password-rules (public, pre-auth) exposes the ACTIVE
complexity config from the settings registry; password_validation.ts
now fetches it and generates the checklist rows per active rule —
inactive requirements no longer linger as fake tasks, and the min
length mirrors the admin's setting instead of a hardcoded 8.

Surfaces: register, setup, admin create-user (Users page), profile
password change (self + admin variants, now gated on checklist pass;
misleading minlength=6 dropped — the server was always the enforcer).

Verified live: flip min_length=12 / number+special off via the
settings API → endpoint + register/profile/admin checklists re-render
row-for-row; enforcement follows (short rejected, rule-valid accepted
without digits/specials); restore defaults → checklists return.
This commit is contained in:
John O'Keefe committed 2026-10-14 11:08:12 -04:00
1 parent d95814ded3
commit df556d8208
10 files changed
+218 -156

No files matched your search

+9 -5
View File
@@ -112,7 +112,7 @@ templ ProfileForm(user User, actionURL string, requireCurrentPassword bool, show
}
</div>
</div>
<div>
<div x-data="profilePasswordForm">
<div class="flex items-center gap-2 mb-4">
@Icon("shield", "h-5 w-5")
<h3 class="text-lg font-semibold" style="color: var(--text-primary)">Change Password</h3>
@@ -132,22 +132,24 @@ templ ProfileForm(user User, actionURL string, requireCurrentPassword bool, show
<label class="block text-xs font-semibold uppercase tracking-wide mb-1.5" style="color: var(--text-secondary)">New Password</label>
<input
type="password"
id="new-password"
name="new_password"
class="input"
minlength="6"
/>
</div>
<div>
<label class="block text-xs font-semibold uppercase tracking-wide mb-1.5" style="color: var(--text-secondary)">Confirm New Password</label>
<input
type="password"
id="confirm-password"
name="confirm_password"
class="input"
minlength="6"
/>
</div>
<div id="password-requirements" class="text-xs"></div>
<button
type="button"
id="password-submit-btn"
hx-put="/api/auth/password"
hx-target="#password-result"
hx-swap="innerHTML"
@@ -168,22 +170,24 @@ templ ProfileForm(user User, actionURL string, requireCurrentPassword bool, show
<label class="block text-xs font-semibold uppercase tracking-wide mb-1.5" style="color: var(--text-secondary)">New Password</label>
<input
type="password"
id="new-password"
name="new_password"
class="input"
minlength="6"
/>
</div>
<div>
<label class="block text-xs font-semibold uppercase tracking-wide mb-1.5" style="color: var(--text-secondary)">Confirm New Password</label>
<input
type="password"
id="confirm-password"
name="confirm_password"
class="input"
minlength="6"
/>
</div>
<div id="password-requirements" class="text-xs"></div>
<button
type="button"
id="password-submit-btn"
hx-put={ "/api/auth/password/" + user.ID }
hx-target="#password-result"
hx-swap="innerHTML"