PHP İle Kendi Password Managerinizi Yapmak | Localhost'ta Password Manager | MySQL (PDO) & JS & PHP

codinger06

Üye
1 Mar 2023
161
136
127.0.0.1
Merhaba, bu uzun yazıda nasıl kendi şifre yönetici uygulamamızı yaparız onu uzun uzun anlatıp kodlarını paylaşacağım.
Şifre yöneticimizi yaparken PHP, JavaScript, MySQL, HTML ve CSS kullanacağız. HTML ve CSS kısmına pek değinmeyeceğim asıl önemli olarak gördüğüm kısım JavaScript ila PHP kısmı.

Önden kaynak kodlarını bırakayım : İndir Password Manager rar

Herhangi çalıştırılabilir bir dosya barındırmadığından, saf JavaScript, PHP, HTML ve CSS içerdiğinden dolayı VirusTotal taraması koymaya gerek görmedim, fakat ne olur ne olmaz diye en iyisi bırakayım.
Virus Total


İlk önce config.php'den başlayalım. Burası herkese göre değişkenlik gösterecektir, veritabanı bağlantı bilgilerini burada sabit olarak tutuyoruz.

PHP:
<?php
const DB_NAME = "password_manager";
const USERNAME = "root";
const PASSWORD = "";
?>

Gördüğünüz gibi gerekli olan bilgileri verdik. Siz de Localhost'ta nasıl bir şey oldu diye görmek isterseniz şu şekilde veritabanı tablosu açmayı unutmayın.



Veritabanı konfigürasyonlarımızı yaptığımıza göre şimdi PasswordManager sınıfımızı yazalım. PasswordManager.php ise şu şekilde olacak:
PHP:
require "config.php";

class PasswordManager{
    private PDO $db;

    function __construct(){
        $this->db = new PDO("mysql:host=localhost;dbname=".DB_NAME.";charset=UTF8;", USERNAME, PASSWORD);
        if(@$_GET["func"] && @$_POST["param"]){
            $this->{$_GET["func"]}($_POST["param"]);
        }else if(@$_GET["func"]){
            $this->{$_GET["func"]}();
        }
    }
}

İlk olarak config.php dosyamızı dahil ediyoruz, sınıfımızın içerisine özel bir PDO türünde bir alan ekliyoruz. Sadece sınıfın kendisi o değişkene ulaşabilecek. Veritabanı bağlantısını o değişkene koyacağız. PasswordManager sınıfı çağırıldığı zaman $db değişkenimizi standart bir PDO Veritabanı bağlantısı olarak dolduruyoruz. config.php'de oluşturduğumuz sabitleri burada kullanıyoruz.

Daha sonrasında biz yeri gelecek JavaScript dosyalarımızdan PHP'ye istek göndereceğiz, dinamiklik açısından JavaScript olmazsa olmaz yoksa saf PHP de yazardık her sayfayı yenilediğimizde yeni veri gelirdi değil mi? JavaScript'ten PHP tarafına istek gönderirken GET parametreleri sayesinde PasswordManager içerisinde hangi fonksiyondan veri almak istediğimizi belirteceğiz. Bu yüzden görmüş olduğunuz kod diyor ki;


Eğer URL kısmında GET olarak func parametresi verilmişse VE aynı zamanda POST olarak "param" değişkeni verilmiş ise:
Bu sınıf içerisindeki "x" fonksiyonunu çalıştır ve "y" parametresini ver.

Eğer sadece fonksiyon verilmişse, parametre yoksa bu sınıfın içerisindeki "x" fonksiyonunu çağır, içine parametre olarak bir şey verme.


JavaScript tarafından istek atarken şu tarz URL ile istek atacağız: passwordmanager.php?func=addPassword POST olarak ise sonradan vereceğimiz değer gidecek.

Şimdi ise veritabanına yeni şifre ekleyecek olan addPassword fonksiyonunu yazalım.
PHP:
    function addPassword($param){
        $domain = htmlspecialchars(trim($param["domain"]), ENT_QUOTES);
        $details = htmlspecialchars(trim($param["details"]), ENT_QUOTES);
        $password = htmlspecialchars(trim($param["password"]), ENT_QUOTES);
         
        $query = $this->db->prepare("INSERT INTO passwords (domain,details,password) VALUES ('$domain', '$details', '$password')");
        if($query->execute()){
            echo 1;
        }
    }

