Initial commit: DSH Web Manager

This commit is contained in:
2026-10-13 09:34:47 +08:00
commit 85332bbc56
135 changed files with 19712 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
package api
import (
"encoding/json"
"io"
"net/http"
"time"
"golang.org/x/crypto/bcrypt"
)
func (a *API) changePassword(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
if r.Method != http.MethodPost {
fail(w, 405, "method not allowed")
return
}
if _, _, err := a.user(r); err != nil {
fail(w, 401, "authentication required")
return
}
var in struct {
CurrentPassword string `json:"current_password"`
Password string `json:"password"`
}
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4096))
decoder.DisallowUnknownFields()
if decoder.Decode(&in) != nil || decoder.Decode(new(any)) != io.EOF || in.CurrentPassword == "" || len(in.Password) < 12 || len(in.Password) > 72 {
fail(w, 400, "current password and new password 12–72 bytes required")
return
}
// Recheck credentials after waiting for concurrent logins or password resets.
a.authLoginMu.Lock()
defer a.authLoginMu.Unlock()
u, _, err := a.user(r)
if err != nil {
fail(w, 401, "authentication required")
return
}
locked, err := a.accountLocked(u.Username)
if err != nil {
fail(w, 503, "authentication unavailable")
return
}
if locked {
fail(w, 429, "account temporarily locked; retry later")
return
}
currentHash, err := a.s.Password(u.Username)
if err != nil {
fail(w, 503, "authentication unavailable")
return
}
if len(in.CurrentPassword) > 72 || bcrypt.CompareHashAndPassword([]byte(currentHash), []byte(in.CurrentPassword)) != nil {
if err = a.failedLogin(u.Username); err != nil {
fail(w, 503, "authentication unavailable")
return
}
fail(w, 400, "invalid current password")
return
}
hash, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
if err != nil {
fail(w, 500, "password error")
return
}
tx, err := a.s.DB.BeginTx(r.Context(), nil)
if err != nil {
fail(w, 500, "password change failed")
return
}
defer tx.Rollback()
_, err = tx.ExecContext(r.Context(), "UPDATE users SET pass_hash=? WHERE id=?", string(hash), u.ID)
if err == nil {
_, err = tx.ExecContext(r.Context(), "DELETE FROM auth_refresh WHERE sid IN (SELECT id FROM sessions WHERE uid=?)", u.ID)
}
if err == nil {
_, err = tx.ExecContext(r.Context(), "DELETE FROM sessions WHERE uid=?", u.ID)
}
if err == nil {
_, err = tx.ExecContext(r.Context(), "DELETE FROM auth_failures WHERE username=?", u.Username)
}
if err == nil {
_, err = tx.ExecContext(r.Context(), "INSERT INTO audit(uid,action,target,detail,at) VALUES(?,?,?,?,?)", u.ID, "user.change_password", u.ID, "", time.Now().Unix())
}
if err == nil {
err = tx.Commit()
}
if err != nil {
fail(w, 500, "password change failed")
return
}
writeJSON(w, map[string]bool{"ok": true, "reauthenticate": true})
}