diff --git a/backend/handlers/user/profile.go b/backend/handlers/user/profile.go index 1c1e521..01b9806 100644 --- a/backend/handlers/user/profile.go +++ b/backend/handlers/user/profile.go @@ -13,6 +13,7 @@ import ( "time" "github.com/go-chi/chi/v5" + "golang.org/x/crypto/bcrypt" "golang.org/x/text/cases" "golang.org/x/text/language" @@ -486,3 +487,69 @@ func ListAdminUsersHandler(w http.ResponseWriter, r *http.Request) { return } } + +type ChangePasswordRequest struct { + CurrentPassword string `json:"current_password"` + NewPassword string `json:"new_password"` +} + +func ChangePasswordHandler(w http.ResponseWriter, r *http.Request) { + userID, ok := mw.GetUserID(r.Context()) + if !ok { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + + var req ChangePasswordRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "invalid request", http.StatusBadRequest) + return + } + + if req.CurrentPassword == "" || req.NewPassword == "" { + http.Error(w, "current password and new password are required", http.StatusBadRequest) + return + } + + if len(req.NewPassword) < 8 { + http.Error(w, "password must be at least 8 characters", http.StatusBadRequest) + return + } + if len(req.NewPassword) > 72 { + http.Error(w, "password must be less than 72 characters", http.StatusBadRequest) + return + } + + var passwordHash string + err := db.DB.QueryRow(r.Context(), `SELECT password_hash FROM users WHERE id = $1`, userID).Scan(&passwordHash) + if err != nil { + if err == sql.ErrNoRows { + http.Error(w, "user not found", http.StatusNotFound) + return + } + log.Printf("Failed to fetch password hash for user %s: %v", userID, err) + http.Error(w, "server error", http.StatusInternalServerError) + return + } + + if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(req.CurrentPassword)); err != nil { + http.Error(w, "current password is incorrect", http.StatusUnauthorized) + return + } + + newHash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) + if err != nil { + log.Printf("Failed to hash new password for user %s: %v", userID, err) + http.Error(w, "server error", http.StatusInternalServerError) + return + } + + _, err = db.DB.Exec(r.Context(), `UPDATE users SET password_hash = $1, updated_at = NOW() WHERE id = $2`, string(newHash), userID) + if err != nil { + log.Printf("Failed to update password for user %s: %v", userID, err) + http.Error(w, "failed to update password", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} diff --git a/backend/main.go b/backend/main.go index 970133b..9f35720 100644 --- a/backend/main.go +++ b/backend/main.go @@ -139,6 +139,7 @@ func main() { r.Get("/user/profile", user.GetProfileHandler) r.Put("/user/profile", user.UpdateProfileHandler) + r.Put("/user/change-password", user.ChangePasswordHandler) r.Delete("/user/account", user.DeleteAccountHandler) r.Get("/user/loyalty", user.GetLoyaltyHandler) diff --git a/frontend/src/lib/components/admin/ApprovalModal.svelte b/frontend/src/lib/components/admin/ApprovalModal.svelte index 4825343..bdcc4ee 100644 --- a/frontend/src/lib/components/admin/ApprovalModal.svelte +++ b/frontend/src/lib/components/admin/ApprovalModal.svelte @@ -416,27 +416,14 @@