Burada gördüğümüz üzere bir $param değişkeni var, bu değişken JavaScript tarafından göndereceğimiz JSON türünde veri olacak. Sonrasında bu değişkene şu şekilde veri göndereceğiz; {"param":{"domain":"blabla","details":"vs. vs."}} Bunların hepsine yakın zamanda geleceğiz şimdilik PHP tarafını inceliyoruz, sadece verinin nasıl geldiğini aklınızda canlandırın bu şekilde bir JSON verisini işleyeceğiz.

Gelen verileri < > ' gibi karakterlerden arındırıyoruz, bir çok açığı kapatmaya yardımcı olabilir. Verinin sağında solunda sadece boşluktan oluşan yerler varsa temizliyoruz ve veritabanına bu verileri ekliyoruz. Eğer başarılı bir şekilde eklenmiş ise ekrana 1 yazdıracağız. Bu 1'i niye yazdırıyoruz JavaScript tarafında göreceğiz. 1 değil de "oldu" diye bir şey de yazdırabilirdik JavaScript tarafında yaptığımız işlemin başarılı olup olmadığını kontrol edeceğiz.


Aynı zamanda artık JavaScript tarafına da geçelim. request.js'yi inceliyoruz.
JavaScript:
function request(func, method, param){
    var result = $.ajax({
        url : "./PasswordManager.php?func="+func,
        type : method,
        data : {"param" : param},
        async : false,
    })
    return result.responseText;
}

Bir fonksiyon var, bu fonksiyon "func", "method", ve "param" parametrelerini alıyor. Bu fonksiyon çalıştığı zaman bir URL adresine istek yolluyor. Aynı yukarıda bahsettiğimiz üzere ?func= şekilinde bizim vermiş olduğumuz fonksiyon adını çağırıyor. GET ile mi istek atacağız POST ile mi onu da bizden alıyor ve yukarıda bahsettik yine JSON ile veri gönderiyoruz "param" : param şekilinde request fonksiyonuna verdiğimiz veriyi PHP tarafına "param" adlı değişken ile yolluyor, ve en son bu fonksiyon bize gittiği yerde ne gördüğünü söylüyor adeta. Ekrana gittiği yerde ne yazılmış ise onu bize veriyor. Bahsettik ya 1 yazıyor diye, işte diyeceğiz ki atıyorum request("fonksiyon", "GET", paramvsvs) == 1 ise ; başarılı yazdır mesela.


JavaScript:
function addPassword(domain, details, password){
    setTimeout(() => {
        listPasswords();      
    }, 500);
    return request("addPassword", "POST", {"domain" : domain, "details" : details, "password" : password});
}

Burada request fonksiyonunu kullanarak şifre ekleme işlemini yapıyoruz. Gerekli olan veriyi JSON ile gönderiyoruz
JSON Verisi Örneği

{

"json-data" : "merhaba",
"diger-veri" : [1,2,3,4],
"obje" : {"merhaba1" : 1, "ikimerhaba" : 2}
}

PHP tarafında $param["domain"] ile çekiyorduk bakın orada param yerine yazmışız zaten PHP tarafında aynı bir PHP dizisine veya objesine nasıl ulaşıyorsak gelen JSON verisine de öyle ulaşabiliriz.

Ekrana yazdırılan veriyi bu fonksiyon da döndürüyor, ve orada bir listPasswords() görüyoruz yarım saniye sonra çalıştırıyor. O orada kalsın birazdan geleceğiz.


Şimdi tekrar PHP tarafına dönelim listPasswords() fonksiyonunu inceleyeceğiz. Bu JavaScript tarafındaki aynı fonksiyon değil.
PHP:
    function listPasswords(){
        $passwords = "";

        $query = $this->db->query("SELECT * FROM passwords", PDO::FETCH_ASSOC);

        if($query->rowCount() == 0){
            die(0);
        }

        foreach ($query as $password) {
            $passwords .= '{"id" : "'.$password["id"].'","domain" : "'.$password["domain"].'", "details" : "'.$password["details"].'", "password" : "'.$password["password"].'"},';
        }

        echo '{"passwords" : ['.rtrim($passwords, ",").']}';

    }

