v2.5.2
Giriş yap

10 Dakikada bir tıklanabilen buton

mek
874 defa görüntülendi

Merhaba, 1 Kullanıcı ve 1 İlan tablom var. Kullanıcılar ilanlarını ekleyip, yayına alabiliyor. İstek üzerine "Üste taşıma" butonu eklemeye karar verdim fakat butonu aktif olarak bırakmak istemiyorum.

Yaklaşık 2 saattir nasıl bir mantık kurabilirim diye düşünüyorum ama saatten mi yoksa bilgimin yetersiz olduğundan mı bilemiyorum ama kodu tam yazmaya başlarken "o iş öyle olmaz ki" diye beynim uyarıp duruyor.

Google'da çeşitli forumlarda yapmak istediğim konuyu aradım fakat herkes mantığı anlatıp, bırakıp gitmiş. çoğu mantıksız bir mantık anlatıp bir daha cevap bile vermemiş neredeyse.

Yapmak istediğim şey tam olarak şu;

İlanlar listesinde her kullanıcının kendi ilanında görebileceği "Yukarı Taşı" butonunun olması ve bu butona tıklandığı zaman işlem sayfasında tarihe +10 ekleyip, 10 dakika boyunca bu butona tekrar tıklanmamasını sağlamak.

Aslında gayet açık ve net okuduğum zaman kendime anlatabiliyorum ama iş kod yazmaya geldiği zaman tarihe nasıl + 10 ekleyip, o butonu 10 dakika sonra aktif veya pasif edeceğim kısmını kendime anlatamıyorum. Bu konu için bana yardım edebilir misiniz? şimdiden cevaplar için teşekkür ederim.

ebykdrms
813 gün önce

Merhaba. Etikete PHP girmişsiniz ama front-end tarafında bu sorunu çözebilirsiniz.
Bir butona tıklandığı anda tarihi alır, üzerine 10dk ekler ve bu değeri butona atribute olarak eklersiniz.
Butona her tıklandığında bu attribute'ü kontrol edersiniz ve henüz zamanı gelmemişse işlem yaptırmazsınız.
Örnek olarak (JQuery ile):

const isButtonFree = ($button) => {
    const now = (new Date()).getTime();
    const afterTenMinutesTime = (new Date(now + 10 * 60 * 1000)).getTime();
    const buttonCode = Number($button.attr('data-code'));
    if(isNaN(buttonCode) || buttonCode < now) {
        $button.attr('data-code', afterTenMinutesTime);
        return true;
    }
    return false;
};

$(document).on("click", "button.sendToUp", function() {
    if(!isButtonFree($(this))) { alert("Son tıklamanın üzerinden 10 dk geçmemiş!"); return; }
    
    // Butona tıklanınca normalde yapılan işlemler...
    alert("Butona tıklanabildi");
});

Tarayıcıda konsolu kullanmayı bilenler bu işlemi aşamasınlar, diyorsanız ve butona her tıklama işlemi sunucuya bir istek atıyorsa yukarıda verdiğim kodun üstüne bir de PHP tarafında token oluşturmanız ve her butona basışta sunucu tarafında token kontrolü yapmanız gerekir.
Bu mantıkta da PHP tarafında request'e özel bir token üretecek ve response üzerinden bunu tarayıcıya göndereceksiniz.
Tarayıcı bu kodu alıp butona attribute olarak basacak.
Butona her basışta attribute üzerinden bu token değeri okunacak ve request'e eklenecek.
Bu token, içinde 10 dk sonrasının verisini şifreli şekilde tutan ve şifrenin anahtarını bilmeyenin şifreyi çözemeyeceği bir kod olmalı.
Bu konu, Tayfun ERBİLEN'in PHP ile Verileri Şifrelemek makalesinden yardım alınarak çözülebilir.

Şimdi diyelim ki siz butona basıldığında işlem yapıp yapmayacağınızı belirlemek için MoveUp.php sayfasına post isteği atıyorsunuz ve yanıta göre işlem yapıp yapmayacağınıza karar veriyorsunuz.
MoveUp.php

