From a1b1f8138aa9c439dc6cf375d8f843c93ceedbe3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 12 Nov 2022 15:10:14 +0000 Subject: [PATCH] Updated email confirmation flow so confirmation is done via POST To avoid non-user GET requests (Such as those from email scanners) auto-triggering the confirm submission. Made auto-submit the form via JavaScript in this extra added step with user-link backup to keep existing user flow experience. Closes #3797 --- .../Auth/ConfirmEmailController.php | 16 ++++++++++- resources/js/components/auto-submit.js | 12 +++++++++ resources/js/components/index.js | 2 ++ resources/lang/en/auth.php | 2 ++ .../auth/register-confirm-accept.blade.php | 27 +++++++++++++++++++ routes/web.php | 3 ++- tests/Auth/RegistrationTest.php | 14 ++++++++-- 7 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 resources/js/components/auto-submit.js create mode 100644 resources/views/auth/register-confirm-accept.blade.php diff --git a/app/Http/Controllers/Auth/ConfirmEmailController.php b/app/Http/Controllers/Auth/ConfirmEmailController.php index ea633ff3a..b282d0601 100644 --- a/app/Http/Controllers/Auth/ConfirmEmailController.php +++ b/app/Http/Controllers/Auth/ConfirmEmailController.php @@ -51,14 +51,28 @@ class ConfirmEmailController extends Controller return view('auth.user-unconfirmed', ['user' => $user]); } + /** + * Show the form for a user to provide their positive confirmation of their email. + */ + public function showAcceptForm(string $token) + { + return view('auth.register-confirm-accept', ['token' => $token]); + } + /** * Confirms an email via a token and logs the user into the system. * * @throws ConfirmationEmailException * @throws Exception */ - public function confirm(string $token) + public function confirm(Request $request) { + $validated = $this->validate($request, [ + 'token' => ['required', 'string'] + ]); + + $token = $validated['token']; + try { $userId = $this->emailConfirmationService->checkTokenAndGetUserId($token); } catch (UserTokenNotFoundException $exception) { diff --git a/resources/js/components/auto-submit.js b/resources/js/components/auto-submit.js new file mode 100644 index 000000000..11494ae82 --- /dev/null +++ b/resources/js/components/auto-submit.js @@ -0,0 +1,12 @@ + +class AutoSubmit { + + setup() { + this.form = this.$el; + + this.form.submit(); + } + +} + +export default AutoSubmit; \ No newline at end of file diff --git a/resources/js/components/index.js b/resources/js/components/index.js index ee282b1fd..9f801668e 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -4,6 +4,7 @@ import ajaxForm from "./ajax-form.js" import attachments from "./attachments.js" import attachmentsList from "./attachments-list.js" import autoSuggest from "./auto-suggest.js" +import autoSubmit from "./auto-submit.js"; import backToTop from "./back-to-top.js" import bookSort from "./book-sort.js" import chapterContents from "./chapter-contents.js" @@ -64,6 +65,7 @@ const componentMapping = { "attachments": attachments, "attachments-list": attachmentsList, "auto-suggest": autoSuggest, + "auto-submit": autoSubmit, "back-to-top": backToTop, "book-sort": bookSort, "chapter-contents": chapterContents, diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index c670106f9..dc4b242a0 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -61,6 +61,8 @@ return [ 'email_confirm_send_error' => 'Email confirmation required but the system could not send the email. Contact the admin to ensure email is set up correctly.', 'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.', 'email_confirm_resent' => 'Confirmation email resent, Please check your inbox.', + 'email_confirm_thanks' => 'Thanks for confirming!', + 'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.', 'email_not_confirmed' => 'Email Address Not Confirmed', 'email_not_confirmed_text' => 'Your email address has not yet been confirmed.', diff --git a/resources/views/auth/register-confirm-accept.blade.php b/resources/views/auth/register-confirm-accept.blade.php new file mode 100644 index 000000000..e52bdb411 --- /dev/null +++ b/resources/views/auth/register-confirm-accept.blade.php @@ -0,0 +1,27 @@ +@extends('layouts.simple') + +@section('content') + +
+
+

{{ trans('auth.email_confirm_thanks') }}

+ +

{{ trans('auth.email_confirm_thanks_desc') }}

+ +
+
+ @include('common.loading-icon') +
+
+
+ {{ csrf_field() }} + + +
+
+
+ +
+
+ +@stop diff --git a/routes/web.php b/routes/web.php index f66f0d984..de913c543 100644 --- a/routes/web.php +++ b/routes/web.php @@ -316,7 +316,8 @@ Route::get('/register', [Auth\RegisterController::class, 'getRegister']); Route::get('/register/confirm', [Auth\ConfirmEmailController::class, 'show']); Route::get('/register/confirm/awaiting', [Auth\ConfirmEmailController::class, 'showAwaiting']); Route::post('/register/confirm/resend', [Auth\ConfirmEmailController::class, 'resend']); -Route::get('/register/confirm/{token}', [Auth\ConfirmEmailController::class, 'confirm']); +Route::get('/register/confirm/{token}', [Auth\ConfirmEmailController::class, 'showAcceptForm']); +Route::post('/register/confirm/accept', [Auth\ConfirmEmailController::class, 'confirm']); Route::post('/register', [Auth\RegisterController::class, 'postRegister']); // SAML routes diff --git a/tests/Auth/RegistrationTest.php b/tests/Auth/RegistrationTest.php index 45d265b72..5c3aab6a8 100644 --- a/tests/Auth/RegistrationTest.php +++ b/tests/Auth/RegistrationTest.php @@ -46,8 +46,18 @@ class RegistrationTest extends TestCase return $notification->token === $emailConfirmation->token; }); - // Check confirmation email confirmation activation. - $this->get('/register/confirm/' . $emailConfirmation->token)->assertRedirect('/login'); + // Check confirmation email confirmation accept page. + $resp = $this->get('/register/confirm/' . $emailConfirmation->token); + $acceptPage = $this->withHtml($resp); + $resp->assertOk(); + $resp->assertSee('Thanks for confirming!'); + $acceptPage->assertElementExists('form[method="post"][action$="/register/confirm/accept"][component="auto-submit"] button'); + $acceptPage->assertFieldHasValue('token', $emailConfirmation->token); + + // Check acceptance confirm + $this->post('/register/confirm/accept', ['token' => $emailConfirmation->token])->assertRedirect('/login'); + + // Check state on login redirect $this->get('/login')->assertSee('Your email has been confirmed! You should now be able to login using this email address.'); $this->assertDatabaseMissing('email_confirmations', ['token' => $emailConfirmation->token]); $this->assertDatabaseHas('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);