Öncelikle boş string olarak bir değişken oluşturuyoruz. İçini dolduracağız. Sonrasında veritabanına bir sorguda bulunuyoruz tüm şifreleri bize getir diyoruz. Eğer hiçbir şifre yok ise ekrana 0 bastırıp fonksiyondan çıkış yapıyoruz. Sonrasında veritabanından gelen verileri alıp ekrana yazdırıyoruz fakat fark ettiğiniz üzere düz yazı olarak değil.

Bir JSON olarak ekrana yazdırıyoruz. Sonrasında JavaScript tarafından bu sayfaya istek attığımızda karşımıza bir JSON verisi çıkacak ve onu kolayca işlenebilir veri haline getirebileceğiz. Her şifre için yine bir JSON objesi oluşturuyoruz ve bunu $password değişkenine ekliyoruz. Birden çok obje olduğu için bunları dizi şeklinde tutacağız. En sonunda ekrana yazdırırken yine bir JSON objesi içinde yazıdırıyoruz "passwords" : [password1,password2,password3] şeklinde olacak. Toplanan tüm şifreleri o şekilde ekrana bastırıyoruz.

Rtrim dememizin sebebi normalde veriler [{},{}] şeklinde depolanır fakat bizim birleştirmemiz ile [{},{},] olarak sonunda bir virgül daha kalıyor. Onu silmiş oluyoruz.




Tekrardan JavaScript tarafına geliyoruz.


JavaScript:
function listPasswords(){
    var passwords = request("listPasswords", "GET", null);
    if(passwords == 0){
        document.getElementById("content").getElementsByTagName("table")[0].innerHTML = `
            <div class="no-password">
                Hiçbir kayıtlı şifreniz yok.
            </div>
       
        `
        return 0;
    }
    passwords = JSON.parse(passwords).passwords;

    var table = document.getElementById("content").getElementsByTagName("table")[0];
    table.innerHTML = "";

    passwords.forEach(p => {
        let id = p.id;
        let domain = p.domain;
        let details = p.details;
       


        if(details.length >= 38){
            details = details.substring(0,39) + "...";
        }
        table.innerHTML += `      

        <tr>
            <td><img height="20" width="20" src='http://www.google.com/s2/favicons?domain=${domain}'></td>
            <td>${domain}</td>
            <td>${details}</td>
            <td><button class="btn-danger" onclick="deletePassword(${id})">Sil</button></td>
            <td><button style="width:60px;" onclick="btnGetPasswordInfo(${id})" class="btn-primary">Aç</button></td>
        </tr>


       
       
        `;
    });
   
}

İlk öncelikle yazmış olduğumuz PHP'deki listPasswords fonksiyonuna istek atıyoruz ve gelen veriyi (JSON olarak dönen şifreler) bir değişkene atıyoruz. Orada null vermemizin sebebi POST olarak ayrıyeten bir veri göndermiyoruz o yüzden sadece veri alıyoruz.

Eğer dönmüş olan veri 0 ise yukarıda bahsettik 0 bastırdığını bu demek oluyor ki hiç şifre yok o yüzden HTML'de incelerseniz görürsünüz sonucumuzu göreceğimiz alana çıktıyı bastırıyoruz Kayıtlı şifreniz yok diye, sonra fonksiyondan çıkıyoruz ama eğer kayıt varsa devam. Gelen JSON datası çiğ bir şekilde işlenmeyi bekliyor.

