Problema functie de inregistrare

Daca un vizitator vrea sa se inregistreze si apasa de mai multe ori pe pe butonul Submit, contul se insereaza de mai multe ori in baza de date.

        <form action="post.php" method="post" >
            <div class="mb-3">
                <label for="username" class="form-label">Username</label>
                <input type="text" class="form-control" name="username" id="username" required pattern="[a-zA-Z0-9]+" title="Please use aplhanumeric charaters only." tabindex="1">
            </div>
            <div class="mb-3">
                <label for="email" class="form-label">Email</label>
                <input type="text" class="form-control" id="email" name="email" tabindex="2"  required {literal}pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"{/literal}>
            </div>				
            <div class="mb-3">
                <label for="password" class="form-label">Password</label>
                <input type="password" class="form-control" id="password" name="password" tabindex="2">
            </div>
            <div class="mb-3">
                <label for="passwordConfirm" class="form-label">Repeat Password</label>
                <input type="passwordConfirm" class="form-control" id="passwordConfirm" name="passwordConfirm" tabindex="2">
            </div>	

            <button type="submit" class="btn btn-primary" tabindex="3" >Submit</button>
        </form>

post.php

		if (isset($_POST['username'], $_POST['password'], $_POST['passwordConfirm'])) {

			$username = $_POST['username'];
			$password = $_POST['password']; 
			$passwordConfirm = $_POST['passwordConfirm']; 
			$email = $_POST['email'];
			
			
			$register = register($username, $password, $passwordConfirm, $email);
			
			if ($register == 0) {
				// register success 		
				$_SESSION["SuccessMessage"] = 'You have successfully registered';
				Redirect_to($site_url."/login");
			} else  if($register == 1){
				// register failed 
				$_SESSION["ErrorMessage"] = 'The username you entered is invalid. Please use alphanumerical charaters only';
				Redirect_to($site_url."/register");
			} else  if($register == 2){
				// register failed 
				$_SESSION["ErrorMessage"] = 'Passwords do not match';
				Redirect_to($site_url."/register");
			}else  if($register == 3){
				// register failed 
				$_SESSION["ErrorMessage"] = 'Your passwords must meet the following criteria: </br> - Must be a minimum of 8 characters</br> -  Must contain at least 1 number</br> - Must contain at least one uppercase character</br> - Must contain at least one lowercase character</br>';
				Redirect_to($site_url."/register");
			}else  if($register == 4){
				// register failed 
				$_SESSION["ErrorMessage"] = 'The email you entered is invalid';
				Redirect_to($site_url."/register");
			}else  if($register == 5){
				// register failed 
				$_SESSION["ErrorMessage"] = 'This email is used by another user';
				Redirect_to($site_url."/register");
			}else  if($register == 6){
				// register failed 
				$_SESSION["ErrorMessage"] = 'This username is used by another user';
				Redirect_to($site_url."/register");		
			}
		}


functia

