Archived
1
0
Fork 0

Merge branch '10-user-confirm-email-when-creating-new-password'

This commit is contained in:
Henrik Hautakoski 2018-06-12 23:51:52 +02:00
commit daa8803f5e
10 changed files with 418 additions and 6 deletions

View file

@ -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/

View file

@ -11,6 +11,9 @@ database:
dbname: httpcb
charset: utf8
#sendgrid
#key: value
# OAuth
#oauth:
#providers:

View file

@ -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();

View file

@ -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();

View file

@ -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)
));

View 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();
}
}

View 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();
}
}
}

View 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>