Kendisi sadece bir string. Bu stringi bir fonksiyon yardımı ile ulaşılabilir interaktif bir veriye dönüştürüyoruz. Şifreleri {"passwords":[p1,p2,p3]} şeklinde kaydetmiştik işlediğimiz JSON nesnesi içerisindeki passwords'u değişkene atıyoruz. Şimdi elimizde bildiğiniz saf bir dizi var, veritabanındaki şifrelere sahibiz. Şifreleri yazdırmadan önce ilk önce yazdıracağımız alanı temizliyoruz. Sonra tasarım anlamında önceden hazırladığım şablonla yazdırıyoruz. Her şifrenin ID'sini domainini vs. yazdırıyoruz. orada gördüğünüz if bloğu diyor ki eğer açıklamanın karakterleri 38'den büyükse hepsini yazdırma böl 38'e kadar olan kısmını sonuna ... yazdır diyor. Yoksa çok taşar eğer oraya uzun bir açıklama yazdıysanız.

http://www.google.com/s2/favicons?domain=https://turkhackteam.org bunu bilmiyor olabilirsiniz domain= kısmına yazdığınız sitenin iconunu size veriyor. Bu sayede istediğimiz her sitenin iconuna sahip olabiliyoruz.



request.js'deki son fonksiyonlarımız ise
JavaScript:
function deletePassword(id){
    request("deletePassword", "POST", {"id":id});
    listPasswords();
}
function getPasswordInfo(id){
    var result = request("getPasswordInfo", "POST", {"id":id});

    if(result == 0){return 0;}

    return JSON.parse(result);
}

Yukarıda görmüştünüz onclick= falan yazıyordu orada diyorduk ki bu butona basıldığı zaman şu fonksiyonu çağır ve parametre olarak o şifrenin ID'sini ver. deletePassword fonksiyonunda bir ID bilgisi göndererek PHP tarafında şifrenin silinmesi görevini üstlenen sınıfı çağırıyoruz ve şifre silindikten sonra tablonun en son halini görmek için listPasswords(); fonksiyonunu çağırıyoruz.

getPassword()'da ise PHP tarafında bir fonksiyona ID bilgisini göndererek yine istek atıyoruz. Eğer dönen veri 0 ise biz de 0 döndürüyoruz. Popup kısmında burayı göreceğiz, şifreyi aç dediğimizde önümüze popup çıkacak o popupu şifrenin verileri ile dolduracağız. Eğer dönen veri 0 değilse bize gelen şifre verilerine JSON'a dönüştürüp döndürüyoruz.


Şimdi PHP tarafına geçip delete ve getInfo görevlerini inceleyelim.

PHP:
    function deletePassword($param){
        $id = htmlspecialchars(trim($param["id"]), ENT_QUOTES);
        $query = $this->db->prepare("DELETE FROM passwords WHERE id='$id'");
        $query->execute();
    }

    function getPasswordInfo($param){
        $id = htmlspecialchars(trim($param["id"]), ENT_QUOTES);
        $query = $this->db->prepare("SELECT * FROM passwords WHERE id='$id'");
        $query->execute();

        $password = "";
       
        if($query->rowCount() == 0){die(0);}

        foreach ($query as $p) {
            $password .= '{"domain" : "'.$p["domain"].'", "details" : "'.$p["details"].'", "password" : "'.$p["password"].'"},';
        }

        echo rtrim($password, ",");
       
       
    }

deletePassword'da yapılan işlem oldukça basit, gelen ID'ye sahip olan şifreyi temizliyoruz. Gelen veriyi özel karakterlerden arındırmamız önemli, sonra bir şifreyi sildim sanırsınız; başkaları tüm tabloyu silebilir :D

getPasswordInfo'da ise verdiğimiz ID değerinde olan tek bir şifreyi çekiyoruz. Bu şifrenin özelliklerini yine JSON olarak kaydediyoruz. Sonrasında bir diziye koymadan bunu ekrana yazdırıyoruz. Tek bir şifre ile çalıştığımızdan ötürü diziye koymamıza gerek olmadan direkt tek bir obje halinde JSON olarak yazdırıyoruz.


Asıl işlevsel kısım tamam, şimdi biraz UI kısmına geçelim.

