Files
dsh-web/internal/api/password.go
T

95 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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})
}