I'm having a problem getting users accounts to verify. I get it to insert data and then send out a confirmation link but when it's clicked in the email it doesn't update the 'active' row from 0 to 1. I'm been messing with this all night and it's probably something simple but either way if anyone can help I'd greatly appreciate it.
Also if anyone could provide any tips on making this injection proof I'd also be very happy. Thanks!
<?php
require ('classes/class.db.php');
require ('classes/class.pass.php');
try {
if(isset($_GET['email']) && !empty($_GET['email']) AND isset($_GET['email_hash']) && !empty($_GET['email_hash'])){
// Verify data
$search = "SELECT email, email_hash, active FROM users WHERE email='".$email."' AND hash='".$email_hash."' AND active='0'";
$match = $db->num_rows( $query );
if($match > 0){
$stmt = $db->prepare('UPDATE users (active) VALUES (:active) WHERE active = 0');
$status = $stmt->execute(array(
':active' => 1));
if( $status )
{
echo '<p>Your account has been activated, you can now login</p>';
}
}
}else{
echo '<p>Your account is already activated</p>';
}
} catch (PDOException $e) {
die($e->getMessage());
}
?>
UPDATE #1
Tried what Akam suggested but still am getting some errors. Here is what I have for my statement.
$stmt = $db->prepare("UPDATE users SET active ='1' where active = '0' and email=:email AND email_hash=:email_hash");
$status = $stmt->execute(array(
':email' => $_GET['email'],
':email_hash' => $_GET['email_hash']
));
UPDATE #2
Seems like the problem is $_GET['email_hash'] which can't be echoed or stored in a variable. It won't take the random hash string from the signup.php page and carry it over to the verify.php page, but the email address carries over perfectly fine. I'm a bit confused and would love for someone to clarify this for me. Thanks.
UPDATE #3
Problem was as simple as turning $_GET['email_hash'] to $_GET['hash'] . Thanks again!
You can also do all by one query, no need to first query if you write same conditions in the update:
"UPDATE users SET active ='1' where active = '0' and email=:email AND hash=:email"
Your UPDATE query contains a syntax error. Try changing the query to
$stmt = $db->prepare('UPDATE users SET active=:active WHERE active=0 AND email=:email AND email_hash=:email_hash');
And then include $email and $email_hash in your associative array during statement execution.
Just as a side note, calling isset($var) && !empty($var) is roughly equivalent to writing !empty($var). As per the documentation, because
empty($var)
is congruent to
!isset($var) || $var == false,
it follows that !empty($var) is congruent to isset($var) && $var != false.
Related
I have a php script who ask my database with PDO to verify if some values sent exists. If they exists, the database respond with the id of this line's value.
I tested the query on mysql and it works but the value received is false.
This code is only for personal use.
There is the code :
<?php
include("../template/pdo.php");
$query = $pdo->prepare("SELECT id_utilisateur FROM utilisateur
WHERE `mail` IN ( ':mail' )
AND `mdp` IN ( ':mdp' )");
$query->bindParam(':mail', $_GET['identifiant'], PDO::PARAM_STR);
$query->bindParam(':mdp', $_GET['mdp'], PDO::PARAM_STR);
$success = $query->execute();
if($success)
{
$result = $query->fetch();
var_dump($result); //bool(false) actually
if($result == false){
$message = "Try again.";
}
else{
$message = "Congratulation !";
}
}
I tested everything I know :
$_GET is a print/paste from my database table to my url and i have print him
Printed/pasted on phpMyAdmin the query from PDOStatement::debugDumpParams() with my $_GET values
pdo.php work and used on other scripts
No log in my logs files.
Someone can help me ?
Thanks !
If you are testing against a single value use =, not IN.
If you have a list of values, several changes are needed.
The bind code will add quotes, you already have quotes. Remove your quotes.
I am trying to have user accounts that can be enabled or disabled.
I have a active field in my table that is set to either yes or no.
This is my code for the login page.
<?php
/* User login process, checks if user exists and password is correct */
require_once 'includes/db.php';
// Escape email to protect against SQL injections
$email = $mysqli->escape_string($_POST['email']);
$result = $mysqli->query("SELECT * FROM dxd_membership WHERE email='$email'");
if ( $result->num_rows == 0 ){ // User doesn't exist
$_SESSION['message'] = "User with that email doesn't exist!";
header("location: error.php");
}
else { // User exists
$user = $result->fetch_assoc();
$active = $mysqli->query("SELECT * FROM dxd_membership WHERE email = '$email' AND active = 'YES'");
if ($active == '1')
{
if ( password_verify($_POST['password'], $user['password']) ) {
$userid = $_SESSION['userid'];
$_SESSION['email'] = $user['email'];
$_SESSION['firstname'] = $user['firstname'];
$_SESSION['lastname'] = $user['lastname'];
$_SESSION['username'] = $user['username'];
$_SESSION['paynum'] = $user['paynum'];
$_SESSION['empnum'] = $user['empnum'];
$_SESSION['phone'] = $user['phone'];
$_SESSION['active'] = $user['active'];
$_SESSION['lastlogin'] = $user['lastlogin'];
$_SESSION['signup'] = $user['signup'];
$_SESSION['lastupdate'] = $user['lastupdate'];
// This is how we'll know the user is logged in
$_SESSION['logged_in'] = true;
$update = $mysqli->query("UPDATE dxd_membership SET lastlogin=NOW() WHERE email = '$email'");
header("location: welcome.php");
}
else {
$_SESSION['message'] = "You have entered wrong password please try again!";
header("location: error.php");
}
}
else {
header("location: disabled.php");
}
}
?>
I am sure it is a silly error i have here but it will not check the active field and then either let the user login to the welcome.php page if active is yes or send them to the disabled.php page if their account active is set to no (disabled).
Can anyone help me with correcting the code so that it will work.
Thanks
Look, I see several issues in your code. The first is the double query for the same data. You can simplify this whole thing to one query.
Another (and more important) is the fact that you're just appending data to the SQL query, where the whole objective of MySQLi is to avoid injections by binding params. So a -more- correct way to do it would be this one:
EDIT: escape_string avoids this. I completely ignored it.
<?php
/* User login process, checks if user exists and password is correct */
require_once 'includes/db.php';
// Escape email to protect against SQL injections
$email = $mysqli->escape_string($_POST['email']);
$result = $mysqli->query("SELECT * FROM dxd_membership WHERE email = '{$email}'");
if ( $result->num_rows == 0 ){ // User doesn't exist
$_SESSION['message'] = "User with that email doesn't exist!";
header("Location: error.php");
exit; // Add an "exit" here, because if you add something else, it will run too (even if you asked to redirect... basically is the browser the one that chooses if it follows the redirect or not, but your script still goes on).
}
else { // User exists
$user = $result->fetch_assoc();
// There's no point in filtering using another MySQL query, since YOU ALREADY HAVE THIS DATA. Just use PHP to read it and act appropiately.
// Doing another query is just WASTING resources for no useful purpose.
//$active = $mysqli->query("SELECT * FROM dxd_membership WHERE email = '$email' AND active = 'YES'");
if ( $user['active'] == 'YES' ) {
// Your processing here, you get the idea
}
}
?>
Of course, the best alternative is to use a MySQLi statement and use bind_param/execute. This example is only to follow your style of using MySQLi.
It's pretty obvious
$active = $mysqli->query("SELECT * FROM dxd_membership WHERE email = '$email' AND active = 'YES'");
if ($active == '1') //<-- see it
{
if ( password_verify($_POST['password'], $user['password']) )
Try this
if ($active->num_rows == 1 ) //or != 0 This is false or a result set.
Even if you did have the value of their active filed in there ( you have select * ) you would still be checking string '1' against string 'YES'
Please note I haven't used mysqli in about 4 years, as I use PDO. So that might not be the entire problem, but just seemed wrong..
In fact that second query is not needed as you already have the data you seek, so you can change it.
Now if you are sure active will always be YES for them being active, the $user already contains this data, so why not use it like this, and save the query.
$email = $mysqli->escape_string($_POST['email']);
$result = $mysqli->query("SELECT * FROM dxd_membership WHERE email='$email'");
if ( $result->num_rows == 0 ){ // User doesn't exist
$_SESSION['message'] = "User with that email doesn't exist!";
header("location: error.php");
}else { // User exists
$user = $result->fetch_assoc();
/* comment these next 2 lines out when not debugging */
echo "<pre>"; //whitespace formating
var_export( $user );
if ($user['active'] == 'YES'){
// .....
}
}
One thing I feel compelled to mention is that you should look into prepared statements. You can find information on that here
http://php.net/manual/en/mysqli.quickstart.prepared-statements.php
Whenever you concatenate in a SQL query you should be using a prepared statement instead, as it opens you application to SQL injection attacks. Now that I look closer you are using escape_string while this is good, the preferred way is prepared statements. This is because with a prepared statement, the variables are entirely separate from the query commands and so the DB knows not to execute anything in them. Even with escaping there could be edge cases that may be an issue, I don't know of any per-say, but something like using a Hexadecimal version of a quote are things I have seen in examples, or weird character strings that the DB would see as a quote.
Here is mysql_* code:
Activation mail and hash check
PDO:
Do anyone sees the solution ?
if (isset($_GET['email']) && !empty($_GET['email']) AND isset($_GET['hash']) && !empty($_GET['hash'])){
// Verify data
$search = $db->prepare("SELECT email, hash, active FROM users WHERE email=:email AND hash=:hash AND active=0");
$search->bindParam(':email', $_POST['email'], PDO::PARAM_STR);
$search->bindParam(':hash', $_POST['hash'], PDO::PARAM_STR);
$search->execute();
//$match = $search->fetch(PDO::FETCH_ASSOC);
$match = $search->rowCount();
There is a problem in this part of condition
if($match > 0){
// We have a match, activate the account
$db->prepare("UPDATE users SET active= 1 WHERE email=:email AND hash=:hash AND active=0");
$db->bindParam(':email', $_POST['email'], PDO::PARAM_STR);
$db->bindParam(':hash', $_POST['hash'], PDO::PARAM_STR);
$db->execute();
echo '<div class="statusmsg">Your account has been activated, you can now login</div>';
}else{
// No match -> invalid url or account has already been activated.
echo '<div class="statusmsg">The url is either invalid or you already have activated your account.</div>';
}
}else{
// Invalid approach
echo '<div class="statusmsg">Invalid approach, please use the link that has been send to your email.</div>';
}
The condition finishes the code here:
The url is either invalid or you already have activated your account.
But it should finish the code here:
Your account has been activated, you can now login.
You check $_GET but later using $_POST.
You have few mistakes in your code,
First the $_POST that you are using here does not exists.
$search->bindParam(':email', $_POST['email'], PDO::PARAM_STR);
$search->bindParam(':hash', $_POST['hash'], PDO::PARAM_STR);
You should be using $_GET
rowCount returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement executed by the corresponding PDOStatement object.
Thefore rowcount, is not reliable on select,
below is working code that you can use to achieve what you looking for.
if (isset($_GET['email']) && !empty($_GET['email']) AND isset($_GET['hash']) && !empty($_GET['hash'])){
$email = $_GET['email'];
$hash = $_GET['hash'];
$search = $db->prepare("SELECT email, hash, active FROM users WHERE email=:email AND hash=:hash AND active=0");
$search->bindParam(':email', $email, PDO::PARAM_STR);
$search->bindParam(':hash', $hash, PDO::PARAM_STR);
$search->execute();
$match = $search->fetchall(PDO::FETCH_ASSOC);
if(count($match) > 0){
//then match exists activate the profile
$stmt = $db->prepare("UPDATE users SET active= 1 WHERE email= ? AND hash= ? AND active=0")->execute(array($email,$hash));
if(!$stmt){
print_r($db->errorInfo());
}else{
//account activated
echo '<div class="statusmsg">Your account has been activated, you can now login</div>';
}
}else{
// No match -> invalid url or account has already been activated.
echo '<div class="statusmsg">The url is either invalid or you already have activated your account.</div>';
}
}else{
// Invalid approach
echo '<div class="statusmsg">Invalid approach, please use the link that has been send to your email.</div>';
}
The problem in the flow is bound to this line of code:
$match = $search->rowCount();
You're executing a SELECT query and the rowCount is only available on INSERT, DELETE or UPDATE queries.
Instead, to find out if there is at leas match, you can use fetch() to get the "first" row, and if it exists, there was a match:
...
$match = $search->fetch();
if ($match) {
...
As another alternative approach to prevent that line of code you can in theory as well remove the first SELECT query in full and execute the second UPDATE query first and then check with the rowCount whether or not the user was updated.
There is more left open to comment with your code, like the logic you validate the hashes as they have no time-limit and the generation of the hash is even unknown but often critical.
Reference:
Row count with PDO
Hello Stackoverflow community,
I'm starting to work with PDO soon. I have a trivial question that I do not know how to solve. So, let me know if you guys can help me.
I have a form that aims to update data from a user account in a member space. This form has three fields "Last Name", "Name" and "E-mail".
I don't want that the user register and existing e-mail. However, if the user does not want to update their email and only wants to change the "Last Name" and "Name" fields, the PHP code must allow updating the records in the database.
I created a function to process the form. It is able to prevent the insertion of duplicate records, but it has a problem. If the user does not want to update their email, the function returns that there is already an equal email in the database. In fact, email already exists, so I would like to know how to implement this exception in my code to allow it to update the records when the user does not want to change their e-mail?
Below the function:
function update_admin_profile() {
session_start();
if (isset($_SESSION['id']) AND isset($_SESSION['login'])) {
// get the session variables for another propouse.
$id = $_SESSION['id'];
$pseudo = $_SESSION['login'];
// p - It's the URL parameter. anti_sql_injection is a function to check the parameter.
$p = anti_sql_injection($_GET['p']);
if (isset($_POST['last_name']) AND isset($_POST['name']) AND isset($_POST['email'])) {
$bdd = connexion_bdd();
$query = $bdd->prepare('SELECT * FROM tbl__administrators WHERE email = :email');
$query->execute(array('email' => htmlspecialchars($_POST['email'])));
$count=$query->rowCount();
if ($count == 0 ) {
$update = $bdd->prepare('UPDATE tbl__administrators SET last_name = :last_name, name = :name, email = :email WHERE id = ' . $p);
$update->execute(array(
'last_name' => htmlspecialchars($_POST['last_name']),
'name' => htmlspecialchars($_POST['name']),
'email' => htmlspecialchars($_POST['email'])
));
//The profile was updated.
header('Location: notify.php?m=49');
} else {
//The e-mail already exists!
header('Location: notify.php?m=48');
}
} else {
//Please fill in all fields
header('Location: notify.php?m=41');
}
} else {
//You session is expired. You will be disconnected now. Please, perform the login again and repeat this operation.
header('Location: notify.php?m=7');
}
}
Note: It's function works if I change the e-mail.
Thank you so much for your help.
Have nice day.
If the user does not want to update their email, the function returns that there is already an equal email in the database.
It's very simple. Just add another condition to exclude the current user from the query results
$query = $bdd->prepare('SELECT 1 FROM tbl__administrators WHERE email = ? and id != ?');
$query->execute(array($_POST['email'], $id));
OK I have this code to send an email account verification link
$verifyemail = $clean['email'];
$to = $verifyemail;
$subject = 'Virtual Pierz Close | Verify Your Account';
$message = "Thanks for registering with VPC, on clicking the verification link below, your account will be confirmed, you can then go ahead buy Virtual Properties, donating £5 each time to the worthwhile charity.
http://www.cambrianvacation.co.uk/vpc/registered.php?
email='$verifyemail'&hash='$hash1' ";
$headers = 'From:noreply#cambrianvacation.co.uk'; // Set from headers
mail($to, $subject, $message, $headers);
And then I have this code, that is trying to activate the account by setting active = 1 in the database, which will then be part of the access control logic at login, without active = 1, there is no login, amongst other protection
if(isset($_GET['email']) && !empty($_GET['email']) AND isset($_GET['hash']) && !empty($_GET['hash'])){
// Verify data
$accountemail = $_GET['email'];
$accounthash = $_GET['hash'];
}
$accountActive = 1;
$notactive = 0;
$username = '';
$password2 = '';
$username = 'xxxxxxx';
$password2 = 'xxxxxxx';
$db1 = new PDO('mysql:host=localhost;dbname=xxxxxxxxxxxxx', $username, $password2, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
$db1->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try{
$search = $db1->prepare("SELECT email, hash, active FROM users WHERE email = :email AND hash= :hash AND active = :active");
$search->bindParam(':email', $accountemail);
$search->bindParam(':hash', $accounthash);
$search->bindParam(':active', $notactive);
$search->execute();
$colcount = $search->columnCount();
}catch(PDOException $e) {
$e->getMessage();
}
print_r($colcount);
if($colcount === 3){
//try{
$update = $db1->prepare("UPDATE users SET active=:active WHERE email=:email AND hash=:hash AND active = :active");
$update->bindParam(':active', $accountActive);
$update->bindParam(':email', $accountemail);
$update->bindParam(':hash', $accounthash);
$update->bindParam(':active', $notactive);
$update->execute();
//}catch(PDOException $e) {
// $e->getMessage();
//}
However I cannot get the active column to update.
I've also thought about using the GET['email'] could be subject to semantic url attacks, however the logic won't activate the account without the matching hash, which is randomly generated with crypt().........
If anyone can see any security holes in the code, please tell me.........
There really is no reason to make two separate queries here. Why not just have one query to update the record based on hash and email and active = 0? If the count of modified rows = 1, then you had a success, else you had a failure. You probably don't care why it failed, as it would be bad from a security perspective to indicate back to the user why update failed (i.e. bad email, bad hash, already active user, etc.).
That being said, your problem actually lies in the fact that your update uses ? style bindings, while you are using bindParam() with :param style bindings. This won't work since those values are not present in the prepared statement.
So just use this one single query:
UPDATE users SET active = 1 WHERE email = :email AND hash = :hash AND active = 0
Obviously if you think you are going to change the value for active/non-active then feel free to use a parameter for those as well, but my guess is you would want to treat that as a boolean-style tinyint field with only allowable values of 0 and 1, so there is really no point in having the parametrization there.
What you could do, is not include "email" at all.
You could try to gen the url by doing this:
$secret = "1032940fdjsjdkf##$!##%djsfisd";
$hash = md5($email.$secret);
$url = "http://www.cambrianvacation.co.uk/vpc/registered.php?hash=".$hash;
In the update query, you are using "?" for parameters, but then you try to set them as named with bindParam(). You should use
$update->execute(array($accountActive, $accountemail, $accounthash, $notactive));
Or modify the update query this way:
$update = $db1->prepare("UPDATE users SET active=:active WHERE email=:email AND hash=:hash");
Your new parameters are not bound correctly, change:
$update = $db1->prepare("UPDATE users SET active=? WHERE email=? AND hash=? AND active = ?");
To:
$update = $db1->prepare("UPDATE users SET active=:active WHERE email=:email AND hash=:hash");
EDIT - Full Update Code:
$update = $db1->prepare("UPDATE users SET active=:active WHERE email=:email AND hash=:hash");
$update->bindParam(':active', $accountActive);
$update->bindParam(':email', $accountemail);
$update->bindParam(':hash', $accounthash);
$update->execute();