MCPcopy Index your code
hub / github.com/PatchMon/PatchMon / ChangePassword

Method ChangePassword

server-source-code/internal/handler/auth.go:1052–1116  ·  view source on GitHub ↗

ChangePassword handles PUT /auth/change-password.

(w http.ResponseWriter, r *http.Request)

Source from the content-addressed store, hash-verified

1050
1051// ChangePassword handles PUT /auth/change-password.
1052func (h *AuthHandler) ChangePassword(w http.ResponseWriter, r *http.Request) {
1053 userID, _ := r.Context().Value(middleware.UserIDKey).(string)
1054 if userID == "" {
1055 Error(w, http.StatusUnauthorized, "Unauthorized")
1056 return
1057 }
1058 var req struct {
1059 CurrentPassword string `json:"currentPassword"`
1060 NewPassword string `json:"newPassword"`
1061 }
1062 if err := decodeJSON(r, &req); err != nil {
1063 Error(w, http.StatusBadRequest, "Invalid request body")
1064 return
1065 }
1066 if req.CurrentPassword == "" {
1067 Error(w, http.StatusBadRequest, "Current password is required")
1068 return
1069 }
1070 if err := ValidatePasswordPolicy(h.resolved, req.NewPassword); err != nil {
1071 Error(w, http.StatusBadRequest, err.Error())
1072 return
1073 }
1074 user, err := h.users.GetByID(r.Context(), userID)
1075 if err != nil || user == nil {
1076 Error(w, http.StatusNotFound, "User not found")
1077 return
1078 }
1079 if user.PasswordHash == nil {
1080 Error(w, http.StatusBadRequest, "Cannot change password for OIDC-only accounts")
1081 return
1082 }
1083 hash := strings.TrimSpace(*user.PasswordHash)
1084 if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(req.CurrentPassword)); err != nil {
1085 Error(w, http.StatusUnauthorized, "Current password is incorrect")
1086 return
1087 }
1088 newHash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), 12)
1089 if err != nil {
1090 Error(w, http.StatusInternalServerError, "Failed to hash password")
1091 return
1092 }
1093 if err := h.users.UpdatePassword(r.Context(), userID, string(newHash)); err != nil {
1094 Error(w, http.StatusInternalServerError, "Failed to change password")
1095 return
1096 }
1097 // Security baseline: invalidate everything that grants access without
1098 // re-entering the new password on another browser.
1099 // * Trusted-device rows → attacker with a trust cookie cannot skip MFA.
1100 // * Other sessions → attacker with a refresh_token cannot keep a live session.
1101 // We keep the caller's own session alive so the UI doesn't log them out
1102 // mid-action after a successful password change.
1103 if h.trustedDevices != nil {
1104 if err := h.trustedDevices.RevokeAllForUser(r.Context(), userID); err != nil && h.log != nil {
1105 h.log.Error("change password revoke trusted devices failed", "user_id", userID, "error", err)
1106 }
1107 }
1108 currentSessionID, _ := r.Context().Value(middleware.SessionIDKey).(string)
1109 if h.sessions != nil {

Callers

nothing calls this directly

Calls 10

ErrorFunction · 0.85
decodeJSONFunction · 0.85
ValidatePasswordPolicyFunction · 0.85
clearDeviceTrustCookieFunction · 0.85
ErrorMethod · 0.80
JSONFunction · 0.70
UpdatePasswordMethod · 0.65
ValueMethod · 0.45
GetByIDMethod · 0.45
RevokeAllForUserMethod · 0.45

Tested by

no test coverage detected