From 51d04ce6fe34101c051fb79da415952cfc03bd63 Mon Sep 17 00:00:00 2001 From: Jamie Sykes Date: Mon, 15 Jul 2024 15:39:02 +0100 Subject: [PATCH 1/4] feat: add in functionality to allow a configurable amount of times a link can be accessed before it is expired. --- CHANGELOG.md | 4 + src/config.php | 2 + src/controllers/MagicLoginController.php | 10 ++- src/migrations/Install.php | 1 + src/models/AuthModel.php | 2 +- src/models/Settings.php | 9 ++- src/records/AuthRecord.php | 30 ++++++++ src/templates/settings.twig | 11 +++ tests/_data/config/multiple-link.php | 6 ++ tests/_data/config/single-link.php | 5 ++ tests/_data/magiclogin_authrecord.php | 12 ++- tests/_data/magiclogin_userrecord.php | 17 ++++- tests/functional/LinkExpiryTest.php | 95 ++++++++++++++++++++++++ 13 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 tests/_data/config/multiple-link.php create mode 100644 tests/_data/config/single-link.php create mode 100644 tests/functional/LinkExpiryTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bb3008..36b77c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. +## 3.1.2 - 2024-08-30 + +* Backport Feature/link access limit to 3.x () + ## 3.1.1 - 2023-04-12 ### Bug Fixes diff --git a/src/config.php b/src/config.php index e7e77d7..af1d450 100755 --- a/src/config.php +++ b/src/config.php @@ -31,4 +31,6 @@ 'authenticationEmailSubject' => 'Magic Login Link', // Rate Limit for how frequently a magic login email can be sent (in minutes). 'emailRateLimit' => 5, + // Amount of times a link can be accessed before it expires. + 'linkAccessLimit' => 1, ]; diff --git a/src/controllers/MagicLoginController.php b/src/controllers/MagicLoginController.php index ba5e7b9..9d75754 100755 --- a/src/controllers/MagicLoginController.php +++ b/src/controllers/MagicLoginController.php @@ -326,8 +326,14 @@ public function actionAuth($publicKey, $timestamp, $signature) return $this->redirect($loginUrl); } - // Remove the auth record since we are logged in now. - $authRecord->delete(); + // Increment the access count. + $authRecord->accessCount++; + $authRecord->save(); + + if ($authRecord->hasExpired()) { + // Remove the auth record since we are logged in now. + $authRecord->delete(); + } // Redirect user to the url provided by the login page. return $this->redirect($authRecord->redirectUrl); diff --git a/src/migrations/Install.php b/src/migrations/Install.php index 3e4dc6a..32a8cfa 100755 --- a/src/migrations/Install.php +++ b/src/migrations/Install.php @@ -111,6 +111,7 @@ protected function createTables() 'publicKey' => $this->string()->notNull(), 'privateKey' => $this->string()->notNull(), 'redirectUrl' => $this->string()->null(), + 'accessCount' => $this->integer()->notNull()->defaultValue(0), 'dateCreated' => $this->dateTime()->notNull(), 'dateUpdated' => $this->dateTime()->notNull(), // 'siteId' => $this->integer()->notNull(), - I don't think this is required right now but may be in future. diff --git a/src/models/AuthModel.php b/src/models/AuthModel.php index 9c7d377..d350f25 100755 --- a/src/models/AuthModel.php +++ b/src/models/AuthModel.php @@ -121,7 +121,7 @@ public function rules(): array { $rules = parent::rules(); $rules[] = [['publicKey', 'privateKey', 'redirectUrl'], 'string']; - $rules[] = [['userId'], 'number']; + $rules[] = [['userId', 'accessCount'], 'number']; $rules[] = [['dateCreated', 'nextEmailSend'], DateTimeValidator::class]; return $rules; diff --git a/src/models/Settings.php b/src/models/Settings.php index f765d1c..145b148 100755 --- a/src/models/Settings.php +++ b/src/models/Settings.php @@ -59,6 +59,13 @@ class Settings extends Model */ public ?int $emailRateLimit = 5; + /** + * How many times a login link can be accessed before it expires. + * + * @var integer + */ + public ?int $linkAccessLimit = 1; + // TODO: Add a setting to say if magic login click should also verify a user. // Grey out the option if verification is disabled on the website. @@ -78,7 +85,7 @@ class Settings extends Model public function rules(): array { return [ - [['linkExpiry', 'passwordLength', 'emailRateLimit'], 'number'], + [['linkExpiry', 'passwordLength', 'emailRateLimit', 'linkAccessLimit'], 'number'], [['authenticationEmailSubject'], 'string'], ]; } diff --git a/src/records/AuthRecord.php b/src/records/AuthRecord.php index bff0b5f..75c5e9b 100755 --- a/src/records/AuthRecord.php +++ b/src/records/AuthRecord.php @@ -52,4 +52,34 @@ public static function tableName() { return '{{%magiclogin_authrecord}}'; } + + /** + * Determine if we have hit the access limit for the auth record. + * + * @return boolean + */ + public function hasHitAccessLimit() { + $linkAccessLimit = MagicLogin::$plugin->getSettings()->linkAccessLimit; + if ($linkAccessLimit !== null && $this->accessCount >= $linkAccessLimit) { + return true; + } + + return false; + } + + public function hasExpired() { + // Check if timestamp is within bounds set by plugin configuration + $linkExpiryAmount = MagicLogin::getInstance()->getSettings()->linkExpiry; + $dateCreated = new \DateTime($this->dateCreated, new \DateTimeZone('UTC')); + $expiryTimestamp = $dateCreated->getTimestamp() + ($linkExpiryAmount * 60); + if ($expiryTimestamp < time()) { + return true; + } + + if ($this->hasHitAccessLimit()) { + return true; + } + + return false; + } } diff --git a/src/templates/settings.twig b/src/templates/settings.twig index 97f8e72..51a6038 100755 --- a/src/templates/settings.twig +++ b/src/templates/settings.twig @@ -60,6 +60,17 @@ min: 1, value: settings.emailRateLimit}) }} + + {{ forms.textField({ + label: 'Link Access Limit', + instructions: 'When sending magic login links, this number states how many times the link can be accessed before it expires.', + id: 'linkAccessLimit', + name: 'linkAccessLimit', + min: 1, + value: settings.linkAccessLimit, + disabled: 'linkAccessLimit' in overrides, + warning: 'linkAccessLimit' in overrides ? configWarning('linkAccessLimit')}) + }} {% endnamespace %}