Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
All notable changes to this project will be documented in this file.
<!--- END HEADER -->

## 3.1.2 - 2024-08-30

* Backports several features to 3.x

### Features

* Add in functionality to allow a configurable amount of times a link can be accessed before it is expired. (65c274)
* Add migrations to count how many times a link has been accessed (fa28ee)
* Disable settings field and display message to user if being overridden in config (1a5dbf)

## 3.1.1 - 2023-04-12

### Bug Fixes
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,5 @@
},
"class": "creode\\magiclogin\\MagicLogin"
},
"version": "3.1.1"
"version": "3.1.2"
}
6 changes: 5 additions & 1 deletion src/MagicLogin.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class MagicLogin extends Plugin
*
* @var string
*/
public string $schemaVersion = '2.0.0';
public string $schemaVersion = '2.1.0';

/**
* Set to `true` if the plugin should have a settings view in the control panel.
Expand Down Expand Up @@ -423,10 +423,14 @@ public function getSettingsResponse(): mixed
/** @var Controller $controller */
$controller = Craft::$app->controller;

$overrides = Craft::$app->getConfig()->getConfigFromFile(strtolower($this->handle));


return $controller->renderTemplate('magic-login/settings', [
'plugin' => $this,
'settingsHtml' => $settingsHtml,
'settings' => $this->getSettings(),
'overrides' => array_keys($overrides),
]);
}

Expand Down
2 changes: 2 additions & 0 deletions src/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
];
10 changes: 8 additions & 2 deletions src/controllers/MagicLoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/migrations/Install.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
35 changes: 35 additions & 0 deletions src/migrations/m240615_120115_add_access_count_field.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace creode\magiclogin\migrations;

use Craft;
use craft\db\Migration;

/**
* m240615_120115_add_access_count_field migration.
*/
class m240615_120115_add_access_count_field extends Migration
{
/**
* @var string The database driver to use
*/
public $tableSchema;

/**
* @inheritdoc
*/
public function safeUp(): bool
{
$this->addColumn('{{%magiclogin_authrecord}}', 'accessCount', $this->integer()->defaultValue(0));
return true;
}

/**
* @inheritdoc
*/
public function safeDown(): bool
{
$this->dropColumn('{{%magiclogin_authrecord}}', 'accessCount');
return true;
}
}
15 changes: 14 additions & 1 deletion src/models/AuthModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ class AuthModel extends Model
*/
public $redirectUrl;

/**
* Amount of times the link has been accessed.
*
* @var integer
*/
public $accessCount = 0;

/**
* Creation date of the record.
*
Expand All @@ -83,6 +90,12 @@ class AuthModel extends Model
*/
public function isExpired()
{
// Access count.
$linkAccessLimit = MagicLogin::getInstance()->getSettings()->linkAccessLimit;
if ($linkAccessLimit !== null && $this->accessCount >= $linkAccessLimit) {
return true;
}

// Check if timestamp is within bounds set by plugin configuration
$linkExpiryAmount = MagicLogin::getInstance()->getSettings()->linkExpiry;
$dateCreated = new \DateTime($this->dateCreated->format('Y-m-d H:i:s'), new \DateTimeZone('UTC'));
Expand Down Expand Up @@ -121,7 +134,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;
Expand Down
9 changes: 8 additions & 1 deletion src/models/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -78,7 +85,7 @@ class Settings extends Model
public function rules(): array
{
return [
[['linkExpiry', 'passwordLength', 'emailRateLimit'], 'number'],
[['linkExpiry', 'passwordLength', 'emailRateLimit', 'linkAccessLimit'], 'number'],
[['authenticationEmailSubject'], 'string'],
];
}
Expand Down
30 changes: 30 additions & 0 deletions src/records/AuthRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
36 changes: 32 additions & 4 deletions src/templates/settings.twig
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@

{% import "_includes/forms" as forms %}

{% macro configWarning(setting) -%}
{% set setting = '<code>'~setting~'</code>' %}
{{ "This is being overridden by the {setting} config setting in your {file} config file."|t('magic-login', {
setting: setting,
file: 'magin-login.php'
})|raw }}
{%- endmacro %}
{% from _self import configWarning %}

{% block content %}

{{ actionInput('plugins/save-plugin-settings') }}
Expand All @@ -39,7 +48,9 @@
id: 'linkExpiry',
name: 'linkExpiry',
min: 1,
value: settings.linkExpiry})
value: settings.linkExpiry,
disabled: 'linkExpiry' in overrides,
warning: 'linkExpiry' in overrides ? configWarning('linkExpiry')})
}}

