-2

I have recently adapted some code from http://megarush.net/forgot-password-php/ to help me create a forgot password function. I have changed it to mysqli and added some bcrypt features when updating the password. In short form, the user types in their email address, get sent a link, and this link allows them to update their password but it also gets hashed again. My problem is... I can get the email to go to the user, but when the link is clicked it keeps saying "Invalid link or Password already changed" even when trying new email addresses. Any ideas where I've gone wrong? Appreciate the help guys!

I have a token table with email, token, and used.

forgot.php

<?php require 'header.php'; 

if (!isset($_GET['email'])) {
  echo '<form action="forgot.php">
        Enter Your Email Id:
        <input type="text" name="email" />
        <input type="submit" value="Reset My Password" />
        </form>';
  exit();
}

$email = $_GET['email'];
$sql = "SELECT email FROM user WHERE email='$email'";
$query = $mysqli_conn->query($sql);
if ($query->num_rows == 0) {
  echo "Email id is not registered";
  die();
}

$token = getRandomString(10);
$sql = "INSERT INTO `tokens` (`token`, `email`) VALUES ('{$token}','{$email}')";
$query = $mysqli_conn->query($sql);

function getRandomString($length) {
  $validCharacters = "ABCDEFGHIJKLMNPQRSTUXYVWZ123456789";
  $validCharNumber = strlen($validCharacters);
  $result = "";
  for ($i = 0; $i < $length; $i++) {
    $index = mt_rand(0, $validCharNumber - 1);
    $result.= $validCharacters[$index];
  }
  return $result;
}

function mailresetlink($to, $token) {
  $subject = "Forgot Password";
  $uri = 'http://' . $_SERVER['HTTP_HOST'];
  $message = '
    <html>
    <head>
    <title>Forgot Password</title>
    </head>
    <body>
    <p>Click on the given link to reset your password <a     
    href="' . $uri . '/project/reset.php?token=' . $token . '">Reset Password</a></p>

    </body>
    </html>
    ';
  $headers = "MIME-Version: 1.0" . "\r\n";
  $headers.= "Content-type:text/html;charset=iso-8859-1" . "\r\n";
  $headers.= 'From: Admin<webmaster@example.com>' . "\r\n";
  $headers.= 'Cc: Admin@example.com' . "\r\n";
  if (mail($to, $subject, $message, $headers)) {
    echo "We have sent the password reset link to your  email id <b>" . $to . "      
    </b>";
  }
}

if (isset($_GET['email'])) mailresetlink($email, $token);
?>

reset.php

<?php require 'header.php';

$token = $_GET['token'];

if (!isset($_POST['password'])) {
  $sql = "SELECT email FROM tokens WHERE token='" . $token . "' and used=0";
  $query = $mysqli_conn->query($sql);
  while ($row = mysqli_fetch_array($query)) {
    $email = $row['email'];
  }

  if ($email != '') {
    $_SESSION['email'] = $email;
  }
  else die("Invalid link or Password already changed");
}

$password = $_POST['password'];
$email = $_SESSION['email'];

if (!isset($password)) {
  echo '<form method="post">
        enter your new password:<input type="password" name="password" />
        <input type="submit" value="Change Password">
        </form>';
}

if (isset($_POST['password']) && isset($_SESSION['email'])) {
  $password = password_hash($password, PASSWORD_DEFAULT);
  $sql = "UPDATE user SET password= '$password' where email='$email'";
  $query = mysqli_query($sql);
  if ($query) mysqli_query("UPDATE tokens SET used=1 WHERE token='$token'");
  echo "Your password is changed successfully";
  if (!$query) echo "An error occurred";
}

?>  

UPDATE: The invalid error is now fixed and the form displays, but now it just appears saying 'an error occurred'. Added sql errors in to pick up any errors, it seems to be fine until it gets to updating the password as I have echoed variables and

   if (isset($_POST['password']) && isset($_SESSION['email'])) {}

comes back working

