From a25261cd1399e8015c69ad4ab624b1ee0440a210 Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Mon, 11 Jun 2018 22:12:47 +0200 Subject: [PATCH 01/12] composer: adding sendgrid. --- composer.json | 3 +- composer.lock | 111 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 955c3ff..481a621 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,7 @@ "league/oauth2-github": "^2.0", "league/oauth2-google": "^2.2", "omines/oauth2-gitlab": "^3.1", - "localheinz/json-printer": "^2.0" + "localheinz/json-printer": "^2.0", + "sendgrid/sendgrid": "^7.0" } } diff --git a/composer.lock b/composer.lock index 8031bbd..18c64ec 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "5b02615c35e5c720f8fc96266a7f87a3", - "content-hash": "1e872347d68f7521be008e5a53d4b5e4", + "hash": "cb468603a22f2d265e74e0e1cb692ca1", + "content-hash": "f519b8b85514afd2d01426a06e3bea02", "packages": [ { "name": "guzzlehttp/guzzle", @@ -687,6 +687,113 @@ ], "time": "2017-12-23 06:48:51" }, + { + "name": "sendgrid/php-http-client", + "version": "3.9.6", + "source": { + "type": "git", + "url": "https://github.com/sendgrid/php-http-client.git", + "reference": "e9a04d949ee2d19938ab83dc100933a3b41a8ec7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sendgrid/php-http-client/zipball/e9a04d949ee2d19938ab83dc100933a3b41a8ec7", + "reference": "e9a04d949ee2d19938ab83dc100933a3b41a8ec7", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~4.4", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "SendGrid\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Bernier", + "email": "dx@sendgrid.com" + }, + { + "name": "Elmer Thomas", + "email": "elmer@thinkingserious.com" + } + ], + "description": "HTTP REST client, simplified for PHP", + "homepage": "http://github.com/sendgrid/php-http-client", + "keywords": [ + "api", + "fluent", + "http", + "rest", + "sendgrid" + ], + "time": "2018-04-10 18:06:08" + }, + { + "name": "sendgrid/sendgrid", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sendgrid/sendgrid-php.git", + "reference": "b659d96f19f69bcef6807300f88ac7a1b2449328" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sendgrid/sendgrid-php/zipball/b659d96f19f69bcef6807300f88ac7a1b2449328", + "reference": "b659d96f19f69bcef6807300f88ac7a1b2449328", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.6", + "sendgrid/php-http-client": "~3.9" + }, + "replace": { + "sendgrid/sendgrid-php": "*" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.9 || ^6.4.3", + "squizlabs/php_codesniffer": "3.*", + "swaggest/json-diff": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "SendGrid\\": "lib/", + "SendGrid\\Mail\\": "lib/mail/", + "SendGrid\\Contacts\\": "lib/contacts/", + "SendGrid\\Stats\\": "lib/stats/" + }, + "files": [ + "lib/SendGrid.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "This library allows you to quickly and easily send emails through SendGrid using PHP.", + "homepage": "http://github.com/sendgrid/sendgrid-php", + "keywords": [ + "email", + "grid", + "send", + "sendgrid" + ], + "time": "2018-05-19 16:38:14" + }, { "name": "symfony/config", "version": "v3.4.6", From 58377273ccaf25fd28943168e43dcce9061ff8f1 Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Mon, 11 Jun 2018 22:44:25 +0200 Subject: [PATCH 02/12] migration: adding password_link table. --- .../20180611202847_password_link.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 app/migrations/20180611202847_password_link.php diff --git a/app/migrations/20180611202847_password_link.php b/app/migrations/20180611202847_password_link.php new file mode 100644 index 0000000..843dd11 --- /dev/null +++ b/app/migrations/20180611202847_password_link.php @@ -0,0 +1,18 @@ +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(); + } +} From fec9e5e6563a2de06f09551e27534d48ebc28a17 Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Mon, 11 Jun 2018 23:56:09 +0200 Subject: [PATCH 03/12] app/library/Mvc/Model/Behavior/RandomId.php: add support for extending the expression used to find unique rows. --- app/library/Mvc/Model/Behavior/RandomId.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/library/Mvc/Model/Behavior/RandomId.php b/app/library/Mvc/Model/Behavior/RandomId.php index e5c644a..93d3c2a 100644 --- a/app/library/Mvc/Model/Behavior/RandomId.php +++ b/app/library/Mvc/Model/Behavior/RandomId.php @@ -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) )); From f312ae83ce52b6d653b22cf047c455e222ef2b9d Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Mon, 11 Jun 2018 23:57:05 +0200 Subject: [PATCH 04/12] adding app/models/Data/PasswordLink.php --- app/models/Data/PasswordLink.php | 174 +++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 app/models/Data/PasswordLink.php diff --git a/app/models/Data/PasswordLink.php b/app/models/Data/PasswordLink.php new file mode 100644 index 0000000..9f834c4 --- /dev/null +++ b/app/models/Data/PasswordLink.php @@ -0,0 +1,174 @@ +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(); + } + } +} From 552af58c7f730aec4e5dadd272750722ab8d9e17 Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Tue, 12 Jun 2018 00:33:10 +0200 Subject: [PATCH 05/12] app/config/services.php: adding "activation-link" route. --- app/config/services.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/config/services.php b/app/config/services.php index 85a2866..c062c68 100644 --- a/app/config/services.php +++ b/app/config/services.php @@ -131,6 +131,11 @@ $di->setShared('router', function() { 'action' => 'settings', ))->setName('user-settings'); + $router->add('/act/{link}', array( + 'controller' => 'user', + 'action' => 'activationlink', + ))->setName('activation-link'); + return $router; }); From b6074cf3ef5caa2a503edae16ed9b04ac2d7f67e Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Tue, 12 Jun 2018 00:34:22 +0200 Subject: [PATCH 06/12] app/controllers/UserController.php: in settingsAction() create password link for new passwords. --- app/controllers/UserController.php | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/app/controllers/UserController.php b/app/controllers/UserController.php index 0d038d3..a5be866 100644 --- a/app/controllers/UserController.php +++ b/app/controllers/UserController.php @@ -4,7 +4,8 @@ namespace App\Controller; use App\Controller\ControllerBase, App\Form\UserSettings as UserSettingsForm, - App\Model\Data\ActivityLog; + App\Model\Data\ActivityLog, + App\Model\Data\PasswordLink; class UserController extends ControllerBase { @@ -21,9 +22,30 @@ 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(); + + // TODO: Send the email here. + + $msg = "For security reasons. Before a password can be created " + . "a email has been sent to {$user->getEmail()} with " + . "a activation link."; + + $this->flash->notice($msg); + } } + $user->save(); $form->initialize(); From 5cf31430c777867dfe7f5751cf11d8928ae2784e Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Tue, 12 Jun 2018 00:34:49 +0200 Subject: [PATCH 07/12] app/controllers/UserController.php: adding activationLinkAction() --- app/controllers/UserController.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/app/controllers/UserController.php b/app/controllers/UserController.php index a5be866..4e6c7de 100644 --- a/app/controllers/UserController.php +++ b/app/controllers/UserController.php @@ -58,6 +58,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(); From 7b9ae5c38112306cd106116fc17f6b757cb00221 Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Tue, 12 Jun 2018 21:57:07 +0200 Subject: [PATCH 08/12] app/config/conf.local.sample.yml: adding sendgrid. --- app/config/conf.local.sample.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/config/conf.local.sample.yml b/app/config/conf.local.sample.yml index 8fa5619..8792856 100644 --- a/app/config/conf.local.sample.yml +++ b/app/config/conf.local.sample.yml @@ -11,6 +11,9 @@ database: dbname: httpcb charset: utf8 +#sendgrid + #key: value + # OAuth #oauth: #providers: From 97e00c0c285cea3975aa404ecf45b7395eeb5637 Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Tue, 12 Jun 2018 21:57:26 +0200 Subject: [PATCH 09/12] app/config/services.php: adding sendgrid --- app/config/services.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/config/services.php b/app/config/services.php index 85a2866..1b91fe5 100644 --- a/app/config/services.php +++ b/app/config/services.php @@ -219,6 +219,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(); From 823f6d2e3f308b7a42ce1c9da2015a1e70325fac Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Tue, 12 Jun 2018 23:32:03 +0200 Subject: [PATCH 10/12] app/config/services.php: adding template service. --- app/config/conf.app.yml | 1 + app/config/services.php | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/app/config/conf.app.yml b/app/config/conf.app.yml index d2f073e..01819d0 100644 --- a/app/config/conf.app.yml +++ b/app/config/conf.app.yml @@ -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/ diff --git a/app/config/services.php b/app/config/services.php index 7271114..b540692 100644 --- a/app/config/services.php +++ b/app/config/services.php @@ -178,6 +178,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(); From 9432f189aa0d5730f3fc84c3241aba61be48ac83 Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Tue, 12 Jun 2018 23:50:39 +0200 Subject: [PATCH 11/12] adding app/templates/mail/password_activation.volt --- app/templates/mail/password_activation.volt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 app/templates/mail/password_activation.volt diff --git a/app/templates/mail/password_activation.volt b/app/templates/mail/password_activation.volt new file mode 100644 index 0000000..3b9e380 --- /dev/null +++ b/app/templates/mail/password_activation.volt @@ -0,0 +1,10 @@ + +{% set link_url = request.getScheme() ~ '://' + ~ request.getHttpHost() + ~ url(['for': 'activation-link', 'link': link ]) %} + +

Httcb Password Activation

+ +

Please click here to activate the password.

+ +

Or copy and paste this link into the address field in your browser: {{ link_url }}

From a2abf940b52ed9689923f7553afb5f21f456abcf Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Tue, 12 Jun 2018 23:51:04 +0200 Subject: [PATCH 12/12] app/controllers/UserController.php: in settingsAction() send the activation email :) --- app/controllers/UserController.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/controllers/UserController.php b/app/controllers/UserController.php index 4e6c7de..8faef57 100644 --- a/app/controllers/UserController.php +++ b/app/controllers/UserController.php @@ -5,7 +5,8 @@ namespace App\Controller; use App\Controller\ControllerBase, App\Form\UserSettings as UserSettingsForm, App\Model\Data\ActivityLog, - App\Model\Data\PasswordLink; + App\Model\Data\PasswordLink, + SendGrid\Mail\Mail as SendGridMail; class UserController extends ControllerBase { @@ -36,7 +37,19 @@ class UserController extends ControllerBase ->setPassword($hash) ->save(); - // TODO: Send the email here. + $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 {$user->getEmail()} with "