+ /**
+ * Reattempt a system login after a previous stopped attempt.
+ *
+ * @throws Exception
+ */
+ public function reattemptLoginFor(User $user)
+ {
+ if ($user->id !== ($this->getLastLoginAttemptUser()->id ?? null)) {
+ throw new Exception('Login reattempt user does align with current session state');
+ }
+
+ $lastLoginDetails = $this->getLastLoginAttemptDetails();
+ $this->login($user, $lastLoginDetails['method'], $lastLoginDetails['remember'] ?? false);
+ }
+
+ /**
+ * Get the last user that was attempted to be logged in.
+ * Only exists if the last login attempt had correct credentials
+ * but had been prevented by a secondary factor.
+ */
+ public function getLastLoginAttemptUser(): ?User
+ {
+ $id = $this->getLastLoginAttemptDetails()['user_id'];
+
+ return User::query()->where('id', '=', $id)->first();
+ }
+
+ /**
+ * Get the details of the last login attempt.
+ * Checks upon a ttl of about 1 hour since that last attempted login.
+ *
+ * @return array{user_id: ?string, method: ?string, remember: bool}
+ */
+ protected function getLastLoginAttemptDetails(): array
+ {
+ $value = session()->get(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY);
+ if (!$value) {
+ return ['user_id' => null, 'method' => null];
+ }
+
+ [$id, $method, $remember, $time] = explode(':', $value);
+ $hourAgo = time() - (60 * 60);
+ if ($time < $hourAgo) {
+ $this->clearLastLoginAttempted();
+
+ return ['user_id' => null, 'method' => null];
+ }
+
+ return ['user_id' => $id, 'method' => $method, 'remember' => boolval($remember)];
+ }
+
+ /**
+ * Set the last login attempted user.
+ * Must be only used when credentials are correct and a login could be
+ * achieved but a secondary factor has stopped the login.
+ */
+ protected function setLastLoginAttemptedForUser(User $user, string $method, bool $remember)
+ {
+ session()->put(
+ self::LAST_LOGIN_ATTEMPTED_SESSION_KEY,
+ implode(':', [$user->id, $method, $remember, time()])
+ );
+ }
+
+ /**
+ * Clear the last login attempted session value.
+ */
+ protected function clearLastLoginAttempted(): void
+ {
+ session()->remove(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY);
+ }
+
+ /**
+ * Check if MFA verification is needed.
+ */
+ public function needsMfaVerification(User $user): bool
+ {
+ return !$this->mfaSession->isVerifiedForUser($user) && $this->mfaSession->isRequiredForUser($user);
+ }
+
+ /**
+ * Check if the given user is awaiting email confirmation.
+ */
+ public function awaitingEmailConfirmation(User $user): bool
+ {
+ return $this->emailConfirmationService->confirmationRequired() && !$user->email_confirmed;
+ }