127 lines
3.5 KiB
Go
127 lines
3.5 KiB
Go
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,
|
|
})
|
|
}
|
|
}
|