package routes import ( "bssapp-backend/auth" "bssapp-backend/internal/auditlog" "bssapp-backend/internal/security" "database/sql" "encoding/json" "net/http" "golang.org/x/crypto/bcrypt" ) func ChangeOwnPasswordHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") // -------------------------------------------------- // 1️⃣ JWT CLAIMS // -------------------------------------------------- claims, ok := auth.GetClaimsFromContext(r.Context()) if !ok || claims == nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return } // -------------------------------------------------- // 2️⃣ PAYLOAD // -------------------------------------------------- var req struct { CurrentPassword string `json:"current_password"` NewPassword string `json:"new_password"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid payload", http.StatusBadRequest) return } if req.CurrentPassword == "" || req.NewPassword == "" { http.Error(w, "current_password and new_password required", http.StatusBadRequest) return } // -------------------------------------------------- // 3️⃣ PASSWORD POLICY // -------------------------------------------------- if err := security.ValidatePassword(req.NewPassword); err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } // -------------------------------------------------- // 4️⃣ MEVCUT HASH ÇEK // -------------------------------------------------- var currentHash string err := db.QueryRow(` SELECT password_hash FROM mk_dfusr WHERE id = $1 AND is_active = true `, claims.ID).Scan(¤tHash) if err != nil { http.Error(w, "user not found", http.StatusUnauthorized) return } // -------------------------------------------------- // 5️⃣ CURRENT PASSWORD CHECK // -------------------------------------------------- if bcrypt.CompareHashAndPassword( []byte(currentHash), []byte(req.CurrentPassword), ) != nil { http.Error(w, "current password incorrect", http.StatusUnauthorized) return } // -------------------------------------------------- // 6️⃣ NEW HASH // -------------------------------------------------- newHash, err := bcrypt.GenerateFromPassword( []byte(req.NewPassword), bcrypt.DefaultCost, ) if err != nil { http.Error(w, "hash error", http.StatusInternalServerError) return } // -------------------------------------------------- // 7️⃣ UPDATE (⚠️ force_password_change DEĞİŞMEZ) // -------------------------------------------------- _, err = db.Exec(` UPDATE mk_dfusr SET password_hash = $1, password_updated_at = now(), updated_at = now() WHERE id = $2 `, string(newHash), claims.ID) if err != nil { http.Error(w, "password update failed", http.StatusInternalServerError) return } // -------------------------------------------------- // 8️⃣ AUDIT // -------------------------------------------------- auditlog.Write(auditlog.ActivityLog{ ActionType: "PASSWORD_CHANGED", ActionCategory: "security", ActionTarget: claims.Username, IsSuccess: true, }) // -------------------------------------------------- // 9️⃣ RESPONSE // -------------------------------------------------- _ = json.NewEncoder(w).Encode(map[string]any{ "success": true, }) } }