diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go
index 198dd362..5d86812f 100644
--- a/internal/handlers/auth.go
+++ b/internal/handlers/auth.go
@@ -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
diff --git a/internal/router/auth.go b/internal/router/auth.go
index 47608b13..0d1e4a3d 100644
--- a/internal/router/auth.go
+++ b/internal/router/auth.go
@@ -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)
diff --git a/templates/admin_users.templ b/templates/admin_users.templ
index 1719411b..46ffc63c 100644
--- a/templates/admin_users.templ
+++ b/templates/admin_users.templ
@@ -49,26 +49,8 @@ templ adminCreateUserForm() {
Password Requirements
-
- -
- ○ At least 8 characters
-
- -
- ○ One uppercase letter
-
- -
- ○ One lowercase letter
-
- -
- ○ One number
-
- -
- ○ One special character
-
- -
- ○ Passwords match
-
-
+
+
diff --git a/templates/admin_users_templ.go b/templates/admin_users_templ.go
index 8f136f73..9a59bf64 100644
--- a/templates/admin_users_templ.go
+++ b/templates/admin_users_templ.go
@@ -41,7 +41,7 @@ func adminCreateUserForm() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
Create User
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "Create User
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -106,7 +106,7 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue("user-" + user.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 120, Col: 33}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 102, Col: 33}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
@@ -127,7 +127,7 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 129, Col: 96}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 111, Col: 96}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@@ -150,7 +150,7 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 134, Col: 87}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 116, Col: 87}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
@@ -163,7 +163,7 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(FormatInTimezone(user.CreatedAt, currentUser.Timezone))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 139, Col: 73}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 121, Col: 73}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
@@ -186,7 +186,7 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue("/api/auth/profile/" + user.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 156, Col: 50}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 138, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
if templ_7745c5c3_Err != nil {
@@ -199,7 +199,7 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue("#role-result-" + user.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 157, Col: 48}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 139, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
if templ_7745c5c3_Err != nil {
@@ -237,7 +237,7 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue("role-result-" + user.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 168, Col: 42}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 150, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil {
@@ -250,7 +250,7 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue("/admin/users/" + user.ID + "/profile-modal")
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 172, Col: 63}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 154, Col: 63}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10)
if templ_7745c5c3_Err != nil {
@@ -289,7 +289,7 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue("/api/auth/profile/" + user.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 191, Col: 53}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 173, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
if templ_7745c5c3_Err != nil {
@@ -302,7 +302,7 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue("#user-" + user.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 192, Col: 41}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_users.templ`, Line: 174, Col: 41}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
if templ_7745c5c3_Err != nil {
diff --git a/templates/profile_form.templ b/templates/profile_form.templ
index 2d3ecfa7..fd0a4081 100644
--- a/templates/profile_form.templ
+++ b/templates/profile_form.templ
@@ -112,7 +112,7 @@ templ ProfileForm(user User, actionURL string, requireCurrentPassword bool, show
}
- ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -455,7 +455,7 @@ func ProfileForm(user User, actionURL string, requireCurrentPassword bool, showR
return templ_7745c5c3_Err
}
if requireCurrentPassword {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "