PHP + sql provjera za registraciju novog korisnika

Pokušavam složiti jedan jednostavni CMS i to ide ne tako loše. Ipak, vidio sam da mi register funkcija ne radi baš najbolje. Korisnik se uredno može registrirati, pa tako i logirati u svoj profil, ali je problem što mi dva ili više korisnika mogu imati isti username i isti email.

https://pastebin.com/x0pid98Y

Gore je kod, a ovaj dio ne mogu da stavim u pogon (red 28-35)

//  $sql         = "select * from users where (username = '$username' or email = '$email');";
//  $row         =  mysqli_fetch_assoc($res);
// form validation: ensure that the form is correctly filled
//  if ($username==$row['username']) {
//    echo "Username already exists";
//  }

može li pomoć oko ovoga? Hvala

Najjednostavnije staviš u sql za polje username i email unique check. Pogledaj primjer ovdje.

https://www.w3schools.com/sql/sql_unique.asp

A pri spremanju u bazu novog korisnika ako već postoji isti e-mail i username bacit će ti exception. Taj exception uhvatiš u try catch blok i ispišeš da je takav korisnik već registriran.

Zašto selektiraš usere koji imaju isti username ili e-mail?
Zašto onda provjeravaš je li jednak username ili e-mail?

Po meni je puno bolje samo prebrojiti slogovoe s istim username ili e-mailom.
Ako ima više od 0, javlja se greška “Username ili e-mail postoji”.

Druga opcija je da prvo brojiš userrname i onda po potrebi i e-mail.

Naravno, baza je zadnji bastion obrane koja mora imati unique polja username i unique e-mail.

2 Likeova

Hmm, išao sam da provjerim redove u bazi, da query vidi jeli imam taj username…

Taj pristup je i meni logičan (da baza ne dopušta dupli unos korisničkog imena ili email adrese). Problem je što ne znam kako uhvatiti exception, da ne baci grešku na zaslon korisniku, i da ne poništi unos polja koja su OK…

Pogledat ću neko rješenje u tom pravcu. Hvala

Ne tak. Uniq polje imaš da baza čuva svoj intigritet…kak reče trnac, to je zadnja linija obrane…a ti program nikad ne želiš tjerat svjesno u grešku.

Trnac ti je sve rekao…čim select po username vrati nešto, znaš da nesmiješ upisivat jer username već postoji. Isto ponoviš za provjeru emaila.

…napisem jos pokoju kad sjednem za komp. :slight_smile:

1 Like

Tako je točno su ti @bozoou i @trnac rekli.
Probaj u tom smjeru ići.

1 Like

Možeš ići sa dva querya, u jednom provjeriš ima li postojećih unosa sa istim username, a drugim queryem dohvaćaš postojeće redove sa istim emailom. Ako ti neki od tih query-a nešto vrati, odmah znaš na čemu si.

Ne trebaš čak niti dohvaćati redove, u ovom slučaju je dovoljno da dohvatiš broj redaka postojećih unosa.

U slučaju da ideš sa jednim queryem kao što ti ideš, onda ti trebaju dvije provjere:

if ($username==$row['username']) $userExist = true;
if ($email==$row['email']) $emailExist = true;

No meni se čini da tebi ne predstavlja taj dio problem…nego da si zapeo uopće u čitanju itema koji se nalaze u varijabli $row, jer ti taj dio kodea ne liči baš ispravno.
Moraš negdje imati while loop koji iterira kroz sve redove koji su dohvaćeni iz baze. Kod mene taj dio codea je nekada izgleda ovako:

$query="SELECT * FROM table_name WHERE param=$param";
$result = mysql_query($query);
while($row = mysql_fetch_assoc($result)){

	print_r($row) //...čini nešto sa row, gdje se ovaj dio codea iterira za svaki row koji je dohvaćen iz baze
}

Za dulje staze se preporučuje na prelazak na PDO za rad sa bazom, sada mi taj dio izgleda ovako:

$query="SELECT * FROM table_name WHERE param=$param";
$STH = $db->prepare($query); $STH->execute();
while($row = $STH->fetch(PDO::FETCH_ASSOC)){
	
	print_r($row) //...čini nešto sa row, gdje se ovaj dio codea iterira za svaki row koji je dohvaćen iz baze
}

Tu moraš imati definiranu varijablu $db koju kreiraš prilikom konekcije na bazu. U prvoj varijanti se ne sjećam kako je taj code znao na koju se bazu odnosi čitanje.

1 Like

Prije registracije provjeris da li username postoji i provjeriš da li postoji email ,sa count:

select count() from user where username=?
select count(
) from user where email=?

A u bazi na koloni username staviš unique index, kao i za email.

Za mysqli i pdo imam složene funkcije kojoj predam sql statement kao parametar:
npr.
select count(*) from user where username=?

i drugi parametar mi je array sa podacima:
npr. $data = array(‘peroperic’) ili $data = array(‘[email protected]’)

query_select($sql, $data)

funkcija vraća array sa poljima i ne treba nikakava for petlja.

1 Like

Hvala svima na javljanju. Riješio sam ovaj problem, pa da postavim kod za moguće vaše komentare ili sugestije…