JavaScript:
class PopupAddPassword{
    content = `
        <h2>Şifre Ekle</h2>
        <form id="form-add-password">
            <div>
                Domain : <input type="text" id="domain">
            </div>
            <div>
                Detaylar : <textarea id="details" cols="30" rows="10" autocomplete="off"></textarea>
            </div>
            <div>
                Şifre : <input type="text" id="password" autocomplete="off"><div class="random" onclick="randomGenerate()">Rastgele Oluştur</div>
            </div>
            <button class="btn-primary" id="button-add-password">Şifre Ekle</button>
        </form>
    `;
    popupClass = "popup-add-password"
}
class PopupPasswordInfo{
    content = `
        <div>
            Domain : <span><b id="domain-span"></b></span>
        </div>
        <div>
            Detaylar :
            <span id="details-span" class="details">
            </span>
        </div>
        <div>
            Şifre : <span><b id="password-span"></b></span>
        </div>

    `;
    popupClass = "popup-get-password";
}
class PopupSpecialized{
    constructor(content, popupClass){
        this.content = content;
        this.popupClass = popupClass;
    }
}

class Popup{
    constructor(popup=null){
        if(popup != null){
            document.getElementById("background").style.display = "flex";
            document.getElementById("background").innerHTML += `
            <div class="popup ${popup.popupClass}">
                <div class="header">
                    <div class="close"></div>
                </div>
                <div class="content">
                    ${popup.content}
                </div>
            </div>
            `  
   
            document.getElementsByClassName("close")[0].addEventListener("click", () => {
                this.clearPopups();
            })
   
            document.getElementById("background").onclick = e => {
                if(e.target.id == "background"){
                    this.clearPopups();
                }
            }  
        }
    }

    clearPopups(){
        document.getElementById("background").style.display = "none";
        document.getElementsByClassName("popup")[0].remove();
    }
}


Bu şekilde bir kodumuz var, bu kodda kullanacağımız Popup türlerini tanımladım. İçeriklerini, ve hangi css sınıfını alacaklarını yazdım. Specialized olanında ise sınıfı çağırırken içeriğini ve css sınıfını biz belirleyeceğiz. Popup sınıfını çağırırken verdiğimiz sınıfın içerisindeki html içeriğini bir popup açarak ekrana çıkartacak. İşte eğer kırmızı kapatma tuşuna basarsa popupları kapatacak vs. bunlar anlatımda pek önem vermediğim şeyler.

Ama bakın buraya.

JavaScript:
function writePasswordInfo(domain, details, password){
    document.getElementById("domain-span").innerText = domain;
    document.getElementById("details-span").innerText = details;
    document.getElementById("password-span").innerText = password;
}

Yukarıdaki sınıflardan birinin içeriğinde şifre bilgilerini yazdıracaktık. O alanların ID'si var o alanların ID'sine bu fonksiyonda verdiğimiz değerleri yazdıracak şimdi o değerleri alma işlerine gelelim yavaş yavaş.

JavaScript:
function btnGetPasswordInfo(id){
    var passwordInfo = getPasswordInfo(id);
   
    if(passwordInfo == 0){
        new Popup(new PopupSpecialized("Nasıl!? Talep edilen şifre ID'si bulunamıyor, görünüşe bakılırsa işler iyiye gitmiyor."));
        return 0;
    }

    new Popup(new PopupPasswordInfo());

    var domain = passwordInfo.domain;
    var details = passwordInfo.details;
    var password = passwordInfo.password;

    writePasswordInfo(domain, details, password);
}

Burada yazmış olduğumuz getPasswordInfo fonksiyonundan belirli bir ID'deki şifrenin bilgilerini çekiyor. Eğer dönen bilgi sıfır ise ki bu normal değil işlemekte olduğumuz ID veritabanında yok demek bu da olamaz yapay şartlarla gerçekleştirilmedikçe. Çünkü biz HTML kısmına onclick=fonksiyon(id) yazdırıyorduk o sırada hangi şifreyi ekrana yazdırıyorsa aç butonunun onclick eventine o idyi koyuyordu kısacası imkansız. O yüzden ekrana böyle bir uyarı mesajı atabiliriz bunun gibi durumlarda.

