]> BookStack Code Mirror - bookstack/commitdiff
Auth: Changed email confirmations to use login attempt user
authorDan Brown <redacted>
Mon, 20 May 2024 16:23:15 +0000 (17:23 +0100)
committerDan Brown <redacted>
Mon, 20 May 2024 16:23:15 +0000 (17:23 +0100)
Negates the need for a public confirmation resend form
since we can instead just send direct to the last session login attempter.

app/Access/Controllers/ConfirmEmailController.php
app/Access/Controllers/HandlesPartialLogins.php
app/Access/EmailConfirmationService.php
app/Exceptions/StoppedAuthenticationException.php
lang/en/errors.php
resources/views/auth/register-confirm-awaiting.blade.php [moved from resources/views/auth/user-unconfirmed.blade.php with 66% similarity]
tests/Auth/RegistrationTest.php

index 94647e06e597df86b40a288932f8cd869d40f0de..d71b8f45068f9059e7b021fbfd8abebd74de8f3e 100644 (file)
@@ -32,13 +32,17 @@ class ConfirmEmailController extends Controller
 
     /**
      * Shows a notice that a user's email address has not been confirmed,
-     * Also has the option to re-send the confirmation email.
+     * along with the option to re-send the confirmation email.
      */
     public function showAwaiting()
     {
         $user = $this->loginService->getLastLoginAttemptUser();
+        if ($user === null) {
+            $this->showErrorNotification(trans('errors.login_user_not_found'));
+            return redirect('/login');
+        }
 
-        return view('auth.user-unconfirmed', ['user' => $user]);
+        return view('auth.register-confirm-awaiting');
     }
 
     /**
@@ -90,19 +94,24 @@ class ConfirmEmailController extends Controller
     /**
      * Resend the confirmation email.
      */