Imao sam

if (isset($_POST['register_btn'])) {
	    		register();
	}

a sada sam na button dodao sql provjeru

if (isset($_POST['register_btn'])) {
	$username = $_POST['username'];
	$sql = "SELECT * FROM users WHERE username='$username'";
	$results = mysqli_query($db, $sql);
	if (mysqli_num_rows($results) > 0) {
		array_push($errors, "Username $username taken");
	}else{
		register();
	}}

Kasnije ću doraditi + email provjeru, ali to je to. Još jednom hvala svima :smiley:

@komentar

Koristi prepare statement i čišćenje inputa.

Ovo ti je podložno sql injection-u.

Napravi si klasu za db.

1 Like
$sql ="INSERT INTO com (id_private, name, city, address, phone, fax, email, description) VALUES(?, ?, ?, ?, ?, ?, ?, ?)";
if($stmt=$mysqli->prepare($sql)){
  $stmt->bind_param("ssssssss", $id_private, $name, $city, $address, $phone, $fax, $email, $description);
  $stmt->execute();
$stmt->close();
} }

Ovako nešto? Ovaj kod mi radi. Kako misliš čišćenje inputa?

Još jedno pitanje. Ovaj dio koda koristim za unos

$mysqli = new mysqli('localhost', 'myuser', 'myPassword', 'mydb');
$errors = array();

	if(isset($_POST['save_btn_com'])){
    $id_private=$_POST['id_private'];
    $name=$_POST['name'];
    $city=$_POST['city'];
    $address=$_POST['address'];
    $phone=$_POST['phone'];
    $fax=$_POST['fax'];
    $email=$_POST['email'];
    $description=$_POST['description'];
$sql = "SELECT * FROM com WHERE id_private='$id_private'";
$results = mysqli_query($db, $sql);
if (mysqli_num_rows($results) > 0) {
  array_push($errors, "This ID $id_private is already taken");
}
    if (empty($id_private)) {
  		array_push($errors, "ID-number required");
  	}
    if (empty($name)) {
  		array_push($errors, "Name is required");
  	} else{
$sql ="INSERT INTO com (id_private, name, city, address, phone, fax, email, description) VALUES(?, ?, ?, ?, ?, ?, ?, ?)";
if($stmt=$mysqli->prepare($sql)){
  $stmt->bind_param("ssssssss", $id_private, $name, $city, $address, $phone, $fax, $email, $description);
  $stmt->execute();
$stmt->close();
} }}

Provjera radi OK, unos tako-tako

Dakle, ako kliknem na dugme Save, a query se ne izvrši zbog uvjeta if , u bazu se ne zapiše redak, ali mi se ID (PRIMARY KEY, AUTO_INCREMENT) poveća za taj broj pogrešnih pokušaja.

Jel ti poznat SQL injection? Rekao bih da je to jedan od glavnih propusta web stranice/baze na koji se mora paziti.

Fora je u tome da ti netko pod username pošalje sljedeću vrijednost, tipa:

bozoou' and DELETE WHERE username!='0

ti kada to ugradiš u svoj query izraz koji izgleda npr:
SELECT * FROM users WHERE username='$username'

dobit ćeš izraz za izvršavanje sljedeći query:

SELECT * FROM users WHERE username='bozoou' and DELETE WHERE username!='0'

Ne znam sada da li sam točno napisao delete statement koji ide nakon select naredbe…ali svakako da se može i tu dopisati. Skužio si poantu i problem.

Zato svi parametri koji se dodaju u $query moraju proći “čišćenje” …ili ti ga escape() metodu.

Ima više načina na koji se to može napraviti, ali nikako se nemoj pouzdati na to što na client strani radiš provjeru input parametara. Sve što dolazi sa client strane je uvijek podložno manipulaciji da se zaobiđu tvoje provjere sa javascriptom. Jedina istinska provjera može biti na server strani, a provjere na client strani postoje samo radi user experienca …ili ti ga da obavijestiš korisnika da je neki input krivo kucao. (Onaj koji namjerno želi krivo kucati, uvijek to može :wink: )

Svakako si proguglaj malo o SQL injection, nije velika materija.

Code nisam ni gledao, ali sumnjam da je u tome riječ. Jer ako se nešto nije upisalo AUTO_INCREMENT nebi smio biti taknut.

Da nisi negdje nešto brisao u bazi? Jer kada brišeš neki redak …AUTO_INCREMENT će nastaviti brojati kao da ništa nije brisano. Nekako mi se čini da ti je to…

P.S. ima ti PHP lijepšu sintaksu za array_push metodu, dovoljno ti je kucati:

$someArray[]="nesto za insert";

if (mysqli_num_rows($results) > 0) {
  array_push($errors, "This ID $id_private is already taken");
}

Ovaj dio ne zaustavlja izvršenje koda. U slučaju da si postavio kolonu ‘$id_private’ kao unique, red se neće upisati ali će povećati ID za jedan.

    if (empty($id_private)) {
  		array_push($errors, "ID-number required");
  	}

Ovaj dio također ne zaustavlja izvršenje koda.