Jess
  • 63
  • 2
  • 9
  • mail function don't work on localhost, if you're trying it on localhost – rummykhan Apr 06 '16 at 11:55
  • @rummykhan the email sends fine to the user! it's just the link that when clicked, comes back with the error 'invalid link or password changed'. My website is hosted on a server – Jess Apr 06 '16 at 11:57
  • Oh sorry my bad let me see that again – rummykhan Apr 06 '16 at 11:58
  • No problem. Any ideas? @rummykhan – Jess Apr 06 '16 at 12:04
  • Where is your database connection in `reset.php`? – Apb Apr 06 '16 at 12:06
  • Sorry, it is further up the page in front of a lot of html so I forgot to put it in. It is in a header file at the top of both files @Apb – Jess Apr 06 '16 at 12:07
  • Have you checked whether `$email` variable is set or not? @Jess – Apb Apr 06 '16 at 12:08
  • Is this not shown in my forgot.php on line 2? Do I need more? I'm quite a beginner to PHP so apologies if I don't make sense @Apb – Jess Apr 06 '16 at 12:12
  • 1
    `if ($email != '')` -> `if ($email !== '')` – Chay22 Apr 06 '16 at 12:17
  • First, did you start the session? Then, what is the password column's length? – Funk Forty Niner Apr 06 '16 at 12:23
  • The password length is 255 as my bcrypt has worked before when registering. And yes, it's in my header file. The error has gone but it now won't update the password ans shows a blank box @Fred-ii- – Jess Apr 06 '16 at 12:25
  • @Jess See my answer below. Found it. – Funk Forty Niner Apr 06 '16 at 12:27
  • Have you checked your error logs? You're making an assumption the queries are working. Add error checking, such as `or die(mysqli_error($mysqli_conn))` to your queries. Or you can find the issues in your current error logs. – Jay Blanchard Apr 06 '16 at 13:04
  • @JayBlanchard done this on every query and it still doesn't show anything. Now my mail function will not send, however it did before. As soon as the email is filled in the form and submitted, I can't a blank notification (i.e. where the 'email is sent' notification is meant to be) – Jess Apr 06 '16 at 13:23

1 Answers1

1

The reason why your queries are not firing, is that you did not pass your db connection to all mysqli_query(), being in this block of code:

if (isset($_POST['password']) && isset($_SESSION['email'])) {
  $password = password_hash($password, PASSWORD_DEFAULT);
  $sql = "UPDATE user SET password= '$password' where email='$email'";
  $query = mysqli_query($sql);
  if ($query) mysqli_query("UPDATE tokens SET used=1 WHERE token='$token'");
  echo "Your password is changed successfully";
  if (!$query) echo "An error occurred";
}

Just as you did for $query = $mysqli_conn->query($sql);.

Remember to check for errors also.

This if (!$query) echo "An error occurred"; does not help you here.

Add error reporting to the top of your file(s) right after your opening PHP tag for example
<?php error_reporting(E_ALL); ini_set('display_errors', 1); then the rest of your code, to see if it yields anything, as well as or die(mysqli_error($mysqli_conn)) to mysqli_query().


Your present code is open to SQL injection. Use prepared statements, or PDO with prepared statements.


Footnotes:

You should use a conditional empty() rather than if ($email != ''), it's better.

Another thing: When using UPDATE, it's best to use mysqli_affected_rows() for truthness, as you could get a false positive.

Here is an example using mysqli_affected_rows() and I changed isset() to !empty() for the password POST array:

if (!empty($_POST['password']) && isset($_SESSION['email'])) {

  $password = password_hash($password, PASSWORD_DEFAULT);
  $sql = "UPDATE user SET password= '$password' where email='$email'";
  $query = mysqli_query($mysqli_conn, $sql) or die(mysqli_error($mysqli_conn));

  if (mysqli_affected_rows($mysqli_conn)){
    mysqli_query($mysqli_conn, "UPDATE tokens SET used=1 WHERE token='$token'");

      echo "Your password is changed successfully";
  }
  else {
    echo "An error occured: " . mysqli_error($mysqli_conn);
    }


}

Edit:

Change this whole block:

$token = $_GET['token'];

if (!isset($_POST['password'])) {
  $sql = "SELECT email FROM tokens WHERE token='" . $token . "' and used=0";
  $query = $mysqli_conn->query($sql);
  while ($row = mysqli_fetch_array($query)) {
    $email = $row['email'];
  }

  if ($email != '') {
    $_SESSION['email'] = $email;
  }
  else die("Invalid link or Password already changed");
}

while getting rid of this code block (for now):

if ($email != '') {
$_SESSION['email'] = $email;
}
else die("Invalid link or Password already changed");

The first code block above to be replaced with and checking if the row exists with mysqli_num_rows():