-    public function resend(Request $request)
+    public function resend()
     {
-        $this->validate($request, [
-            'email' => ['required', 'email', 'exists:users,email'],
-        ]);
-        $user = $this->userRepo->getByEmail($request->get('email'));
+        $user = $this->loginService->getLastLoginAttemptUser();
+        if ($user === null) {
+            $this->showErrorNotification(trans('errors.login_user_not_found'));
+            return redirect('/login');
+        }
 
         try {
             $this->emailConfirmationService->sendConfirmation($user);
+        } catch (ConfirmationEmailException $e) {
+            $this->showErrorNotification($e->getMessage());
+
+            return redirect('/login');
         } catch (Exception $e) {
             $this->showErrorNotification(trans('auth.email_confirm_send_error'));
 
-            return redirect('/register/confirm');
+            return redirect('/register/awaiting');
         }
 
         $this->showSuccessNotification(trans('auth.email_confirm_resent'));
index c5554e47300131855e14ec2894b6943551fb0680..47a63d19b0c0a0f43f528e04736b1958ad2f5acd 100644 (file)
@@ -17,7 +17,7 @@ trait HandlesPartialLogins
         $user = auth()->user() ?? $loginService->getLastLoginAttemptUser();
 
         if (!$user) {
-            throw new NotFoundException('A user for this action could not be found');
+            throw new NotFoundException(trans('errors.login_user_not_found'));
         }
 
         return $user;
index de9ea69b882302236760a8202f62880eb77622f8..1a5156d3e7aeba5237b405b035653d20379a5298 100644 (file)
@@ -17,7 +17,7 @@ class EmailConfirmationService extends UserTokenService
      *
      * @throws ConfirmationEmailException
      */
-    public function sendConfirmation(User $user)
+    public function sendConfirmation(User $user): void
     {
         if ($user->email_confirmed) {
             throw new ConfirmationEmailException(trans('errors.email_already_confirmed'), '/login');
index 51e48aa1cf307f7dc211fd2e96f94eadc3dbf583..8a917bc52261340889d6ea5259f4b4a005def92d 100644 (file)
@@ -9,16 +9,10 @@ use Illuminate\Http\Request;
 
 class StoppedAuthenticationException extends \Exception implements Responsable
 {
-    protected $user;
-    protected $loginService;
-
-    /**
-     * StoppedAuthenticationException constructor.
-     */
-    public function __construct(User $user, LoginService $loginService)
-    {
-        $this->user = $user;
-        $this->loginService = $loginService;
+    public function __construct(
+        protected User $user,
+        protected LoginService $loginService
+    ) {
         parent::__construct();
     }
 
index 8773a78cbf737dcfecac183665fa9759aa5d2959..752eb5672f1687616f1ca012d4a1b3b665c492b5 100644 (file)
@@ -37,6 +37,7 @@ return [
     'social_driver_not_found' => 'Social driver not found',
     'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.',
     'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
+    'login_user_not_found' => 'A user for this action could not be found.',
 
     // System
     'path_not_writable' => 'File path :filePath could not be uploaded to. Ensure it is writable to the server.',
similarity index 66%
rename from resources/views/auth/user-unconfirmed.blade.php
rename to resources/views/auth/register-confirm-awaiting.blade.php
index 2f780b8a31f21eaa097966fdc7b1cb4655b6baef..e5d6f61b253ff10d4247796d804d21ba15e06e89 100644 (file)
             </p>
 
             <form action="{{ url("/register/confirm/resend") }}" method="POST" class="stretch-inputs">
-                {!! csrf_field() !!}
-                <div class="form-group">
-                    <label for="email">{{ trans('auth.email') }}</label>
-                    @if($user)
-                        @include('form.text', ['name' => 'email', 'model' => $user])
-                    @else
-                        @include('form.text', ['name' => 'email'])
-                    @endif
-                </div>
+                {{ csrf_field() }}
                 <div class="form-group text-right mt-m">
                     <button type="submit" class="button">{{ trans('auth.email_not_confirmed_resend_button') }}</button>
                 </div>
index 42d1120e4a89bd70afda399fe8b538f557aafdc1..2666fa3b4c78d6a1897d2a595028aed90b925002 100644 (file)
@@ -25,6 +25,9 @@ class RegistrationTest extends TestCase
         $resp->assertRedirect('/register/confirm');
         $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
 
+        $resp = $this->get('/register/confirm');
+        $resp->assertSee('Thanks for registering!');
+
         // Ensure notification sent
         /** @var User $dbUser */
         $dbUser = User::query()->where('email', '=', $user->email)->first();
@@ -232,4 +235,59 @@ class RegistrationTest extends TestCase
 
         $response->assertStatus(429);
     }
+
+    public function test_registration_confirmation_resend()
+    {
+        Notification::fake();
+        $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']);
+        $user = User::factory()->make();
+
+        $resp = $this->post('/register', $user->only('name', 'email', 'password'));
+        $resp->assertRedirect('/register/confirm');
+        $dbUser = User::query()->where('email', '=', $user->email)->first();
+
+        $resp = $this->post('/login', ['email' => $user->email, 'password' => $user->password]);
+        $resp->assertRedirect('/register/confirm/awaiting');
+
+        $resp = $this->post('/register/confirm/resend');
+        $resp->assertRedirect('/register/confirm');
+        Notification::assertSentToTimes($dbUser, ConfirmEmailNotification::class, 2);
+    }
+
+    public function test_registration_confirmation_expired_resend()
+    {
+        Notification::fake();
+        $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']);
+        $user = User::factory()->make();
+
+        $resp = $this->post('/register', $user->only('name', 'email', 'password'));
+        $resp->assertRedirect('/register/confirm');
+        $dbUser = User::query()->where('email', '=', $user->email)->first();
+
+        $resp = $this->post('/login', ['email' => $user->email, 'password' => $user->password]);
+        $resp->assertRedirect('/register/confirm/awaiting');
+
+        $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
+        $this->travel(2)->days();
+
+        $resp = $this->post("/register/confirm/accept", [
+            'token' => $emailConfirmation->token,
+        ]);
+        $resp->assertRedirect('/register/confirm');
+        $this->assertSessionError('The confirmation token has expired, A new confirmation email has been sent.');
+
+        Notification::assertSentToTimes($dbUser, ConfirmEmailNotification::class, 2);
+    }
+
+    public function test_registration_confirmation_awaiting_and_resend_returns_to_log_if_no_login_attempt_user_found()
+    {
+        $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']);
+
+        $this->get('/register/confirm/awaiting')->assertRedirect('/login');
+        $this->assertSessionError('A user for this action could not be found.');
+        $this->flushSession();
+
+        $this->post('/register/confirm/resend')->assertRedirect('/login');
+        $this->assertSessionError('A user for this action could not be found.');
+    }
 }