JavaScript:
document.getElementById("mypasses-add-password").onclick = () => {
    new Popup(new PopupAddPassword());

    document.getElementById("form-add-password").onsubmit = e => {
        e.preventDefault();
        var domain = document.getElementById("domain").value;
        var details = document.getElementById("details").value;
        var password = document.getElementById("password").value;
   
        var result = addPassword(domain, details, password);

        clearPopups();    
        if(result == 1){
            new Popup(new PopupSpecialized("Şifre başarıyla eklendi.", "popup-alert"));    
        }else{
            new Popup(new PopupSpecialized("Şifre eklenirken bir sorun oluştu.", "popup-alert"));    
        }
   
   
    }
}

Burada Şifre Ekle butonuna basınca olacaklar ile karşı karşıyayız. Ekrana şifre ekle popupını çıkartıyor, ve kullanıcının verileri girip yollamasını bekliyor. Kullanıcı verileri yollayınca gelen verileri alıyoruz, yazmış olduğumuz addPassword fonksiyonuna atıyoruz. addPassword fonksiyonuna gelen veriyi kaydediyoruz. 1 verisi geliyorsa başarıyla tamamlamış demek oluyordu. Buna göre popuplar oluşturuyoruz.

JavaScript:
function randomGenerate(){
    letters = "ABCDEFGHIJKLMNOPRSTUVYZWXabcdefghijklmnoprstuvyzwx";
    specialChars = "!#+&{([)]=}?*_-.,";
    integer = "12345678790";
    timestamp = String(Date.now());
    timestamp = timestamp.substring(5,timestamp.length-1);
    randomPlace = Math.floor(Math.random() * 21);

    password = "";

    for(let i = 0; i < 7; i++){
        let randLetter = Math.floor(Math.random() * letters.length)
        let randSpecialChar = Math.floor(Math.random() * specialChars.length)
        let randInteger = Math.floor(Math.random() * integer.length);


        password += letters[randLetter] + integer[randInteger] + specialChars[randSpecialChar]
    }

    password = password.substring(0,randomPlace) + timestamp + password.substring(randomPlace+2, password.length-1);
   

    document.getElementById("password").value = password;
}

En son olarak maşallah MD5 encryption algoritması gibi bir şeyi görüyoruz :D Burada güçlü bir şifre oluşturmaya çalışıyoruz. Döngü 7 defa dönüyor 7 defa hem harf hem özel karakter hem rakam yazdırıyoruz 7 x 3 ten 21 adet karakter olmuş oldu sonra timestamp alıp 21 karakterin içerisinde hangi araya o timestampı sıkıştırsak diye random fonksiyonunu kullanıp gelen index değerini timestampa eşitliyoruz. Sonunda bir şifre üretmiş oluyoruz. Tasarıma hiç değinmiyorum siz attığım kaynak kodlarından inceleyebilirsiniz localhostunuzda açıp bakabilirsiniz.


İnanın bu küçük çaplı projeyi yazmak anlatmasından çok daha kolaydı, hepinize başarılar diliyorum umuyorum ki bu yazmış olduğum küçük çaplı proje web geliştirme aşamasında kendini geliştiren insanlara yeni yöntemler, yeni şeyler katmıştır. Kolay gelsin.


 
Üst

Turkhackteam.org internet sitesi 5651 sayılı kanun’un 2. maddesinin 1. fıkrasının m) bendi ile aynı kanunun 5. maddesi kapsamında "Yer Sağlayıcı" konumundadır. İçerikler ön onay olmaksızın tamamen kullanıcılar tarafından oluşturulmaktadır. Turkhackteam.org; Yer sağlayıcı olarak, kullanıcılar tarafından oluşturulan içeriği ya da hukuka aykırı paylaşımı kontrol etmekle ya da araştırmakla yükümlü değildir. Türkhackteam saldırı timleri Türk sitelerine hiçbir zararlı faaliyette bulunmaz. Türkhackteam üyelerinin yaptığı bireysel hack faaliyetlerinden Türkhackteam sorumlu değildir. Sitelerinize Türkhackteam ismi kullanılarak hack faaliyetinde bulunulursa, site-sunucu erişim loglarından bu faaliyeti gerçekleştiren ip adresini tespit edip diğer kanıtlarla birlikte savcılığa suç duyurusunda bulununuz.