iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 20
1

流程大致上是這樣:首先使用者忘記密碼了,所以要準備從登入頁連過去的忘記密碼頁 (forget),讓使用者填寫註冊時的 email ,這時候產出包含 token 的 email 連結送到對方信箱,請用戶去信箱收信後點連結回到網站,這是第二頁,系統再驗證這個 Token 確認是本人後提供重置密碼的表單 (reset) 以協助變更新密碼。

  • 共兩頁表單介面
  • 路由要準備接收表單請求
  • 要驗證 Email
  • 要產出 Token
  • 要驗證 Token
  • 要處理 Email 錯誤的問題
  • 要處理 Token 錯誤的問題
  • 要處理 已經變更密碼後二次進入的問題

view/body/forget.php

<div class="container">
	<div class="row">
	    <div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3">
			<form role="form" method="post" action="" autocomplete="off">
				<h2>Reset Password</h2>
				<p><a href='login'>Back to login page</a></p>
				<hr>

				<?php
				//check for any errors
				if(isset($error)){
					foreach($error as $error){
						echo '<p class="bg-danger">'.$error.'</p>';
					}
				}

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

					//check the action
					switch ($_GET['action']) {
						case 'active':
							echo "<h2 class='bg-success'>Your account is now active you may now log in.</h2>";
							break;
						case 'reset':
							echo "<h2 class='bg-success'>Please check your inbox for a reset link.</h2>";
							break;
					}
				}
				?>

				<div class="form-group">
					<input type="email" name="email" id="email" class="form-control input-lg" placeholder="Email" value="" tabindex="1">
				</div>

				<hr>
				<div class="row">
					<div class="col-xs-6 col-md-6"><input type="submit" name="submit" value="Sent Reset Link" class="btn btn-primary btn-block btn-lg" tabindex="2"></div>
				</div>
			</form>
		</div>
	</div>
</div>

view/body/reset.php

<div class="container">
	<div class="row">
	    <div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3">
	    	<?php if(isset($stop)){

	    		echo "<p class='bg-danger'>$stop</p>";

	    	} else { ?>

				<form role="form" method="post" action="" autocomplete="off">
					<h2>Change Password</h2>
					<hr>

					<?php
					//check for any errors
					if(isset($error)){
						foreach($error as $error){
							echo '<p class="bg-danger">'.$error.'</p>';
						}
					}

					//check the action
					if(isset($_GET['action'])) {
						switch ($_GET['action']) {
							case 'active':
								echo "<h2 class='bg-success'>Your account is now active you may now log in.</h2>";
								break;
							case 'reset':
								echo "<h2 class='bg-success'>Please check your inbox for a reset link.</h2>";
								break;
						}
					}
					?>

					<div class="row">
						<div class="col-xs-6 col-sm-6 col-md-6">
							<div class="form-group">
								<input type="password" name="password" id="password" class="form-control input-lg" placeholder="Password" tabindex="1">
							</div>
						</div>
						<div class="col-xs-6 col-sm-6 col-md-6">
							<div class="form-group">
								<input type="password" name="passwordConfirm" id="passwordConfirm" class="form-control input-lg" placeholder="Confirm Password" tabindex="1">
							</div>
						</div>
					</div>
					
					<hr>
					<div class="row">
						<div class="col-xs-6 col-md-6"><input type="submit" name="submit" value="Change Password" class="btn btn-primary btn-block btn-lg" tabindex="3"></div>
					</div>
				</form>

			<?php } ?>
		</div>
	</div>
</div>

route.php

