]> BookStack Code Mirror - bookstack/commitdiff
Updated email confirmation flow so confirmation is done via POST
authorDan Brown <redacted>
Sat, 12 Nov 2022 15:10:14 +0000 (15:10 +0000)
committerDan Brown <redacted>
Sat, 12 Nov 2022 15:11:59 +0000 (15:11 +0000)
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

app/Http/Controllers/Auth/ConfirmEmailController.php
resources/js/components/auto-submit.js [new file with mode: 0644]
resources/js/components/index.js
resources/lang/en/auth.php
resources/views/auth/register-confirm-accept.blade.php [new file with mode: 0644]
routes/web.php
tests/Auth/RegistrationTest.php

index ea633ff3ab8cb9bc039cd41ea84dc16450c58a04..b282d0601f31eaab351f971be08d0f0ad6540150 100644 (file)
@@ -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 (file)
index 0000000..11494ae
--- /dev/null
@@ -0,0 +1,12 @@
+
+class AutoSubmit {
+
+    setup() {
+        this.form = this.$el;
+
+        this.form.submit();
+    }
+
+}
+
+export default AutoSubmit;
\ No newline at end of file
index ee282b1fd7b8094debd638e4feb17ac84a2b5fb3..9f801668e8b4958f1e8440587c38a7b2a365b4d5 100644 (file)
@@ -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,
index c670106f9b64d79f28c666e8122e077e6b0f355d..dc4b242a09ee68f5e0411e3500f59e00c5d78ea4 100644 (file)
@@ -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 (file)
index 0000000..e52bdb4
--- /dev/null
@@ -0,0 +1,27 @@
+@extends('layouts.simple')
+
+@section('content')
+
+    <div class="container very-small mt-xl">
+        <div class="card content-wrap auto-height">
+            <h1 class="list-heading">{{ trans('auth.email_confirm_thanks') }}</h1>
+
+            <p class="mb-none">{{ trans('auth.email_confirm_thanks_desc') }}</p>
+
+            <div class="flex-container-row items-center wrap">
+                <div class="flex min-width-s">
+                    @include('common.loading-icon')
+                </div>
+                <div class="flex min-width-s text-s-right">
+                    <form component="auto-submit" action="{{ url('/register/confirm/accept') }}" method="post">
+                        {{ csrf_field() }}
+                        <input type="hidden" name="token" value="{{ $token }}">
+                        <button class="text-button">{{ trans('common.continue') }}</button>
+                    </form>
+                </div>
+            </div>
+
+        </div>
+    </div>
+
+@stop
index f66f0d984ba886b84b2c25c83ec6b41b88bea050..de913c543ab3a6eb7e4d542b027618f91b22c8ce 100644 (file)
@@ -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
index 45d265b72bc72e7db6544de2319283b5a44faffb..5c3aab6a8ba2cdb0e7a9a33f09a18f4118043f24 100644 (file)
@@ -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]);