feat: dedicated login page for oauth2 logins
This commit is contained in:
1 parent
56adf72d12
commit
2aa98b81fe
8 files changed
+376
-69
No files matched your search
@@ -5,6 +5,7 @@ namespace App\Controller;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Service\BookingSessionManager;
|
||||
use App\Service\BookingSummaryAssembler;
|
||||
use League\Bundle\OAuth2ServerBundle\Manager\ClientManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -21,13 +22,28 @@ class SecurityController extends AbstractController
|
||||
Request $request,
|
||||
BookingSessionManager $bookingSessionService,
|
||||
BookingSummaryAssembler $summaryDataService,
|
||||
ClientManagerInterface $clientManager,
|
||||
): Response {
|
||||
// flag this request in the session as external OAuth2 request if
|
||||
// applicable to immediately logout the current user after successful
|
||||
// authentication (see App\EventListener\AuthorizationCodeListener).
|
||||
// Read before the booking flow is decided, because it overrules it.
|
||||
$session = $request->getSession();
|
||||
$targetPath = $session->get('_security.main.target_path');
|
||||
$isOauth2 = \is_string($targetPath) && str_contains($targetPath, '/authorize');
|
||||
$session->set('_oauth2', $isOauth2);
|
||||
|
||||
// Check if this is a booking flow (BookingDto exists in session)
|
||||
$bookingDto = $bookingSessionService->getBookingDto($request, BookingDto::MODE_CREATE);
|
||||
$isBookingFlow = null !== $bookingDto;
|
||||
$hasPendingBooking = null !== $bookingDto;
|
||||
|
||||
// A pending authorization wins over a half-finished booking. Otherwise the target path
|
||||
// below is overwritten with booking step 1, the user is sent there after logging in, and
|
||||
// the application that asked for the authorization never receives its code.
|
||||
$isBookingFlow = $hasPendingBooking && false === $isOauth2;
|
||||
|
||||
// If authenticated and in booking flow, proceed to Step 1
|
||||
if (null !== $this->getUser() && true === $isBookingFlow) {
|
||||
if (null !== $this->getUser() && true === $hasPendingBooking) {
|
||||
return $this->redirectToRoute('app_booking_create_step_1');
|
||||
}
|
||||
|
||||
@@ -39,21 +55,18 @@ class SecurityController extends AbstractController
|
||||
$error = $authenticationUtils->getLastAuthenticationError();
|
||||
$lastUsername = $authenticationUtils->getLastUsername();
|
||||
|
||||
// flag this request in the session as external OAuth2 request if
|
||||
// applicable to immediately logout the current user after successful
|
||||
// authentication (see App\EventListener\AuthorizationCodeListener).
|
||||
$session = $request->getSession();
|
||||
$targetPath = $session->get('_security.main.target_path');
|
||||
$isOauth2 = null !== $targetPath && str_contains($targetPath, '/authorize');
|
||||
$session->set('_oauth2', $isOauth2);
|
||||
|
||||
// For booking flow, set target path to Step 1 (after successful auth, redirect there)
|
||||
if (true === $isBookingFlow) {
|
||||
$session->set('_security.main.target_path', $this->generateUrl('app_booking_create_step_1'));
|
||||
}
|
||||
|
||||
// Render booking login template if in booking flow, otherwise standard login
|
||||
$template = true === $isBookingFlow ? 'booking/create/authenticate.html.twig' : 'security/login.html.twig';
|
||||
// Render booking login template if in booking flow, the self-explaining one when another
|
||||
// application sent them here to authorize it, otherwise standard login
|
||||
$template = match (true) {
|
||||
$isBookingFlow => 'booking/create/authenticate.html.twig',
|
||||
$isOauth2 => 'security/login_oauth2.html.twig',
|
||||
default => 'security/login.html.twig',
|
||||
};
|
||||
|
||||
// Fetch CMS data for booking flow (uses cache warmed in IndexController)
|
||||
$cmsData = null;
|
||||
@@ -72,9 +85,37 @@ class SecurityController extends AbstractController
|
||||
'travel_date_to' => $bookingDto?->travel->dateTo,
|
||||
'cmsData' => $cmsData,
|
||||
'oauth2' => $isOauth2,
|
||||
'client_name' => $isOauth2 ? $this->authorizingClientName($clientManager, $targetPath) : null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The display name of the application that sent the user here to authorize it, so the login page
|
||||
* can name it instead of leaving the user wondering why the site changed.
|
||||
*
|
||||
* Derived from the pending target path on every render rather than stashed on the session: a
|
||||
* failed login redirects back to this route and the target path survives, so the name comes back
|
||||
* with it. Returns null when the client cannot be resolved, and the page falls back to neutral
|
||||
* wording rather than failing -- this runs before authentication.
|
||||
*/
|
||||
private function authorizingClientName(ClientManagerInterface $clientManager, ?string $targetPath): ?string
|
||||
{
|
||||
$query = null !== $targetPath ? parse_url($targetPath, \PHP_URL_QUERY) : null;
|
||||
if (!\is_string($query) || '' === $query) {
|
||||
return null;
|
||||
}
|
||||
|
||||
parse_str($query, $params);
|
||||
$clientId = $params['client_id'] ?? null;
|
||||
if (!\is_string($clientId) || '' === $clientId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = $clientManager->find($clientId)?->getName() ?? '';
|
||||
|
||||
return '' !== $name ? $name : null;
|
||||
}
|
||||
|
||||
#[Route('/logout', name: 'app_logout')]
|
||||
#[IsGranted('ROLE_USER')]
|
||||
public function logout(): void
|
||||
|
||||
Reference in new issue
Block a user