Skip to content
This repository has been archived by the owner on Nov 2, 2020. It is now read-only.

Commit

Permalink
feat(Auth): Add UserRecover Form
Browse files Browse the repository at this point in the history
1. Add UserRecover Form, So that users can recover their password if
they forget.
2. Add main recover function in UserConfirmForm.

BREAKING CHANGE: Database Stucture of `users_confirms` change
  • Loading branch information
Rhilip committed Jun 4, 2019
1 parent 152331b commit ecf68a9
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ Or you can join our chat group on Telegram -- [@ridpt](https://t.me/ridpt)

| Library | Docs |
|:--|:--|
| [Zui](https://github.com/easysoft/zui): an HTML5 front UI framework | http://zui.sexy/ |
| [Zui](https://github.com/easysoft/zui): an HTML5 front UI framework | http://zui.sexy/ ( Chinese Version ) |
| [FortAwesome](https://github.com/FortAwesome/Font-Awesome): The iconic SVG, font, and CSS toolkit | https://fontawesome.com/icons?d=gallery |
| [flag-css](https://github.com/7kfpun/flag-css): CSS for SVG country flags respecting the original ratio. | https://kfpun.com/flag-css/ |
| [zxcvbn](https://github.com/dropbox/zxcvbn): Low-Budget Password Strength Estimation | https://lowe.github.io/tryzxcvbn/ |
Expand Down
29 changes: 26 additions & 3 deletions apps/controllers/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@

namespace apps\controllers;

use apps\models\form\UserConfirmForm;
use apps\models\User;
use apps\models\form\UserLoginForm;
use apps\models\form\UserConfirmForm;
use apps\models\form\UserRecoverForm;
use apps\models\form\UserRegisterForm;

use Rid\Http\Controller;
Expand Down Expand Up @@ -59,13 +60,35 @@ public function actionConfirm()
]);
} else {
$confirm->flush();
return $this->render('auth/confirm_success');
return $this->render('auth/confirm_success', ['action' => $confirm->action]);
}
}

public function actionRecover()
{
// TODO User Recover Action
if (app()->request->isPost()) {
$form = new UserRecoverForm();
$form->setData(app()->request->post());
$success = $form->validate();
if (!$success) {
return $this->render('auth/error', [
'title' => 'Action Failed',
'msg' => $form->getError()
]);
} else {
$flush = $form->flush();
if ($flush === true) {
return $this->render('auth/recover_next_step');
} else {
return $this->render('auth/error', [
'title' => 'Confirm Failed',
'msg' => $flush
]);
}
}
} else {
return $this->render('auth/recover');
}
}

public function actionLogin()
Expand Down
68 changes: 55 additions & 13 deletions apps/models/form/UserConfirmForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,30 @@
namespace apps\models\form;

use apps\components\User\UserInterface;
use Rid\Helpers\StringHelper;
use Rid\Validators\Validator;


class UserConfirmForm extends Validator
{

public $secret;
public $action;

protected static $action_list = ['register', 'recover'];

protected $id;
private $uid;
public $secret;
private $user_status;

public static function inputRules()
{
return [
'secret' => 'Required',
'action' => [
['Required'],
['InList', ['list' => self::$action_list], 'Unknown confirm action.']
],
];
}

Expand All @@ -31,36 +41,68 @@ public static function callbackRules()
return ['validConfirmSecret'];
}

/**
* Verity The confirm secret and action exist in table `users_confirm` or not
*/
protected function validConfirmSecret()
{
$record = app()->pdo->createCommand(
'SELECT `users_confirm`.`id`,`users_confirm`.`uid`,`users`.`status` FROM `users_confirm`
LEFT JOIN `users` ON `users`.`id` = `users_confirm`.`uid`
WHERE `serect` = :uid LIMIT 1;')->bindParams([
'uid' => $this->secret
WHERE `serect` = :serect AND `action` = :action AND used = 0 LIMIT 1;')->bindParams([
'serect' => $this->secret , 'action' => $this->action
])->queryOne();

if ($record == false) { // It means this confirm key is not exist
$this->buildCallbackFailMsg('confirm key', 'This confirm key is not exist');
$this->buildCallbackFailMsg('confirm key', 'This confirm key is not exist'); // FIXME msg
return;
}

if ($record['status'] !== UserInterface::STATUS_PENDING) {
$this->buildCallbackFailMsg('User', 'User Already Confirmed');
return;
}
$this->uid = $record['uid'];
$this->id = $record['id'];
$this->user_status = $record['status'];
}

public function flush()
{
protected function update_confirm_status() {
app()->pdo->createCommand('UPDATE `users_confirm` SET `used` = 1 WHERE id = :id')->bindParams([
'id' => $this->id
])->execute();
}

public function flush_register() {
if ($this->user_status !== UserInterface::STATUS_PENDING) {
return 'user status is not pending , they may already confirmed or banned'; // FIXME msg
}

app()->pdo->createCommand('UPDATE `users` SET `status` = :s WHERE `id` = :uid')->bindParams([
's' => UserInterface::STATUS_CONFIRMED, 'uid' => $this->uid
])->execute();
app()->pdo->createCommand('DELETE FROM `users_confirm` WHERE id = :id')->bindParams([
'id' => $this->id
])->execute();
$this->update_confirm_status();
app()->redis->del('User:content_' . $this->uid);
return true;
}

public function flush_recover() {
if ($this->user_status !== UserInterface::STATUS_CONFIRMED) {
return 'user status is not confirmed , they may still in pending or banned'; // FIXME msg
}

// generate new password
$new_password = StringHelper::getRandomString(10);
app()->pdo->createCommand('UPDATE `users` SET `password` = :new_password WHERE `id` = :uid')->bindParams([
'new_password'=> password_hash($new_password, PASSWORD_DEFAULT), 'uid'=>$this->uid
])->execute();
$this->update_confirm_status();

// TODO Send user email to tell his new password.




return true;
}

public function flush() {
return $this->{'flush_' . $this->action}(); // Magic function call
}
}
73 changes: 73 additions & 0 deletions apps/models/form/UserRecoverForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php
/**
* Created by PhpStorm.
* User: Rhilip
* Date: 2019/6/4
* Time: 14:42
*/

namespace apps\models\form;


use apps\components\User\UserInterface;
use Rid\Helpers\StringHelper;
use Rid\Validators\CaptchaTrait;
use Rid\Validators\Validator;

class UserRecoverForm extends Validator
{
use CaptchaTrait;

public $email;

protected $_action = 'recover';

public static function inputRules()
{
return [
'email' => 'required | email',
];
}

public static function callbackRules()
{
return ['validateCaptcha'];
}

// TODO Add rate limit for user only can recover once in a time interval

/**
* Check email in our database and send recover link to that email
* Notice: if this email is not exist in our database , will also return bool(true) for security reason.
* However, We will not send recover-confirm-link email.
* @return bool|string bool(true) means flush success ,
* any other value (string) performs like error msg
*/
public function flush() {
// Check this email is in our database or not?
$user_info = app()->pdo->createCommand('SELECT `id`,`status` FROM `users` WHERE `email` = :email;')->bindParams([
'email' => $this->email
])->queryOne();
if ($user_info !== false) {
if ($user_info['status'] !== UserInterface::STATUS_CONFIRMED) {
return 'std_user_account_unconfirmed';
}

// Send user email to get comfirm link
$confirm_key = StringHelper::getRandomString(32);
app()->pdo->createCommand('INSERT INTO `users_confirm` (`uid`,`serect`,`action`) VALUES (:uid,:serect,:action)')->bindParams([
'uid' => $user_info['id'], 'serect' => $confirm_key, 'action' => $this->_action
])->execute();
$confirm_url = app()->request->root() . '/auth/confirm?' . http_build_query([
'secret' => $confirm_key,
'action' => $this->_action
]);

$mail_sender = \Rid\Libraries\Mailer::newInstanceByConfig('libraries.[swiftmailer]');
$mail_sender->send([$this->email], 'Please confirm your action to recover your password', "Click this link $confirm_url to confirm."); // FIXME change to email template
}
return true;
}


}
13 changes: 9 additions & 4 deletions apps/models/form/UserRegisterForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class UserRegisterForm extends Validator
private $leechtime;
private $bonus;

protected $_action = 'register';

public function setData($config)
{
parent::setData($config);
Expand Down Expand Up @@ -271,13 +273,16 @@ public function flush()

if ($this->confirm_way == 'email') {
$confirm_key = StringHelper::getRandomString(32);
app()->pdo->createCommand('INSERT INTO `users_confirm` (uid,serect) VALUES (:uid,:serect)')->bindParams([
'uid' => $this->id, 'serect' => $confirm_key
app()->pdo->createCommand('INSERT INTO `users_confirm` (`uid`,`serect`,`action`) VALUES (:uid,:serect,:action)')->bindParams([
'uid' => $this->id, 'serect' => $confirm_key, 'action' => $this->_action
])->execute();
$confirm_url = app()->request->root() . '/auth/confirm?secret=' . urlencode($confirm_key);
$confirm_url = app()->request->root() . '/auth/confirm?' . http_build_query([
'secret' => $confirm_key,
'action' => $this->_action
]);

$mail_sender = \Rid\Libraries\Mailer::newInstanceByConfig('libraries.[swiftmailer]');
$mail_sender->send([$this->email], 'Please confirm your accent', "Click this link $confirm_url to confirm.");
$mail_sender->send([$this->email], 'Please confirm your accent', "Click this link $confirm_url to confirm."); // FIXME change to email template
}

Site::writeLog($log_text, Site::LOG_LEVEL_MOD);
Expand Down
10 changes: 8 additions & 2 deletions apps/views/auth/confirm_success.php
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
<?php
/**
*
* Created by PhpStorm.
* User: Rhilip
* Date: 2019/3/8
* Time: 19:44
*
* @var League\Plates\Template\Template $this
* @var string $action 'register'|'recover'
*/
?>

<?= $this->layout('auth/base') ?>

<?php $this->start('container') ?>
<div class="jumbotron">
<?php if ($action == 'register'): ?>
<h1>Your account is success Confirmed.</h1>
<p>Click <!--suppress HtmlUnknownTarget --><a href="/auth/login">Login Page</a> to login</p>
<?php elseif ($action == 'recover'): ?>
<h1>Your password has been reset and new password has been send to your email, Please find it and login.</h1>
<?php endif; ?>
<p>Click <!--suppress HtmlUnknownTarget --><a href="/auth/login">Login Page</a> to login, Or wait 5 seconds to auto redirect.</p> <!-- TODO wait seconds change -->
</div>
<?php $this->end(); ?>

<?php $this->push('script') ?>
<script>
window.setTimeout(function () {
location.href = '/auth/login';
}, 2000);
}, 5e3);
</script>
<?php $this->end(); ?>
2 changes: 1 addition & 1 deletion apps/views/auth/login.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

<div class="form-group">
<label for="password">Password</label>
<div class="pull-right"><a href="#" class="text-muted">Forget you password?</a></div> <!-- TODO password recover -->
<div class="pull-right"><a href="/auth/recover" class="text-muted">Forget you password?</a></div> <!-- TODO password recover -->
<div class="input-group">
<span class="input-group-addon"><span class="fas fa-key fa-fw"></span></span>
<input type="password" class="form-control" id="password" name="password" required
Expand Down
61 changes: 61 additions & 0 deletions apps/views/auth/recover.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* Created by PhpStorm.
* User: Rhilip
* Date: 2019/6/3
* Time: 23:10
*/
?>

<?= $this->layout('auth/base') ?>

<?php $this->start('container') ?>
<div class="row">
<div class="col-md-7 col-md-offset-3">
<div class="panel">
<div class="panel-heading">Recover lost user name or password</div>
<div class="panel-body">
<fieldset style="margin-bottom: 10px">
<legend class="text-special">1. Enter Your Email</legend>

<form class="auth-form" method="post">
<div class="form-group">
<label for="email">Email</label>
<div class="input-group">
<span class="input-group-addon"><span class="fas fa-envelope fa-fw"></span></span>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="help-block">The Email when your sign account.</div>
</div>

<div class="form-group">
<label for="captcha">Captcha</label>
<div class="row">
<div class="col-md-4">
<div class="input-group">
<span class="input-group-addon"><span class="fas fa-sync-alt fa-fw"></span></span>
<input type="text" class="form-control" id="captcha" name="captcha" maxlength="6"
required autocomplete="off">
</div>
<div class="help-block">Case insensitive.</div>
</div>
<div class="col-md-4">
<?= $this->insert('layout/captcha') ?>
</div>
</div>
</div>

<div class="text-center">
<button type="submit" value="Register" class="btn btn-primary">Recover it!!</button>
</div>
</form>
</fieldset>

<fieldset><legend>2. Follow The Confirm Link to reset your password.</legend></fieldset>
<fieldset><legend>3. Get new generate password from Email and login.</legend></fieldset>
<fieldset><legend>4. Set your own password in user panel.</legend></fieldset>
</div>
</div>
</div>
</div>
<?php $this->end(); ?>
Loading

0 comments on commit ecf68a9

Please sign in to comment.