I have form with client-side verification by jQuery, which works good. But now for security reasons I want to add also server-side verification (php) for users without JavaScript. I created few functions and array "errors", where errors are logged. After submit I want run the verification. If no errors are logged, continue, if there are errors exit the script. But that part doesn't work, it always continue. My script:
if (isset($_POST['submit'])) {
require_once 'verify_form.php';
$errors = array(
'username' => null,
'password1' => null,
'password2' => null,
'email1' => null,
'email2' => null,
'age' => null
);
validate_all($errors);
if(empty($errors['username']) && empty($errors['password1']) && empty($errors['password2']) && empty($errors['email1']) && empty($errors['email2']) && empty($errors['age'])) {
//do something
} else {
$_SESSION['errorsArray'] = $errors;
header('Location: /registracia');
exit;
}
}
verify_form.php
<?php
function validate_all($errors)
{
validUsername($errors);
validPassword1($errors);
validPassword2($errors);
validEmail1($errors);
validEmail2($errors);
validAge($errors);
}
function validUsername($errors)
{
include 'config.php';
$username=$_POST['usernameReg'];
if (strlen($username) < 3 || strlen($username) > 16) {
$errors['username'] = "Zadajte uživateľské meno v rozmedzí 3 - 16 znakov.";
}
$query = "SELECT * FROM `users` WHERE `username` = '$username'";
$result = mysqli_query($link, $query) or die(mysqli_error($link));
if (mysqli_num_rows($result) > 1) {
$errors['username'] = "Toto uživateľské meno už niekto používa.";
}
}
function validPassword1($errors)
{
$password1=$_POST['password1Reg'];
$regex = '/^([a-zA-Z]|[0-9]|[-]|[_]|[/]|[.])+([a-zA-Z]|[0-9]|[-]|[_]|[/]|[.])+([a-zA-Z]|[0-9]|[-]|[_]|[/]|[.])$/';
if (!preg_match($regex, $password1)) {
$errors['password1'] = 'Vaše heslo obsahuje nepovolené znaky.';
}
if (strlen($password1) < 6) {
$errors['password1'] = 'Heslo musí obsahovať minimálne 6 znakov.';
}
}
function validPassword2($errors)
{
$password2=$_POST['password2'];
if ($password1 != $password2) {
$errors['password2'] = 'Zadané heslá sa nezhodujú.';
}
}
function validEmail1($errors)
{
include 'config.php';
$email1=$_POST['email1'];
$regex = "/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/";
if (!preg_match($regex, $email1)) {
$errors['email1'] = 'Neplatná e-mailová adresa.';
}
$query = "SELECT * FROM `users` WHERE `email` = '$email1'";
$result = mysqli_query($link, $query) or die(mysqli_error($link));
if (mysqli_num_rows($result) > 1) {
$errors['email1'] = "Tento e-mail už niekto používa.";
}
}
function validEmail2($errors)
{
$email2=$_POST['email2'];
if ($email1 != $email2) {
$errors['email2'] = 'Zadané e-maily sa nezhodujú.';
}
}
function validAge($errors)
{
$age=$_POST['age'];
$regex = "/^([0-9]|[0-9][0-9])$/";
if (!preg_match($regex, $age)) {
$errors['age'] = 'Vek musí byť číslo v rozsahu od 0-99.';
}
}
?>
Why the script always continue?
You're passing the $errors array into the validUsername() function. The function doesn't actually receive the original array, but instead it gets a copy of it. You're modifying the copy, but the original is never modified. Here's a smaller example to show you how this works:
function addCheese(Array $arr)
{
$arr[] = 'cheese';
}
$a = array();
addCheese($a);
var_dump($a);
// Outputs:
// array(0) {
// }
One way to fix this would be to modify each validation function to return the modified array:
function validSomething($errors)
{
// ... do validation checks
return $errors;
}
... and then assign the updated version to the external value:
function validate_all($errors)
{
$errors = validUsername($errors);
$errors = validPassword1($errors);
$errors = validPassword2($errors);
$errors = validEmail1($errors);
$errors = validEmail2($errors);
$errors = validAge($errors);
return $errors;
}
Alternatively you could return the local array of errors and assemble them together, or just pass by reference (although this might cause other problems later on).
I'd strongly recommend using some sort of framework to do your validation: this will save you a lot of time in the long run.
function validEmail2($errors) doesn't make sence... $email1 is not defined and will always be != $_POST['email2']
You need to give the valid functions pointers to $errors. For example
function validUsername(&$errors)
Related
I use OOP and i wanted to ask you guys how this would be done! I keep trying but its still not working ;(
Here is my class file:
class Signup {
// Error
public $error = array();
public function validate($username, $email_mobile, $password) {
if(!empty($username) || !empty($email_mobile) || !empty($password)){
if(strlen($username) < 3 || strlen($username) > 50){
$this->error = "Username is too short or too long!";
return $this->error;
}elseif(strlen($email_mobile) < 3 || strlen($email_mobile) > 50) {
$this->error = "Email is too short or too long!";
return $this->error;
}elseif(strlen($password) < 3 || strlen($password) > 50){
$this->error = "Password is too short or too long!";
return $this->error;
}
} else {
$this->error = "Please fill are required feilds";
return $this->error;
}
}
Here is my signup file
$error[] = $signup->validate($username, $email_mobile, $password);
<?php
// require('lib/function/signup.php');
if(isset($error)){
var_dump($error);
foreach ($error as $value) {
echo $value . "<br>";
}
}
?>
I know That im calling the $error in the same file and not the property error. But i dont know how to send this array to the other file! Please help me! Also i have Called everything and the problem is just with my code(i think), i only included my file and made a var to call my signup class
It is never too early in your development career to study coding standards. Jump straight to PSR-12, and adopt all of these guidelines to write beautiful, professional code.
Use data type declarations in your classes where possible, it will improve the data integrity throughout your project(s).
You appear to prefer returning an array of errors. For this reason, I see no benefit in caching the errors long-term in a class property. This coding style is fine to do, but you could choose to return nothing (void) and instead populate a class property $errors, then access it directly after the $signup->validate() call via $signup->errors or use a getter method.
The empty() checks are too late in the flow. Once the values have been passed to the class method, these values must already be declared. For this reason empty() is needless overhead to check for mere "falsiness". Just check the values' string length.
Your data quality checks seem a little immature (email and password checks should be much more complex), but I won't confuse you with any new complexity, but I do expect that your validation rules will increase as you realize that users cannot be trusted to put good values in forms without be forced to do so. For this reason, it is probably unwise to use a loop to check the value lengths because you will eventually need to write individual rules for certain values.
A possible write up:
class Signup
{
public function validate(
string $username,
string $email,
string $password
): array
{
$errors = [];
$usernameLength = strlen($username);
if ($usernameLength < 3 || $usernameLength > 50) {
$errors[] = "Username must be between 3 and 50 characters";
}
$emailLength = strlen($email);
if ($emailLength < 3 || $emailLength > 50) {
$errors[] = "Email must be between 3 and 50 characters";
}
$passwordLength = strlen($password);
if ($passwordLength < 3 || $passwordLength > 50) {
$errors[] = "Password must be between 3 and 50 characters";
}
return $errors;
}
}
When calling this method...
$signup = new Signup();
$errors = $signup->validate(
$_POST['username'] ?? '',
$_POST['email'] ?? '',
$_POST['password'] ?? ''
);
if ($errors) {
echo '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>';
} else {
echo 'No errors';
}
You should add elements to the array, instead of overwriting it, and returning, on all the branches.
class Signup {
public $errors = [];
public function validate($username, $email_mobile, $password) {
if (empty($username)) {
$this->error[] = "Username cannot be empty";
} else {
$strlenUsername = strlen($username);
if ($strlenUsername < 3 || $strlenUsername > 50){
$this->errors[] = "Username is too short or too long!";
}
}
if (empty($email_mobile)) {
$this->error[] = "Email cannot be empty";
} else {
$strlenEM = strlen($email_mobile);
if ($strlenEM < 3 || $strlenEM > 50) {
$this->errors[] = "Email is too short or too long!";
}
}
if (empty($password)) {
$this->errors[] = "Password cannot be empty";
} else {
$strlenPass = strlen($password);
if ($strlenPass < 3 || $strlenPass > 50) {
$this->errors[] = "Password is too short or too long!";
}
}
return $this->errors;
}
}
If you always keep the same constrains for the three fields, you can shorten it:
class Signup {
public function validate($username, $email_mobile, $password) {
$errors = [];
$fields = [
'Username' => $username,
'Email' => $email_mobile,
'Password' => $password
];
foreach($fields as $key => $value) {
if (empty($value)) {
$errors[] = "$key cannot be empty";
} else {
$strlen = strlen($value);
if ($strlen < 3 || $strlen > 50) {
$errors[] = "$key is too short or too long!";
}
}
}
return $errors;
}
}
The above code guesses at what you are trying to do, if you just wanted a fix for not getting any results on $error see the original answer below.
Original answer.
Updating your code to this should give you the results you expect.
class Signup {
// Error
public $error = array();
public function validate($username, $email_mobile, $password) {
if (!empty($username) || !empty($email_mobile) || !empty($password)){
$strlenUsername = strlen($username);
$strlenEM = strlen($email_mobile);
$strlenPass = strlen($password);
if ($strlenUsername < 3 || $strlenUsername > 50){
$this->error[] = "Username is too short or too long!";
} elseif ($strlenEM < 3 || $strlenEM > 50) {
$this->error[] = "Email is too short or too long!";
} elseif ($strlenPass < 3 || $strlenPass > 50){
$this->error[] = "Password is too short or too long!";
}
} else {
$this->error[] = "Please fill are required feilds";
}
return $this->error;
}
}
Keep in mind that, since you are using if-else you will always have, at most, one element in the array, it is hard to tell what you are trying to do with certainty, so I didn't change the logic and just fixed the most obvious problem.
If you want to add error messages to the array, get rid of the else keyword on the conditionals.
If you want to only receive one error message, consider using a string instead of an array.
I made this function to check for expected request variables. It was working great until I realized that if two values (Not keys) were the same, it would return a positive number as though a key was missing. Consider the following code:
function requestCheck($expectedAr)
{
if(isset($_GET) && isset($_POST))
{
$requestAr = array_unique(array_merge($_GET, $_POST));
}elseif(isset($_GET)){
$requestAr = $_GET;
}elseif(isset($_POST)){
$requestAr = $_POST;
}else{
$requestAr = array();
}
$diffAr = array_diff_key(array_flip($expectedAr),$requestAr);
if(count($diffAr) > 0)
{
returnError("Missing variables: ".implode(',',array_flip($diffAr)).".");
}else {
return $requestAr;
}
}
$requestAr = requestCheck(['name','password']);
if 'name' and 'password' both hold the same value, it will run returnError(). Not seeing why.
Here's a dump of $_POST:
array (
'poolName' => 'xpool',
'userPrefix' => 'xpool'
)
array_unique will strip unique values so you'll end up with either name or password but not both.
Solution:
function requestCheck($expectedAr) {
if(isset($_GET) && isset($_POST)) {
$requestAr = $_REQUEST;
}elseif(isset($_GET)) {
$requestAr = $_GET;
}elseif(isset($_POST)) {
$requestAr = $_POST;
}else{
$requestAr = array();
}
$diffAr = array_diff_key(array_flip($expectedAr),$requestAr);
if(count($diffAr) > 0)
{
returnError("Missing variables: ".implode(',',array_flip($diffAr)).".");
}else {
return $requestAr;
}
}
$requestAr = requestCheck(['name','password']);
I think it's safe to also do the following:
function requestCheck($expectedAr) {
$requestAr = isset($_REQUEST) && is_array($_REQUEST)?$_REQUEST:array();
$diffAr = array_diff_key(array_flip($expectedAr),$requestAr);
if(count($diffAr) > 0) {
returnError("Missing variables: ".implode(',',array_flip($diffAr)).".");
}else {
return $requestAr;
}
}
$requestAr = requestCheck(['name','password']);
I'm working on a loader that will load a file once the user authenticated correctly, but before I want to start my file stream I want to check their HWID to check so it matches the HWID on the database and I have managed to do it and I do it like this:
function validate_Hwid(){
global $db, $encryptionEngine;
if (isset($_GET['username']) && isset($_GET['hwid'])) {
$username = $encryptionEngine->init($_GET['username'],"decrypt");
$hwid = $encryptionEngine->init($_GET['hwid'],"decrypt");
$query = $db->simple_select("users", "*", "LOWER(username)='".$db->escape_string(my_strtolower($username))."'", array('limit' => 1));
$user = $db->fetch_array($query);
if ($hwid == $user['hwid']) {
return 1;
} else {
return 0;
}
} else {
return 0;
}
}
but I have a problem, if the user never logged in before the HWID on the DB will be null, how can I change so if HWID on the user is null, than insert the string I provide?
if ($user['hwid'] == null)
{
// insert and call func again
} elseif ($hwid == $user['hwid']) {
return 1;
} else {
return 0;
}
I am trying this code as part of form processing:
<?php
if(isset($_POST['senderEmail']))
{
try
{
require '_php/_security/validation.php'; //SEE BELOW
$rules = array(
'senderEmail' => 'validEmail',
'emailTextbox' => 'validTextbox',
);
$validation = new Validation();
if ($validation->validate($_POST, $rules) == TRUE) {
require("_php/database/dbProcessing.php"); //Form Proccessing for database inclusion
}
else {
foreach($validation->emailErrors as $error){
$emailErrors[] = $error;
$_SESSION['$emailErrors'] = $emailErrors;
header('Location:indexmobile.php#emailErrors');
die('ABORT!');
}
}
}
catch (PDOException $e)
{
$error = 'Error adding elements to database: ' . $e->getMessage();
echo "Error: " . $error;
exit();
}
exit();
}
?>
The validation.php where I do my validation has this:
<?php
class Validation {
public $errors = array();
public function validate($data, $rules) {
$valid = TRUE;
foreach ($rules as $fieldname => $rule) {
$callbacks = explode('|', $rule);
foreach ($callbacks as $callback) {
$value = isset($data[$fieldname]) ? $data[$fieldname] : NULL;
if ($this->$callback($value, $fieldname) == FALSE) $valid = FALSE;
}
}
return $valid;
}
public function validEmail($value, $fieldname) {
$valid = !empty($value);
if ($valid == FALSE) {
$this->emailErrors[] = "The $fieldname is required";
return $valid;
} else {
$valid = filter_var($value, FILTER_VALIDATE_EMAIL);
if ($valid == FALSE) $this->emailErrors[] = "The $fieldname needs to be a valid email";
return $valid;
}
}
public function validTextbox($value, $fieldname) {
$valid = !empty($value);
if ($valid == FALSE) {
$this->emailErrors[] = "The $fieldname is required";
return $valid;
} else {
$whitelist = '/^[a-zA-Z0-9 ,\.\+\\n;:!_\-#]+$/';
$textarea = strip_tags($value);
$textarea = mysql_real_escape_string($textarea);
$valid = preg_match($whitelist, $textarea);
if ($valid == FALSE) $this->errors[] = "The $fieldname contains invalid characters";
return $valid;
}
}
}
Upon using this, Im have issues with the redirect (I think). It seems further that Im having errors in validation. My questions are thus:
Am I doing the header redirect correctly? I've read that " header() must be called before any actual output is sent,.." So is this the reason why this redirect is incorrect? how to make a redirect if i need to show/send something to the redirected page?
function validTextbox always ends up an error that the field is empty. Why so?
Is my entire process of form validation a good way of validating form fields (which i learned from watching an online tutorial)? What is a better way?
Is there something wrong with error reporting in this case?
Thank you for those who replies. I am new to PHP and trying my best to learn the language.
1 - There are several ways to pass on a message to the page you are redirecting to. One is through $_GET like this
$message="Some message for the next page.";
$message=urlencode($message);
header("Location:page.php?message=".$message);
then on page.php
if(!empty($_GET['message']))
{
$_GET['message'];
}
similarly you can also use the session (less secure)
$_SESSION['message']='some other message';
then on page.php
if (!empty($_SESSION['message']))
{
echo $_SESSION['message'];
unset($_SESSION['message']);
}
2 - I would have to see what you are passing to your validate function. You should do a var_dump of $_POST and add that to your question.
3 - It depends on your criteria. If you are just checking for emptiness its overkill. I don't know what text you need / consider valid, but a regex is a reasonable way of enforcing validation.
4 - See #2.
I want to check the username availability while users register. I am working on the front end. The backend code was given to me.
These are the php code in signup.php
if (isset($_GET['chkusername']))
JSON_username_avail($_GET['chkusername']);
function JSON_username_avail($username) {
$ret = array();
print json_encode(validate_username($username, $ret));
die();
}
function validate_username($username, & $retval_arr) {
if ($username == NULL)
$retval_arr['E_UserName'] = "NULL_USERNAME";
else if (!username_validation($username))
$retval_arr['E_UserName'] = "INVALID_USERNAME";
else if (!data_not_exists("user", "username", $username, TRUE))
$retval_arr['E_UserName'] = "USERNAME_EXISTS";
return $retval_arr;
}
function username_validation($user) {
$username = str_split($user);
foreach($username as $i) {
$i = ord($i);
if ($i >= 48 and $i <= 57)
continue;
if ($i >= 65 and $i <= 90)
continue;
if ($i >= 97 and $i <= 122)
continue;
return FALSE;
}
return TRUE;
}
function data_not_exists($table, $field, $data, $CSense = FALSE) {
$conn = connect_db();
$data = filter_var($data, FILTER_SANITIZE_STRING);
if ($CSense == TRUE)
$sql = "SELECT * FROM ".$table.
" WHERE ".$field.
"='".$data.
"'";
else
$sql = "SELECT * FROM ".$table.
" WHERE upper(".$field.
")='".$data.
"'";
$result = mysqli_query($conn, $sql);
switch ($result - > num_rows) {
case 0:
return TRUE;
break;
case 1:
return FALSE;
break;
default:
die("500 Internal Server Error: 122");
} //switch
}
Now I dont know that much of php. I created a javascript function to send the username to the signup.php page for validation.
Here is my function
function submit_form() {
var u = document.getElementById("username").value;
$.post("signup.php", {
"chkusername": u
},
function (data) {
var x = data; //here i dont know how to get the return string. Whether it is NULL_USERNAME OR INVALID_USERNAME OR USERNAME_EXISTS.
}, "json");
}
here i am getting the value of x as [object Object].
But i need to store the return message in variable x. I want to know whether it is NULL_USERNAME OR INVALID_USERNAME OR USERNAME_EXISTS. Kindly help me with that.
The username is POSTed but in the PHP you try to access it with $_GET, change to:
if (isset($_POST['chkusername']))
JSON_username_avail($_POST['chkusername']);
Also your validation logic doesn't look right, what if the username is valid and available? I would add an else clause and set a success variable:
function JSON_username_avail($username) {
$ret = array();
print json_encode(validate_username($username));
die();
}
function validate_username($username) {
$retval_arr = array('success' => false, 'message' => '');
if ($username == NULL)
$retval_arr['message'] = "NULL_USERNAME";
else if (!username_validation($username))
$retval_arr['message'] = "INVALID_USERNAME";
else if (!data_not_exists("user", "username", $username, TRUE))
$retval_arr['msessage'] = "USERNAME_EXISTS";
else
$retval_arr['success'] = true;
return $retval_arr;
}
and the ajax:
if(!data.success){
console.log(data.message);
} else {
// valid and available
}
Try,
function(data){
var x = data.E_UserName;
}, "json");
also change your request to GET as per MrCode's answer
function submit_form() {
var u = document.getElementById("username").value;
$.get("signup.php", {
"chkusername": u
},
function (data) {
var x = data.E_UserName
}, "json");
}