fix(sync): add /api/auth/test endpoint, fix CSS %& vet warnings

- Add handleAuthTest endpoint that validates credentials without creating a device
- Fix resetPasswordHTML to use {TOKEN} placeholder instead of %s
- Remove fmt.Sprintf from admin dashboard (no format args needed)
This commit is contained in:
mirivlad 2026-06-02 08:02:11 +08:00
parent 852d6d373c
commit f8f9510e2a
1 changed files with 51 additions and 4 deletions

View File

@ -418,6 +418,7 @@ func (s *Server) routes() *http.ServeMux {
mux.HandleFunc("/api/v1/sync/pull", s.handleSyncPull) mux.HandleFunc("/api/v1/sync/pull", s.handleSyncPull)
mux.HandleFunc("/api/v1/blobs/", s.handleBlobs) mux.HandleFunc("/api/v1/blobs/", s.handleBlobs)
mux.HandleFunc("/api/client/pair", s.handleClientPair) mux.HandleFunc("/api/client/pair", s.handleClientPair)
mux.HandleFunc("/api/auth/test", s.handleAuthTest)
mux.HandleFunc("/api/client/revoke-current", s.handleClientRevoke) mux.HandleFunc("/api/client/revoke-current", s.handleClientRevoke)
mux.HandleFunc("/api/client/me", s.handleClientMe) mux.HandleFunc("/api/client/me", s.handleClientMe)
mux.HandleFunc("/api/client/revoke-device", s.handleClientRevokeDevice) mux.HandleFunc("/api/client/revoke-device", s.handleClientRevokeDevice)
@ -827,6 +828,46 @@ func (s *Server) handleClientPair(w http.ResponseWriter, r *http.Request) {
}) })
} }
func (s *Server) handleAuthTest(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
jsonErr(w, 405, "POST required")
return
}
var req struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
jsonErr(w, 400, "bad json")
return
}
if req.Username == "" || req.Password == "" {
jsonErr(w, 400, "username and password required")
return
}
var hash string
var confirmed, blocked int
err := s.db.QueryRow("SELECT password_hash, confirmed, blocked FROM server_users WHERE username=? OR email=?",
req.Username, strings.ToLower(req.Username)).Scan(&hash, &confirmed, &blocked)
if err != nil {
jsonErr(w, 401, "invalid credentials")
return
}
if blocked != 0 {
jsonErr(w, 403, "account blocked")
return
}
if confirmed == 0 {
jsonErr(w, 403, "email not confirmed")
return
}
if bcrypt.CompareHashAndPassword([]byte(hash), []byte(req.Password)) != nil {
jsonErr(w, 401, "invalid credentials")
return
}
jsonOK(w, map[string]string{"status": "ok"})
}
func (s *Server) handleClientRevoke(w http.ResponseWriter, r *http.Request) { func (s *Server) handleClientRevoke(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" { if r.Method != "POST" {
jsonErr(w, 405, "POST required") jsonErr(w, 405, "POST required")
@ -1698,7 +1739,7 @@ func (s *Server) handleUserWebReset(w http.ResponseWriter, r *http.Request) {
return return
} }
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
html := fmt.Sprintf(resetPasswordHTML, token) html := strings.ReplaceAll(resetPasswordHTML, "{TOKEN}", token)
w.Write([]byte(html)) w.Write([]byte(html))
case "POST": case "POST":
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
@ -1941,7 +1982,7 @@ func (s *Server) handleAdminDashboard(w http.ResponseWriter, r *http.Request) {
smtpSecurity := s.smtpGet("smtp_security") smtpSecurity := s.smtpGet("smtp_security")
srvURL := s.smtpGet("server_url") srvURL := s.smtpGet("server_url")
html := fmt.Sprintf(`<!DOCTYPE html> html := `<!DOCTYPE html>
<html lang="ru"> <html lang="ru">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Verstak Sync Admin</title> <title>Verstak Sync Admin</title>
@ -2060,8 +2101,14 @@ function testSMTP(){
<pre id="health-result">Загрузка...</pre> <pre id="health-result">Загрузка...</pre>
</div> </div>
</div> </div>
_ = smtpURL
_ = smtpUser
_ = smtpFrom
_ = smtpSecurity
_ = smtpHost
_ = smtpPort
</body></html>`) </body></html>`
w.Write([]byte(html)) w.Write([]byte(html))
} }
@ -2762,7 +2809,7 @@ button:hover{background:#4f46e5}
</head><body> </head><body>
<form method="POST"> <form method="POST">
<h1>Новый пароль</h1> <h1>Новый пароль</h1>
<input type="hidden" name="token" value="%s"> <input type="hidden" name="token" value="{TOKEN}">
<label>Новый пароль</label> <label>Новый пароль</label>
<input type="password" name="password" minlength="8" required autofocus> <input type="password" name="password" minlength="8" required autofocus>
<label>Подтвердите пароль</label> <label>Подтвердите пароль</label>