feat: add admin password change via /settings page

Backend:
- DB: add UpdateAdminPasswordHash(id, hash) in queries_users.go
- Auth: add ChangePassword(username, old, new) method that verifies
  old password, validates length >= 8, and stores bcrypt hash
- Handlers: add handleChangePassword on PUT /api/auth/password (auth required)
- Router: register route inside protected group

Frontend:
- api.ts: add changePassword(oldPassword, newPassword)
- Settings.tsx: new page with form (current + new + confirm), client-side
  validation, success/error feedback
- App.tsx: add /settings route
- Layout.tsx: add 'Ajustes' nav item
This commit is contained in:
2026-07-05 22:31:45 -04:00
parent 4ae7335b31
commit 72a8336087
8 changed files with 174 additions and 1 deletions
+32
View File
@@ -10,6 +10,11 @@ type loginRequest struct {
Password string `json:"password"`
}
type changePasswordRequest struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
if s.Auth == nil {
writeError(w, http.StatusServiceUnavailable, "auth not configured")
@@ -47,3 +52,30 @@ func (s *Server) handleAuthStatus(w http.ResponseWriter, r *http.Request) {
"username": username,
})
}
func (s *Server) handleChangePassword(w http.ResponseWriter, r *http.Request) {
if s.Auth == nil {
writeError(w, http.StatusServiceUnavailable, "auth not configured")
return
}
username, ok := s.Auth.currentUser(r)
if !ok {
writeError(w, http.StatusUnauthorized, "authentication required")
return
}
defer r.Body.Close()
var req changePasswordRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request body")
return
}
if req.OldPassword == "" || req.NewPassword == "" {
writeError(w, http.StatusBadRequest, "old_password and new_password are required")
return
}
if err := s.Auth.ChangePassword(username, req.OldPassword, req.NewPassword); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}