{{ forms.textField({
Expand All @@ -49,7 +60,9 @@
name: 'passwordLength',
min: 16,
max: 50,
value: settings.passwordLength})
value: settings.passwordLength,
disabled: 'passwordLength' in overrides,
warning: 'passwordLength' in overrides ? configWarning('passwordLength')})
}}

{{ forms.textField({
Expand All @@ -58,7 +71,20 @@
id: 'emailRateLimit',
name: 'emailRateLimit',
min: 1,
value: settings.emailRateLimit})
value: settings.emailRateLimit,
disabled: 'emailRateLimit' in overrides,
warning: 'emailRateLimit' in overrides ? configWarning('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')})
}}
</div>

Expand All @@ -69,7 +95,9 @@
instructions: 'What to display on the subject line for Magic Link emails.',
id: 'authenticationEmailSubject',
name: 'authenticationEmailSubject',
value: settings.authenticationEmailSubject})
value: settings.authenticationEmailSubject,
disabled: 'authenticationEmailSubject' in overrides,
warning: 'authenticationEmailSubject' in overrides ? configWarning('authenticationEmailSubject')})
}}
</div>
{% endnamespace %}
Expand Down
6 changes: 6 additions & 0 deletions tests/_data/config/multiple-link.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

return [
'authenticationEmailSubject' => 'Here is your magic login link',
'linkAccessLimit' => 3,
];
5 changes: 5 additions & 0 deletions tests/_data/config/single-link.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

return [
'authenticationEmailSubject' => 'Here is your magic login link',
];
12 changes: 11 additions & 1 deletion tests/_data/magiclogin_authrecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
'nextEmailSend' => null,
],
'test_user_4_auth_record' => [
'userId' => 6,
'userId' => 7,
'publicKey' => 'nq37y47rn9qq1v753q85daa96oh35zpx0okrpcnn9806pzhy18guyytr3mdhtg5x',
'privateKey' => 'vgpcgc8yh8q2f7m9p7xxj8b8zd9jnp3m58ydp5w4a4dukk3ubcqwjp6zbef5582nkc5eew5ann86emmtk9kf8mkvg9yj4zfhbzz9ur5juw5h7d92zk55wnurx3q4twmd',
'redirectUrl' => '/',
Expand All @@ -32,4 +32,14 @@
'uid' => '1fc4a27f-7615-4d7a-9248-760b1099711b',
'nextEmailSend' => null,
],
'test_login_expiry' => [
'userId' => 4,
'publicKey' => 'kwnyvg7fxbbyg5v2tdesyahryu3u73ykxhad4z2u732239u2q4e995vjdjxsj3yz',
'privateKey' => 'mgcewe6nnxm9g798kys3fdy9cprv4dpsx9xqymcwq2595gzx7wqvp2a6z4k3p7nzn4kn7x3zycqw3neybx98x3ezgrqba357j2hb7bcqsbwnjqeyxmpyvua23a4cuxnu',
'redirectUrl' => '/',
'dateCreated' => $date->format('Y-m-d H:i:s'),
'dateUpdated' => $date->format('Y-m-d H:i:s'),
'uid' => 'f46424ab-ffcf-47da-9a2c-f6fae94fd202',
'nextEmailSend' => null,
],
];
17 changes: 13 additions & 4 deletions tests/_data/magiclogin_userrecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@

return [
'pending_user' => [
'id' => 2,
// 'id' => 1,
'username' => 'test',
'email' => 'test@example.com',
'pending' => 1,
'active' => false,
'uid' => '929e0997-03fa-401e-b745-99480e53bf97',
],
'creode_test_user' => [
'id' => 2,
// 'id' => 2,
'username' => 'test',
'email' => 'creode-test@example.com',
'pending' => 1,
'active' => false,
'uid' => 'fe432693-99c9-44f6-9902-f09e246e2ca8',
],
'magic_link_expiry_user' => [
// 'id' => 10,
'username' => 'magic-login-expiry',
'email' => 'magic-login-expiry@example.com',
'uid' => '16b52ee5-4386-4fed-991e-63c4a06a7fd5',
'active' => false,
],
];
Loading