Merge branch '10-user-confirm-email-when-creating-new-password'
This commit is contained in:
commit
daa8803f5e
10 changed files with 418 additions and 6 deletions
|
|
@ -4,6 +4,7 @@ application:
|
|||
modelsDir : ../app/models/
|
||||
migrationsDir : ../app/migrations/
|
||||
viewsDir : ../app/views/
|
||||
templateDir : ../app/templates/
|
||||
listenersDir : ../app/listeners/
|
||||
libraryDir : ../app/library/
|
||||
formsDir : ../app/forms/
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ database:
|
|||
dbname: httpcb
|
||||
charset: utf8
|
||||
|
||||
#sendgrid
|
||||
#key: value
|
||||
|
||||
# OAuth
|
||||
#oauth:
|
||||
#providers:
|
||||
|
|
|
|||
|
|
@ -134,6 +134,11 @@ $di->setShared('router', function() {
|
|||
'action' => 'settings',
|
||||
))->setName('user-settings');
|
||||
|
||||
$router->add('/act/{link}', array(
|
||||
'controller' => 'user',
|
||||
'action' => 'activationlink',
|
||||
))->setName('activation-link');
|
||||
|
||||
return $router;
|
||||
});
|
||||
|
||||
|
|
@ -176,6 +181,25 @@ $di->setShared('view', function () use ($di, $config) {
|
|||
return $view;
|
||||
});
|
||||
|
||||
$di->set('template', function() use ($config) {
|
||||
$view = new Phalcon\Mvc\View\Simple();
|
||||
$view->setViewsDir($config->application->templateDir);
|
||||
$view->registerEngines([
|
||||
'.volt' => function ($view, $di) use ($config) {
|
||||
$volt = new VoltEngine($view, $di);
|
||||
|
||||
$volt->setOptions(array(
|
||||
'compiledPath' => $config->application->viewCacheDir,
|
||||
'compiledSeparator' => '_',
|
||||
));
|
||||
|
||||
return $volt;
|
||||
},
|
||||
'.phtml' => 'Phalcon\Mvc\View\Engine\Php'
|
||||
]);
|
||||
return $view;
|
||||
});
|
||||
|
||||
$di->setShared('assets', function () {
|
||||
|
||||
$manager = new AssetsManager();
|
||||
|
|
@ -222,6 +246,10 @@ $di->setShared('db', function () use ($di, $config) {
|
|||
return $db;
|
||||
});
|
||||
|
||||
$di->setShared('sendgrid', function() use ($config) {
|
||||
return new SendGrid($config->sendgrid->key);
|
||||
});
|
||||
|
||||
$di->setShared('eventsManager', function () {
|
||||
|
||||
$activityLog = new ActivityLog();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ namespace App\Controller;
|
|||
|
||||
use App\Controller\ControllerBase,
|
||||
App\Form\UserSettings as UserSettingsForm,
|
||||
App\Model\Data\ActivityLog;
|
||||
App\Model\Data\ActivityLog,
|
||||
App\Model\Data\PasswordLink,
|
||||
SendGrid\Mail\Mail as SendGridMail;
|
||||
|
||||
class UserController extends ControllerBase
|
||||
{
|
||||
|
|
@ -28,9 +30,42 @@ class UserController extends ControllerBase
|
|||
|
||||
$new_pw = $form->getValue('passwordNew');
|
||||
if (strlen($new_pw) > 0) {
|
||||
|
||||
$hash = password_hash($new_pw, PASSWORD_BCRYPT);
|
||||
$user->setPassword($hash);
|
||||
|
||||
// User had a password before. just update.
|
||||
if (strlen($user->getPassword()) > 0) {
|
||||
$user->setPassword($hash);
|
||||
}
|
||||
// Else we create a password link and email.
|
||||
else {
|
||||
$link = new PasswordLink();
|
||||
$link->setUserId($user->getId())
|
||||
->setPassword($hash)
|
||||
->save();
|
||||
|
||||
$tpl = $this->di->get('template');
|
||||
$body = $tpl->render('mail/password_activation', [
|
||||
'link' => $link->getPublicId()
|
||||
]);
|
||||
|
||||
$mail = new SendGridMail();
|
||||
$mail->setFrom('noreply@shufflingpixels.com');
|
||||
$mail->setSubject('Httpcb password activation');
|
||||
$mail->addTo($user->getEmail());
|
||||
$mail->addContent('text/html', $body);
|
||||
|
||||
$sendgrid = $this->di->get('sendgrid');
|
||||
$sendgrid->send($mail);
|
||||
|
||||
$msg = "For security reasons. Before a password can be created "
|
||||
. "a email has been sent to <strong>{$user->getEmail()}</strong> with "
|
||||
. "a activation link.";
|
||||
|
||||
$this->flash->notice($msg);
|
||||
}
|
||||
}
|
||||
|
||||
$user->save();
|
||||
$form->initialize();
|
||||
|
||||
|
|
@ -44,6 +79,35 @@ class UserController extends ControllerBase
|
|||
$this->view->form = $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a password.
|
||||
*
|
||||
* @param $id
|
||||
*/
|
||||
public function activationLinkAction($id)
|
||||
{
|
||||
$link = PasswordLink::findFirst(['public_id = ?0', 'bind' => [ $id ]]);
|
||||
|
||||
if ($link) {
|
||||
if ($link->isValid()) {
|
||||
|
||||
// Save the password.
|
||||
$link->getUser()
|
||||
->setPassword($link->getPassword())
|
||||
->save();
|
||||
|
||||
$this->flash->success('Your password has been activated.');
|
||||
} else {
|
||||
$this->flash->error('This link has expired or has already been used.');
|
||||
}
|
||||
|
||||
// Make sure the link is deleted.
|
||||
$link->delete();
|
||||
} else {
|
||||
$this->flash->error('This does not seem to be an active link');
|
||||
}
|
||||
}
|
||||
|
||||
public function activityAction($page = 1)
|
||||
{
|
||||
$user = $this->_getAuth()->getUser();
|
||||
|
|
|
|||
|
|
@ -60,6 +60,12 @@ class RandomId extends Behavior implements BehaviorInterface
|
|||
$field = $this->_options['field'];
|
||||
$len = $this->_options['length'];
|
||||
|
||||
if (isset($this->_options['expression'])) {
|
||||
$expr = 'AND ' . $this->_options['expression'];
|
||||
} else {
|
||||
$expr = '';
|
||||
}
|
||||
|
||||
if ($model->$field === null) {
|
||||
|
||||
$random = new \Phalcon\Security\Random();
|
||||
|
|
@ -67,7 +73,7 @@ class RandomId extends Behavior implements BehaviorInterface
|
|||
$id = substr($random->base64Safe(), 0, $len);
|
||||
|
||||
$count = $model->count(array(
|
||||
"$field = ?0",
|
||||
"$field = ?0 $expr",
|
||||
'bind' => array($id)
|
||||
));
|
||||
|
||||
|
|
|
|||
18
app/migrations/20180611202847_password_link.php
Normal file
18
app/migrations/20180611202847_password_link.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
class PasswordLink extends AbstractMigration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$this->table('password_link')
|
||||
->addColumn('public_id', 'string', ['length' => 12 ])
|
||||
->addColumn('user_id', 'integer')
|
||||
->addForeignKey('user_id', 'user', ['id'], [ 'constraint' => 'FK_password_link_user' ])
|
||||
->addColumn('date', 'datetime', [ 'default' => 'CURRENT_TIMESTAMP' ])
|
||||
->addColumn('password', 'string', [ 'limit' => 255, 'null' => true ])
|
||||
->save();
|
||||
}
|
||||
}
|
||||
174
app/models/Data/PasswordLink.php
Normal file
174
app/models/Data/PasswordLink.php
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Data;
|
||||
|
||||
use Httpcb\Mvc\Model\Behavior\RandomId as RandomIdBehavior;
|
||||
use Phalcon\Forms\Element\Date;
|
||||
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
||||
|
||||
class PasswordLink extends Base
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $public_id;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $user_id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $password;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $date;
|
||||
|
||||
/**
|
||||
* Initialize method for model.
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
$this->setSource('password_link');
|
||||
$this->useDynamicUpdate(true);
|
||||
|
||||
// Relationships
|
||||
$this->belongsTo('user_id', User::class, 'id', array('alias' => 'User'));
|
||||
|
||||
// Behaviour
|
||||
$this->addBehavior(new RandomIdBehavior(array(
|
||||
'field' => 'public_id',
|
||||
'length' => 12,
|
||||
'expression' => '(password IS NULL OR HOUR(TIMEDIFF(date, NOW())) = 0)'
|
||||
)));
|
||||
|
||||
$this->addBehavior(new SoftDelete([
|
||||
'field' => 'password',
|
||||
'value' => null
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return PasswordLink
|
||||
*/
|
||||
public function setId(int $id) : PasswordLink
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPublicId()
|
||||
{
|
||||
return $this->public_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $public_id
|
||||
* @return PasswordLink
|
||||
*/
|
||||
public function setPublicId(string $public_id) : PasswordLink
|
||||
{
|
||||
$this->public_id = $public_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getUserId()
|
||||
{
|
||||
return $this->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $user_id
|
||||
* @return PasswordLink
|
||||
*/
|
||||
public function setUserId(int $user_id) : PasswordLink
|
||||
{
|
||||
$this->user_id = $user_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPassword()
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password
|
||||
* @return PasswordLink
|
||||
*/
|
||||
public function setPassword(string $password) : PasswordLink
|
||||
{
|
||||
$this->password = $password;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the link been used?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isUsed() : bool
|
||||
{
|
||||
return strlen($this->password) < 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the link still valid?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid() : bool
|
||||
{
|
||||
// Used links are not valid.
|
||||
if ($this->isUsed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$date = new \DateTime($this->date);
|
||||
$now = new \DateTime();
|
||||
|
||||
// Get the differerence in hours.
|
||||
$diff = $now->format('U') - $date->format('U');
|
||||
$diff = ($diff / 60) / 60;
|
||||
|
||||
// Only valid for an hour.
|
||||
return $diff >= 0 && $diff <= 1;
|
||||
}
|
||||
|
||||
public function beforeCreate()
|
||||
{
|
||||
// Creating a new link automatically removes old ones.
|
||||
$links = self::find(["user_id = ?0", 'bind' => [ $this->user_id ]]);
|
||||
|
||||
foreach($links as $link) {
|
||||
$link->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
app/templates/mail/password_activation.volt
Normal file
10
app/templates/mail/password_activation.volt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
{% set link_url = request.getScheme() ~ '://'
|
||||
~ request.getHttpHost()
|
||||
~ url(['for': 'activation-link', 'link': link ]) %}
|
||||
|
||||
<h1>Httcb Password Activation</h1>
|
||||
|
||||
<p>Please click <a href="{{ link_url }}">here</a> to activate the password.</p>
|
||||
|
||||
<p style="color:grey">Or copy and paste this link into the address field in your browser: <i>{{ link_url }}</i></p>
|
||||
Reference in a new issue