switch($route->getParameter(1)){
    case "reset":
      // 檢查是否有帶 Token 
      $verify_array['resetToken'] = $route->getParameter(2);
      $gump = new GUMP();
      $verify_array = $gump->sanitize($verify_array); 
      $validation_rules_array = array(
        'resetToken'    => 'required'
      );
      $gump->validation_rules($validation_rules_array);
      $filter_rules_array = array(
        'resetToken' => 'trim'
      );
      $gump->filter_rules($filter_rules_array);
      $validated_data = $gump->run($verify_array);
      if($validated_data === false) {
        // 沒有帶 Token 回來,直接踢回 login
        header("Location: login");
        exit;
      } else {
        foreach($validation_rules_array as $key => $val) {
          ${$key} = $verify_array[$key];
        }
        // 有帶 Token 回來的話,確認是否存在
        $table = 'members';
        $condition = 'resetToken = :resetToken';
        $order_by = '1'; 
        $fields = 'resetToken, resetComplete';
        $limit = '1';
        $data_array[':resetToken'] = $resetToken;
        $result = Database::get()->query($table, $condition, $order_by, $fields, $limit, $data_array);
        if(!isset($result[0]['resetToken']) OR empty($result[0]['resetToken'])){
          $stop = 'Invalid token provided, please use the link provided in the reset email.';
        }else if(isset($result[0]['resetComplete']) AND $result[0]['resetComplete'] == 'Yes'){
          $stop = 'Your password has already been changed!';
        }
      }
      
      //if form has been submitted process it
      if(isset($_POST['submit']))
      {
      
        $gump = new GUMP();
        $_POST = $gump->sanitize($_POST); 

        $validation_rules_array = array(
          'password'    => 'required|max_len,20|min_len,3',
          'passwordConfirm' => 'required'
        );
        $gump->validation_rules($validation_rules_array);

        $filter_rules_array = array(
          'password' => 'trim',
          'passwordConfirm' => 'trim'
        );
        $gump->filter_rules($filter_rules_array);

        $validated_data = $gump->run($_POST);

        if($validated_data === false) {
          $error = $gump->get_readable_errors(false);
        } else {
          // validation successful
          foreach($validation_rules_array as $key => $val) {
            ${$key} = $_POST[$key];
          }
          $userVeridator = new UserVeridator();
          $userVeridator->isPasswordMatch($password, $passwordConfirm);
          $error = $userVeridator->getErrorArray();
        } 
        //if no errors have been created carry on
        if(count($error) == 0)
        {
          //hash the password
          $passwordObject = new Password();
          $hashedpassword = $passwordObject->password_hash($password, PASSWORD_BCRYPT);
      
          try {
            $data_array = array();
            $table = 'members';
            $data_array['password'] = $hashedpassword;
            $data_array['resetComplete'] = 'Yes';
            $key = "resetToken";
            $id = $resetToken;
            Database::get()->update($table, $data_array, $key, $id);
            
            //redirect to index page
            header('Location: '.Config::BASE_URL.'login?action=resetAccount');
            exit;
      
          //else catch the exception and show the error.
          } catch(PDOException $e) {
              $error[] = $e->getMessage();
          }
        }
      }
      include('view/header/default.php'); // 載入共用的頁首
      include('view/body/reset.php');     // 載入忘記密碼的頁面
      include('view/footer/default.php'); // 載入共用的頁尾
    break;
    case "forget":
      //if logged in redirect to members page
      if(UserVeridator::isLogin(isset($_SESSION['username'])?$_SESSION['username']:'')){
        header('Location: home'); 
        exit();
      }
      
      //if form has been submitted process it
      if(isset($_POST['submit'])){
        $gump = new GUMP();
        $_POST = $gump->sanitize($_POST); 
        $validation_rules_array = array(
          'email'    => 'required|valid_email'
        );
        $gump->validation_rules($validation_rules_array);

        $filter_rules_array = array(
          'email' => 'trim|sanitize_email'
        );
        $gump->filter_rules($filter_rules_array);
        $validated_data = $gump->run($_POST);

        if($validated_data === false) {
          $error = $gump->get_readable_errors(false);
        } else {
          //email validation
          foreach($validation_rules_array as $key => $val) {
            ${$key} = $_POST[$key];
          }
          $table = 'members';
          $condition = 'email = :email';
          $order_by = '1'; 
          $fields = 'email, memberID'; 
          $limit = '1';
          $data_array[':email'] = $email;
          $result = Database::get()->query($table, $condition, $order_by, $fields, $limit, $data_array);
          if(!isset($result[0]['memberID']) OR empty($result[0]['memberID'])){
            $error[] = 'Email provided is not recognised.';
          }else{
            $memberID = $result[0]['memberID'];
          }
        }

        //if no errors have been created carry on
        if(!isset($error)){

          //create the activation code
          try {
            $data_array = array();
            $data_array['resetComplete'] = 'No';
            $data_array['resetToken'] = md5(rand().time());
            $resetToken = $data_array['resetToken'];
            $key = "memberID";
            $id = $memberID;
            Database::get()->update('members', $data_array, $key, $id);
            
            //send email
            $to = $email;
            $subject = "Password Reset";
            $body = "<p>Someone requested that the password be reset.</p>
            <p>If this was a mistake, just ignore this email and nothing will happen.</p>
            <p>To reset your password, visit the following address: <a href='".Config::BASE_URL."reset/$resetToken'>".Config::BASE_URL."reset/$resetToken</a></p>";

            $mail = new Mail(Config::MAIL_USER_NAME, Config::MAIL_USER_PASSWROD);
            $mail->setFrom(Config::MAIL_FROM, Config::MAIL_FROM_NAME);
            $mail->addAddress($to);
            $mail->subject($subject);
            $mail->body($body);
            $mail->send();

            //redirect to index page
            header('Location: login?action=reset');
            exit;

          //else catch the exception and show the error.
          } catch(PDOException $e) {
              $error[] = $e->getMessage();
          }
        }
      }

      //define page title
      $title = 'Reset Account';
      include('view/header/default.php'); // 載入共用的頁首
      include('view/body/forget.php');    // 載入忘記密碼的頁面
      include('view/footer/default.php'); // 載入共用的頁尾
      break;
}

有問題可以留言問我!


上一篇
Day 19. PHP教程: 實作收信開通帳號機制
下一篇
Day 21. PHP教程: 優化架構與實作 flash session 附原始碼
系列文
寫給朋友的 PHP 從 0 到 100 實戰教程30

尚未有邦友留言

立即登入留言