if (isset($_GET['token'])) {

$token = $_GET['token'];

$sql = "SELECT email FROM tokens WHERE token='" . $token . "' and used=0";
$query = $mysqli_conn->query($sql)  or die(mysqli_error($mysqli_conn));

    if(mysqli_num_rows($query) > 0){

        while ($row = mysqli_fetch_array($query)) {

            $email = $row['email'];
            $_SESSION['email'] = $email;

        }

    }

}
Community
  • 1
  • 1
Funk Forty Niner
  • 74,450
  • 15
  • 68
  • 141
  • Thanks for your help. I've tried your suggestions about adding my connection variable as stated, but now the mail function does not send an email to the user and a blank text box appears. I have also tried the error reporting and nothing shows? – Jess Apr 06 '16 at 12:41
  • @Jess You're welcome. I can't understand what the mail function would have anything to do with this. – Funk Forty Niner Apr 06 '16 at 12:42
  • Would I be able to send you a link of my website? I can't understand either as I have not changed code from forgot.php – Jess Apr 06 '16 at 12:50
  • 1
    @Jess If it's going to the `else die("Invalid link or Password already changed");` then the `if()` failed and you need to find out why. Do a `var_dump();` on `$email = $row['email'];` and for `$_SESSION['email']`. If those are empty, then something failed. – Funk Forty Niner Apr 06 '16 at 13:00
  • @Jess Another thing is that you may be overwriting the `$email` variable with `$email = $row['email'];` and then further below that, you're doing `$email = $_SESSION['email'];`. – Funk Forty Niner Apr 06 '16 at 13:03
  • I have added the var_dump, but the forgot page isn't even getting to the reset one anymore and I hadn't changed anything. As soon as the email is entered and form submitted, a blank page appears. I've added $query = mysqli_query($mysqli_conn, $sql) or die(mysqli_error($mysqli_conn)); to every query in the forgot page aswell. I can't understand why it is suddenly doing this. Can a server block this? – Jess Apr 06 '16 at 13:16
  • ^by blank page, I meant section with no text. – Jess Apr 06 '16 at 13:20
  • I've used my old code posted in my question to check, and it still doesn't work! I really don't understand how – Jess Apr 06 '16 at 13:27
  • @Jess Reload my answer and look under **Edit:**. You need to check if that row exists with `mysqli_num_rows()`. If that doesn't work, then I suggest you go over that tutorial again and use their code exactly as shown, but by changing all of their `mysql_` to `mysqli_` and passing connection to functions that require it. If my memory serves me right, I've used that piece of code before myself and was successful. Another thing I suggest you do is, to echo variables throughout your code and to see at which point(s) it could be breaking, and `var_dump()`'ing also. I also have more in there. – Funk Forty Niner Apr 06 '16 at 13:36
  • Just tried it all from scratch, still nothing :( Its like something is blocking it from sending now-even though no code has changed. Is it possible for a server to block the emails if they think its suspicious? I'm using one.com – Jess Apr 06 '16 at 13:50
  • @Jess Possibly. I built something similar in the past where certain servers would mark it as being suspicious and took a while till I would get an email back. It was delivered alright, but marked as suspicious. Try and setup a plain contact form / simple `mail()` to yourself. If it gets there in a flash, then your forgot password script could be setting something in their system or the mail service at the other end. – Funk Forty Niner Apr 06 '16 at 13:53
  • I already have a contact form set up that sends an email to me (place advert form), just tried that and it just comes back with the form filled in but doesn't submit. Do you think they've blocked me then? – Jess Apr 06 '16 at 14:02
  • @Jess I couldn't say Jess. Try sending them a trouble ticket at your host and see if there's anything they can see that they've spotted. – Funk Forty Niner Apr 06 '16 at 14:03
  • Got in touch with one.com and they said it had been blocked!! All fixed in terms of sending mail, only error I get now is 'an error occured:' but no mysql error appears next to it-this is when a new password is entered. However, this is when taking out the 'invalid error' as you said before – Jess Apr 06 '16 at 15:37
  • @Jess well at least you've gotten that resolved. So, the db still doesn't get updated. I don't know what else I can do here besides what I've already given you. – Funk Forty Niner Apr 06 '16 at 15:39
  • Okay no worries, thanks for everything! Maybe it's because I took out the invalid error message? – Jess Apr 06 '16 at 15:43
  • @Jess You're welcome Jess. Hard to say. Keep at it and as I said earlier, echo variables throughout your code to see what goes through or not. You'll find that pesky bug. *Cheers* – Funk Forty Niner Apr 06 '16 at 15:46