From 07ea0ae7f98b3d6f79e645cb0f37e318a795a3b9 Mon Sep 17 00:00:00 2001 From: Jamie Sykes Date: Tue, 28 Apr 2026 11:32:43 +0100 Subject: [PATCH] fix: fixes an issue with validation of redirection urls, adds documentation test for it --- .gitignore | 1 + README.md | 30 +++++++++++++++++ src/services/MagicLoginAuthService.php | 10 ++++-- test-config/project/project.yaml | 2 +- tests/functional/LoginFormTest.php | 45 ++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 7a703f4..2fdca8a 100755 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ prepros-6.config .ddev # Test Caching / Logging +tests/.env tests/_craft/config/project/* tests/_craft/storage/logs/* tests/_output/* diff --git a/README.md b/README.md index fb55550..552369e 100755 --- a/README.md +++ b/README.md @@ -94,6 +94,36 @@ We are also aware with this plugin that sometimes you may want to inject the exi `{% include 'magic-login/_login-form' %}` +### Redirecting after a successful Magic Login + +When generating a magic login link, you can control where the user is redirected after they click the emailed link and authentication completes. + +To do this, include a hidden field named `magicLoginRedirectUrl` in the POST that requests the magic link. + +```twig +
+ {{ actionInput('magic-login/magic-login/login') }} + {{ csrfInput() }} + + {{ hiddenInput('magicLoginRedirectUrl', url('account')) }} + + + +
+``` + +If `magicLoginRedirectUrl` is not provided (or isn’t considered valid), the plugin will fall back to Craft’s `postLoginRedirect` general config setting. + +**Valid redirect URLs** + +The `magicLoginRedirectUrl` value must be a “full” URL. That means one of: + +- Absolute URL (includes a scheme), e.g. `https://example.com/account` +- Root-relative URL (starts with `/`), e.g. `/account` +- Protocol-relative URL (starts with `//`), e.g. `//example.com/account` + +Values like `account` (no leading `/`) will be treated as invalid and the plugin will fall back to `postLoginRedirect`. + ## Technical Features / Under the Hood ### Password Generation diff --git a/src/services/MagicLoginAuthService.php b/src/services/MagicLoginAuthService.php index 9ae15d3..83ad16a 100755 --- a/src/services/MagicLoginAuthService.php +++ b/src/services/MagicLoginAuthService.php @@ -13,6 +13,7 @@ use Craft; use craft\base\Component; +use craft\helpers\UrlHelper; use creode\magiclogin\MagicLogin; use creode\magiclogin\models\AuthModel; use creode\magiclogin\records\AuthRecord; @@ -92,9 +93,12 @@ public function createMagicLogin(string $userNameOrEmail) $record->userId = $user->id; $record->publicKey = $publicKey; $record->privateKey = $privateKey; - $record->redirectUrl = Craft::$app - ->getRequest() - ->getValidatedBodyParam('magicLoginRedirectUrl') ?? $generalConfig->postLoginRedirect; + $redirectUrl = Craft::$app->getRequest()->getBodyParam('magicLoginRedirectUrl'); + if (is_string($redirectUrl) && UrlHelper::isFullUrl($redirectUrl)) { + $record->redirectUrl = $redirectUrl; + } else { + $record->redirectUrl = $generalConfig->postLoginRedirect; + } $record->save(); // Generate Datetime for current dateCreated and use it's timestamp. diff --git a/test-config/project/project.yaml b/test-config/project/project.yaml index 32aa5b5..3b9f17e 100644 --- a/test-config/project/project.yaml +++ b/test-config/project/project.yaml @@ -20,5 +20,5 @@ system: edition: pro live: true name: 'Magic login testing' - schemaVersion: 5.6.0.2 + schemaVersion: 5.9.0.8 timeZone: America/Los_Angeles diff --git a/tests/functional/LoginFormTest.php b/tests/functional/LoginFormTest.php index 5382479..efd3015 100644 --- a/tests/functional/LoginFormTest.php +++ b/tests/functional/LoginFormTest.php @@ -1,5 +1,9 @@ assertEquals($emailSubject, $magicLoginEmail->getSubject()); } + /** + * Tests that the magicLoginRedirectUrl POST param is persisted on the auth record, + * and will be used after the user clicks their magic link. + * + * @return void + */ + public function testSuccessfulLoginPersistsProvidedRedirectUrl() + { + $validUser = User::findOne(); + + AuthRecord::deleteAll(); + + $authRecords = AuthRecord::find()->all(); + + $targetRedirect = '/after-magic-login'; + + $this->tester->amOnPage('/magic-login/login'); + $this->tester->submitForm( + '#magic-login-form', + [ + 'email' => $validUser->email, + 'magicLoginRedirectUrl' => $targetRedirect, + ], + 'submitButton' + ); + + $updatedAuthRecords = AuthRecord::find()->all(); + $this->assertEquals(count($authRecords) + 1, count($updatedAuthRecords)); + + $authRecord = AuthRecord::find()->one(); + $this->assertNotNull($authRecord, 'AuthRecord should be created for a valid login request.'); + $this->assertEquals($targetRedirect, $authRecord->redirectUrl); + } + /** * Tests that we have a rate limit setup for Magic Login. + * + * @return void */ public function testMagicLoginEmailRateLimit() {