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

+23
View File
@@ -714,6 +714,29 @@ func (h *AuthHandler) ListUsers(c *echo.Context) error {
return c.JSON(http.StatusOK, users)
}
// GetPasswordRules handles GET /api/auth/password-rules (public — the
// register page is pre-auth). Exposes the ACTIVE complexity configuration
// so the UI requirement checkers mirror the real rules instead of
// hardcoding defaults. The server remains the enforcer.
func (h *AuthHandler) GetPasswordRules(c *echo.Context) error {
minLength, upper, lower, number, special := 8, true, true, true, true
if h.settings != nil {
r := h.settings.PasswordRules()
minLength, upper, lower, number, special =
r.MinLength, r.Upper, r.Lower, r.Number, r.Special
}
if minLength < 1 {
minLength = 8
}
return c.JSON(http.StatusOK, map[string]interface{}{
"min_length": minLength,
"require_upper": upper,
"require_lower": lower,
"require_number": number,
"require_special": special,
})
}
// AdminCreateUser handles POST /api/auth/users (AdminMiddleware-gated).
// The admin's counterpart to public registration: creating an account for
// someone else must work even when self-service registration is disabled
+1
View File
@@ -12,6 +12,7 @@ func registerAuthRoutes(cfg *Config, rateLimitMiddleware echo.MiddlewareFunc) {
// Auth routes (no auth required, but rate limited)
e.POST("/api/auth/register", rateLimitMiddleware(cfg.AuthHandler.Register))
e.POST("/api/auth/login", rateLimitMiddleware(cfg.AuthHandler.Login))
e.GET("/api/auth/password-rules", cfg.AuthHandler.GetPasswordRules)
// JWT middleware for protected routes
jwtMiddleware := createJWTMiddleware(cfg)