if (empty($name)) {
  		array_push($errors, "Name is required");
  	}

Tek kod ovog dijela, ako je ‘empty($name)’ tvrdnja tačna neće se izvršiti ‘else{}’ petlja, tj. neće doći do pokušaja upisa u bazu.

Ako želiš da sve potencijalne greške smestiš u array $erroes, možeš napraviti nešto ovako.

 //provera da li $id_private već postoji u bazi
    if (mysqli_num_rows($results) > 0) {
      array_push($errors, "This ID $id_private is already taken");
    }
    //provera da li je $id_private prazan
    if (empty($id_private)) {
    	array_push($errors, "ID-number required");
    }
    //provera da li je $name prazan
    if (empty($name)) {
    	array_push($errors, "Name is required");
    }
    //provera da li postoji grešaka
    if (empty($errors)) {
        // uradi nešto sa $errors, npr.
        // vrati array
            /*
             * $arr_errors = $errors;
             * unset($errors);
             * return $arr_errors;
             */
        // ili vrati JSON
            /*
             * $json_errors = json_encode($errors);
             * unset($errors);
             * return $json_errors;
             */
    } else { // Ako je $errors prazan tj. nepostoji greška, dodaj query u bazu
    $sql ="INSERT INTO com (id_private, name, city, address, phone, fax, email, description) VALUES(?, ?, ?, ?, ?, ?, ?, ?)";
    if($stmt=$mysqli->prepare($sql)){
      $stmt->bind_param("ssssssss", $id_private, $name, $city, $address, $phone, $fax, $email, $description);
      $stmt->execute();
    $stmt->close();
    } }}
1 Like

Thanks.

Riješio si mi dva problema. Sada se IF provodi kako sam i zamislio, a u slučaju greške nemam povećanje ključa za broj grešaka.

Ovako sam uradio err count

if (count($errors) == 0) {
      $sql ="INSERT INTO com (id_private, name, city, address, phone, fax, email, description) VALUES(?, ?, ?, ?, ?, ?, ?, ?)";
      if($stmt=$mysqli->prepare($sql)){
        $stmt->bind_param("ssssssss", $id_private, $name, $city, $address, $phone, $fax, $email, $description);
        $stmt->execute();
      $stmt->close();

} }

Za UI/UX ti treba JS napomena korisnicima koja su polja mandatorna pa i sa JS validacijom koja odbija dalju egzekuciju i interaktivno edukuje korisnika koja polja će mu ubuduće biti potrebna na ovoj tački. A za PHP validaciju ti onda u tom slučaju ne treba vraćanje niza grešaka već samo prve greške čime ćeš postići kod lakši za čitanje. Dalje, bez obzira da l’ ćeš poslušati i uraditi tako, $id_private provjera treba (a i $name) da stoji ispred mysql query-ja. A ako poslušaš ovo sšto ti pričam onda u tom slučaju nećeš bespotrebno stresirati bazu. Recimo:

if (!isset($_POST['id_private']) || empty($_POST['id_private']) || false === lotOfMoreValidationChecks($_POST['id_private'])) {
    // set to flash session message about this error
    // which will be checked and read at page load
    // header('Location: same_page.php');
    // exit;
}
//...
// same for other post data

// if everything passed set variables

$idPrivate = $_POST['id_private'];

Ovo ti je samo provjera dostavljenih podataka i ne treba baza da te zanima dok god nisi utvrdio sve što se može utvrditi. Tek onda pozivaš bazu i naravno, nastaviš sa prepared statements.

1. validacija input podataka
 - ako nisu validni return false/error
2. provjera da l' postoji već u bazi
 - ako postoji return false/error
3. ubacivanje u bazu
 - return true/success

Ovo ti navodim defensive programming primjer ili uopšteno

if (!foo) {
    return false;
}

if (!bar) {
    return false;
}

// we are good
baz = foo + bar
// use baz with no fear

Na ovaj način nikad ne moraš da pamtiš varijable osim onih koje su posljednje u liniji egzekucije. K’o u ovom primjeru, iskoristio si foo i bar da bi dobio novu vrijednost za korištenje i koju dalje pamtiš i koristiš. Problem je ako zaboraviš neki od ovih if blokova ali tome služe testovi (TDD neće ni dozvoliti da zaboraviš).
To bi bilo aplikativno na ono kako sam inicijalizov’o $idPrivate varijablu u primjeru gore.
Nezgodna stvar je što u kodu koji ne koristi puno standarda i paradigme, svi mi koji pokušavamo pomoći imamo za nijansu razlicčite pristupe: neko koristi defensive, neko offensive neko varijable ovako, neko Yoda condition onako ( :upside_down_face: ) i najbitnija stvar je da izvučeš najuopšteniji vid kako da to odradiš.

Predlažem Symfony i Doctrine ORM ako ćeš (a treb’o bi) da koristiš framework.
Može i Laravel i Eloquent. Sam koristim Laravel ali predlažem Symfony jer će te navesti na odredjenu bolju praksu.
Ako se odlučiš za Laravel ovo će ti biti vrlo jednostavno za uraditi (i validaciju i insert) bukvalno u par linija koda.

2 Likeova