Initial commit: DSH Web Manager
This commit is contained in:
@@ -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})
|
||||
}
|
||||
Reference in New Issue
Block a user