// Aynı kodları tekrar tekrar yazmamak için bir fonksiyon...
function sendResponse($val) {
    header("Content-type: application/json; charset=utf-8");
    echo json_encode($val); 
    exit();
}

// Token şifreleme algoritması ve şifre anahtarı verileri...
$tokenKey = 'prototurk.2022.xx1';
$tokenCipher = 'AES-128-ECB';

// POST parametrelerini alalım.
$token = isset($_POST["token"]) ? $_POST["token"] : null;
$id = isset($_POST["id"]) ? $_POST["id"] : null; // başka parametreler de göndermişsiniz diyelim...

// token veya id değeri yoksa hata döndürelim.
if(!$token) sendResponse(["error"=>"token required", "errorCode"=>1]);
if(!$id) sendResponse(["error"=>"id required", "errorCode"=>1]);

// token çözülememişse (değiştirilmiştir muhtemelen) hata dönelim.
$decodedToken = openssl_decrypt($token, $tokenCipher, $tokenKey);
if(!$decodedToken) sendResponse(["error"=>"token hatalı", "errorCode"=>2]);

// token içindeki değer bir timestamp değeri. Bu değeri şu anın timestamp'iyle karşılaştıralım.
// Eğer henüz token'daki tarih şu anki tarihten önce değilse henüz 10 dakikanın geçmediğine dair hata dönelim.
$tokenTime = (int)$decodedToken;
$now = strtotime("now");
if($now < $tokenTime) sendResponse(["error"=>"It hasn't been 10 minutes.", "errorCode"=>3);

// Buraya kadar her şey yolundaysa butonun 10 dakikası geçmiş demektir.
// Şu andan itibaren 10 dakika sonrasını bulup şifreleyelim.
$afterTenMinutes = strtotime("+10 minute");
$newToken = openssl_encrypt($afterTenMinutes, $tokenCipher, $tokenKey);

// Bundan sonra id gibi diğer parametreleri kullanarak gerekli işlemlerimizi yapıyoruz.
// Sonuç olarak döneceğimiz cevapta mutlaka yeni token değerini de gönderiyoruz ki butonun attribute'sine yeni token değeri yazılabilsin.

sendResponse["success"=>"It can move to up!", "token"=>$newToken];

Bu sayfa sizin butona basıldıktan sonra sunucuya istek attığınız ve sunucu taraflı işleri hallettiğiniz sayfa.
Bu sayfaya POST metoduyla "token" değerini göndermezseniz veya değiştirilmiş bir token gönderirseniz size hata verir.
Haliyle butonlar ilk oluştukları zamanda herhangi bir token değerine sahip olmadıkları için bu sayfaya atacağınız her istek hata döner.
Bu nedenle butonları oluştururken hemen token'larını da vermelisiniz.
Örneğin butonlarınızın bulunduğu front-end sayfanız şöyle olacak:

<?php
// Token şifreleme algoritması ve şifre anahtarı verileri...
$tokenKey = 'prototurk.2022.xx1';
$tokenCipher = 'AES-128-ECB';
$firstToken = openssl_encrypt(strtotime("+10 minute"), $tokenCipher, $tokenKey);
?>
<div class="moveableElement" data-id="123">
    <div class="elementTitle">Örnek içerik 1</div>
    <button class="moveToUpButton" data-token="<?=$firstToken?>">Yukarı Taşı</button>
</div>
<div class="moveableElement" data-id="132">
    <div class="elementTitle">Örnek içerik 2</div>
    <button class="moveToUpButton" data-token="<?=$firstToken?>">Yukarı Taşı</button>
</div>
<div class="moveableElement" data-id="231">
    <div class="elementTitle">Örnek içerik 3</div>
    <button class="moveToUpButton" data-token="<?=$firstToken?>">Yukarı Taşı</button>
</div>

Bu örnekte sayfaya girildiği andan itibaren tüm butonlar, 10 dakika sonrasının şifrelenmiş halini token olarak almış olacaklar.
Yani biri sayfaya ilk kez girerse veya eskaza sayfayı yenilerse 10 dk boyunca yukarı taşıma yapamayacak.
Şimdi daha önce front-end taraflı kontrol sağladığımız kodda istek atma örneği yapalım.
Diyelim ki butona basınca üst elementin data-id attribute'sini alıp veriyi PHP'ye ajax ile iletiyoruz...

const isButtonFree = ($button) => {
    const now = (new Date()).getTime();
    const afterTenMinutesTime = (new Date(now + 10 * 60 * 1000)).getTime();
    const buttonCode = Number($button.attr('data-code'));
    if(isNaN(buttonCode) || buttonCode < now) {
        $button.attr('data-code', afterTenMinutesTime);
        return true;
    }
    return false;
};

$(document).on("click", "button.sendToUp", function() {
    const $thisButton = $(this);
    if(!isButtonFree($thisButton)) { alert("Son tıklamanın üzerinden 10 dk geçmemiş!"); return; }

    const itemId = $(this).closest(".moveableElement").attr("data-id");
    const itemToken = $(this).attr("data-token");
    $.ajax({
        url:"MoveUp.php",
        type:"POST",
        data: { id:itemId, token:itemToken },
        success: (res) => {
            // Eğer sunucu token göndermişse butondaki data-token'ı hemen güncelleyelim.
            if(res.token) $thisButton.attr('data-token', res.token);
            if(res.error) {
                if(res.errorCode===1) alert("eksik parametre");
                else if(res.errorCode===2) alert("token hatalı! Sayfayı yenileyin");
                else if(res.errorCode===3) alert("Henüz 10 dakika geçmemiş...");
                return;
            }
            if(res.success) {
                alert("Tamam. Yukarı Taşıma işlemi yapılabilir...");
                return;
            }
        },
        error: (err) => {
            alert("Sunucuyla iletişimde problem...");
        }
    });
});

Muhakkak sizin yapmak istediğiniz şeyler biraz daha farklıdır. Ama bu cevaptan yola çıkarak fikir yürütebilirsiniz. Bundan daha detaylı cevap alabileceğinizi sanmıyorum. :)