function register($username, $password, $passwordConfirm, $email){
	global $conn;

    $error=false;
    if(isValidUsername($username)==1){
        $error=true;
        return "1";
    }
    if($password!=$passwordConfirm){
        $error=true;
        return "2";
    }
    if(isValidPassword($password)==0){
        $error=true;
        return "3";
    }
    if((!filter_var($email, FILTER_VALIDATE_EMAIL))){
        $error=true;
        return "4";
    }

	$username_c = encrypt($username);
	$email_c = encrypt($email);
	
    //Check that username is not already in use, if it is return an error.
    try{
        $stmt = $conn->prepare("SELECT username FROM users WHERE username=:username");
        $stmt->bindParam(':username', $username_c);
        $stmt->execute();
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
    }catch(PDOException $exception){ 
        //error log
    }

    if ($stmt->rowCount() > 0) {
        $error=true;
        return "5";
    }

    //Check that email is not already in use, if it is return an error.
    try{
        $stmt = $conn->prepare("SELECT username FROM users WHERE email=:email");
        $stmt->bindParam(':email', $email_c);
        $stmt->execute();
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
    }catch(PDOException $exception){ 
        //error log
    }

    if ($stmt->rowCount() > 0) {
        $error=true;
        return "6";
    }

    //If no errors, continue with registration 
    if($error==false){
        $options = ['cost' => 12];
        // prepare sql and bind parameters
        try{
            $stmt = $conn->prepare("INSERT INTO users (username, password, email)
            VALUES (:username, :password, :email)"); 
            $stmt->bindParam(':username', encrypt($username));
            $stmt->bindParam(':password', password_hash($password, PASSWORD_DEFAULT, $options));
            $stmt->bindParam(':email', encrypt($email));		
            $stmt->execute();     
			
        }catch(PDOException $exception){ 
            //error log
        }
		
        return "0";
    }
}

In mod normal, un vizitator nu poate folosi un username sau o adresa de email deja inregistrate
Problema apare atunci cand ele nu exista in baza de date iar clientul apasa rapid de mai multe ori butonul Submit.

De ce se insereaza de mai multe ori? Unde este problema?

Singura solutie gasita de mine a fost sa folosesc Recaptcha v2 dar as vrea sa gasesc alta solutie care sa nu depinda de conexiunea la internet

Poti defini anumite campuri UNIQUE in baza de date (eg. email).
:slightly_smiling_face:

2 Likes

Mersi! A functionat

Sau la onclick poti face butonul de submit disabled

1 Like

Este mai simplu cu UNIQUE și să lași baza de date să faca treaba în locul tau.

de ce nu ambele? :upside_down_face:

la primul click pe butonul submit il dezactivezi pana ce vine raspunsul din backend (acolo unde verifici si daca usernameul/emailul nu exista deja in baza de date, inainte sa incerci sa-l inserezi).

Nu sunt neapărat de acord cu abordarea, o să detaliez mai jos.* :smiley:

La problema de mai sus sunt două aspecte:

  1. Pe partea de front-end nu ar trebui să lași utilizatorul să poată da click în disperare pe butonul de înregistrare. Disabled pe butonul de submit sau alte variante care îl împiedică să facă asta sunt OK.
  2. Pe partea de back-end ar trebui ca în pasul 1 să validezi dacă există sau nu utilizatorul respectiv, după care să faci insert-ul (ceea ce faci deja). Odată cu validarea decizi dacă mai iei și alte măsuri: poate vrei să-i trimiți un e-mail cu un link de resetare parolă sau există alte scenarii prin care treci dacă un utilizator existent cere iar cont.

*evident, configurarea câmpului din baza de date să fie Unique nu are de ce să nu fie un layer în plus, dar poți avea situații în care la final datele nu vor fi, de fapt, unice - poate la soft delete de utilizator vrei ca atunci când se înregistrează iar să îi generezi alt id din diverse motive, de exemplu.

1 Like

Pasul asta este usless. Singurul lucru pe care il obtii este un query in plus degeaba, iar daca nu ai field unique in baza de date sunt cazuri in care se poate intampla sa adaugi acelasi lucru de 2 ori.

1 Like

Nu neaparat. Daca nu face validare in backend, mysql returneaza eroare atunci cand se loveste de dublura unui camp unic.
Cred ca pe langa camp unic in baza de date trebuie si validare pentru a arata userului de ce nu merge sa salveze.

Exact ce spuneam și eu, există situații în care este posibil să vrei comportamentul ăsta.

Daca validarea se face doar pe backend, o sa fie o problema de UX, utilizatorul va fi bulversat de ce i se spune ca deja adresa de email inserata la inregistrare este folosita.

Da, trebuie unique pe adresa de email, in acest caz, iar pe partea de frontend rezolvarea poate fi cu CSRF tokeni.

Aceste principii te ajuta depilda si la un formular de comentarii, sa stii ca ultilizatorul a pus cate un comentariu, si nu din greseala apasa submit de mai multe ori, ori un bot adauga cate comentarii vrea el.

Multe librarii au deja aceasta functionalitate demult implementata.