Aslında ilk yazdığım kod bloğundaki işlemler çoğu kullanıcının sürekli Yukarı Taşı yapmasını önleyecektir.
PHP taraflı kontrollerimizle kötü niyetli kullanıcıların bir çoğunu engellemiş olduk.
Peki kötü niyetli bir kullanıcı bu token'la kurduğumuz güvenlik katmanını aşamaz mı? Yeterince tecrübeliyse kolaylıkla aşabilir. Mesela sayfa ilk yüklendiğinde butona verilen token'ı kopyalar. Sonra ilk Yukarı Taşıma işleminden sonra biz javascript'le yeni token'ı butona veririz. Evet bu token'ı silerse veya değiştirirse işlem yapamaz ama ilk kopyaladığı token halen PHP tarafından kabul edilen bir token olduğuna göre bu token'ı istediği butona yapıştırıp tekrar tekrar Yukarı Taşıma işlemi yapabilir. Peki bu kötü niyetli kullanıcı için ne yapabiliriz? Mesela şifreleme yaparken token'a sadece tarih verisini değil aynı zamanda data-id attribute'sindeki değeri de katarız. Böylece her butonun kendine özgü bir token'ı olur. MoveUp.php sayfası da hem tarihi hem de id değerini birlikte konrol eder. Peki böyle yaparak her butonun kendine özgü bir token'ı olmasını sağlamak bu kötü niyetli kullanıcıyı durdurabilir mi? Hayır. Sadece aynı kodu istediği her butonda kullanamamasını sağlamış oluruz. Ama her butonun token'ını yine de kopyala yapıştırla yeniden kullanabilir. Demek ki bir token'ın kullanıldıktan sonra tekrar kullanılamamasını sağlayacak bir algoritmamız olması lazım...
Bunlar daha derinlikli siber güvenlik konuları olduğundan bambaşka bir soru'nun konusu olabilir...