From ba77acdb9d1ae5320f4e6b93ad2a2e332b5d6e0c Mon Sep 17 00:00:00 2001 From: isaacclad Date: Wed, 1 Jul 2026 12:05:41 +0000 Subject: [PATCH] Fix guest join flow redirecting to Care marketing page. Guests were sent through silent SSO after entering the meeting room because /room routes required auth; failed SSO fell back to leftover Care config. Co-authored-by: Cursor --- .../Controllers/Auth/SsoLoginController.php | 18 ++++- app/Http/Middleware/EnsurePlatformSession.php | 1 + bootstrap/app.php | 12 ++- config/ladill.php | 4 +- resources/views/meet/join/denied.blade.php | 2 +- routes/web.php | 78 +++++++++---------- tests/Feature/MeetWebTest.php | 26 +++++++ 7 files changed, 95 insertions(+), 46 deletions(-) diff --git a/app/Http/Controllers/Auth/SsoLoginController.php b/app/Http/Controllers/Auth/SsoLoginController.php index 10ed459..d06cbe3 100644 --- a/app/Http/Controllers/Auth/SsoLoginController.php +++ b/app/Http/Controllers/Auth/SsoLoginController.php @@ -88,7 +88,11 @@ class SsoLoginController extends Controller if ($request->filled('error')) { if (in_array($request->query('error'), ['login_required', 'interaction_required', 'consent_required'], true) && ! $request->boolean('interactive')) { - return redirect()->away((string) config('ladill.marketing_url')); + return redirect()->route('sso.connect', [ + 'redirect' => $intended, + 'interactive' => 1, + 'fallback' => 1, + ]); } return $this->finishCallback($request, $intended, (string) $request->query('error_description', $request->query('error'))); @@ -285,6 +289,10 @@ class SsoLoginController extends Controller private function resolveLanding(IdentityTeamClient $identity, User $user, string $intended): string { + if ($this->isMeetJoinUrl($intended)) { + return $intended; + } + try { return $identity->postAuthRedirect($user->ownerRef(), $intended); } catch (\Throwable) { @@ -292,6 +300,14 @@ class SsoLoginController extends Controller } } + private function isMeetJoinUrl(string $url): bool + { + $meetHost = config('app.meet_domain', 'meet.ladill.com'); + + return str_starts_with($url, 'https://'.$meetHost.'/r/') + || str_starts_with($url, 'http://'.$meetHost.'/r/'); + } + private function safeReturnUrl(string $url): string { $host = parse_url($url, PHP_URL_HOST); diff --git a/app/Http/Middleware/EnsurePlatformSession.php b/app/Http/Middleware/EnsurePlatformSession.php index a3af287..7912de2 100644 --- a/app/Http/Middleware/EnsurePlatformSession.php +++ b/app/Http/Middleware/EnsurePlatformSession.php @@ -68,6 +68,7 @@ class EnsurePlatformSession return redirect()->route('sso.connect', [ 'redirect' => $request->fullUrl(), + 'interactive' => 1, ]); } } diff --git a/bootstrap/app.php b/bootstrap/app.php index 16408be..d8ed3c1 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -54,9 +54,15 @@ return Application::configure(basePath: dirname(__DIR__)) }, ) ->withMiddleware(function (Middleware $middleware): void { - $middleware->redirectGuestsTo(fn (Request $request) => route('sso.connect', [ - 'redirect' => $request->fullUrl(), - ])); + $middleware->redirectGuestsTo(function (Request $request) { + $params = ['redirect' => $request->fullUrl()]; + + if ($request->is('r/*', 'room/*')) { + $params['interactive'] = 1; + } + + return route('sso.connect', $params); + }); $middleware->web(append: [ SetActingAccount::class, ]); diff --git a/config/ladill.php b/config/ladill.php index 7e9e2bf..ce81526 100644 --- a/config/ladill.php +++ b/config/ladill.php @@ -1,6 +1,6 @@ 'care', - 'marketing_url' => env('LADILL_MARKETING_URL', 'https://ladill.com/products/care'), + 'product_slug' => 'meet', + 'marketing_url' => env('LADILL_MARKETING_URL', 'https://ladill.com'), ]; diff --git a/resources/views/meet/join/denied.blade.php b/resources/views/meet/join/denied.blade.php index 4a3714d..70dfb4b 100644 --- a/resources/views/meet/join/denied.blade.php +++ b/resources/views/meet/join/denied.blade.php @@ -11,7 +11,7 @@

Unable to join

{{ $reason ?? 'You do not have access to this meeting.' }}

- Sign in + Sign in
diff --git a/routes/web.php b/routes/web.php index af79d48..cb9a90a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -27,7 +27,7 @@ use Illuminate\Support\Facades\Route; Route::get('/', fn () => auth()->check() ? redirect()->route('meet.dashboard') - : redirect()->route('sso.connect'))->name('meet.root'); + : redirect()->route('sso.connect', ['interactive' => 1]))->name('meet.root'); Route::get('/login', [SsoLoginController::class, 'connect'])->name('login'); Route::get('/sso/connect', [SsoLoginController::class, 'connect'])->name('sso.connect'); @@ -48,6 +48,44 @@ Route::match(['get', 'post'], '/invite/{invitation}/rsvp', [InvitationController Route::get('/w/{room}/register', [WebinarRegistrationController::class, 'show'])->name('meet.webinar.register'); Route::post('/w/{room}/register', [WebinarRegistrationController::class, 'store'])->name('meet.webinar.register.store'); +Route::get('/room/{session}', [MeetingRoomController::class, 'show'])->name('meet.room'); +Route::post('/room/{session}/end', [MeetingRoomController::class, 'end'])->name('meet.room.end'); +Route::post('/room/{session}/leave', [MeetingRoomController::class, 'leave'])->name('meet.room.leave'); +Route::post('/room/{session}/raise-hand', [MeetingRoomController::class, 'raiseHand'])->name('meet.room.raise-hand'); +Route::post('/room/{session}/chat', [MeetingRoomController::class, 'sendMessage'])->name('meet.room.chat'); +Route::post('/room/{session}/reaction', [MeetingRoomController::class, 'sendReaction'])->name('meet.room.reaction'); +Route::get('/room/{session}/poll', [MeetingRoomController::class, 'poll'])->name('meet.room.poll'); +Route::post('/room/{session}/recording/start', [MeetingRoomController::class, 'startRecording'])->name('meet.room.recording.start'); +Route::post('/room/{session}/recording/stop', [MeetingRoomController::class, 'stopRecording'])->name('meet.room.recording.stop'); +Route::post('/room/{session}/lock', [MeetingRoomController::class, 'lock'])->name('meet.room.lock'); +Route::post('/room/{session}/unlock', [MeetingRoomController::class, 'unlock'])->name('meet.room.unlock'); +Route::post('/room/{session}/caption', [MeetingRoomController::class, 'caption'])->name('meet.room.caption'); + +Route::post('/room/{session}/qa', [MeetingFeaturesController::class, 'submitQuestion'])->name('meet.room.qa.submit'); +Route::post('/room/{session}/qa/{question}/approve', [MeetingFeaturesController::class, 'approveQuestion'])->name('meet.room.qa.approve'); +Route::post('/room/{session}/qa/{question}/answer', [MeetingFeaturesController::class, 'answerQuestion'])->name('meet.room.qa.answer'); +Route::post('/room/{session}/qa/{question}/upvote', [MeetingFeaturesController::class, 'upvoteQuestion'])->name('meet.room.qa.upvote'); +Route::post('/room/{session}/polls', [MeetingFeaturesController::class, 'createPoll'])->name('meet.room.polls.create'); +Route::post('/room/{session}/polls/{poll}/vote', [MeetingFeaturesController::class, 'votePoll'])->name('meet.room.polls.vote'); +Route::post('/room/{session}/polls/{poll}/close', [MeetingFeaturesController::class, 'closePoll'])->name('meet.room.polls.close'); +Route::post('/room/{session}/breakouts', [MeetingFeaturesController::class, 'createBreakouts'])->name('meet.room.breakouts.create'); +Route::post('/room/{session}/breakouts/{breakout}/join', [MeetingFeaturesController::class, 'moveToBreakout'])->name('meet.room.breakouts.join'); +Route::post('/room/{session}/breakouts/return', [MeetingFeaturesController::class, 'returnFromBreakout'])->name('meet.room.breakouts.return'); +Route::post('/room/{session}/breakouts/broadcast', [MeetingFeaturesController::class, 'broadcastBreakout'])->name('meet.room.breakouts.broadcast'); +Route::post('/room/{session}/breakouts/close', [MeetingFeaturesController::class, 'closeBreakouts'])->name('meet.room.breakouts.close'); +Route::post('/room/{session}/files', [MeetingFeaturesController::class, 'uploadFile'])->name('meet.room.files.upload'); +Route::get('/room/{session}/files/{file}', [MeetingFeaturesController::class, 'downloadFile'])->name('meet.room.files.download'); +Route::get('/room/{session}/whiteboard', [MeetingFeaturesController::class, 'showWhiteboard'])->name('meet.room.whiteboard.show'); +Route::post('/room/{session}/whiteboard', [MeetingFeaturesController::class, 'saveWhiteboard'])->name('meet.room.whiteboard.save'); +Route::post('/room/{session}/whiteboard/export', [MeetingFeaturesController::class, 'exportWhiteboard'])->name('meet.room.whiteboard.export'); + +Route::post('/room/{session}/live-stream', [MeetingFeaturesController::class, 'configureLiveStream'])->name('meet.room.livestream.configure'); +Route::post('/room/{session}/live-stream/start', [MeetingFeaturesController::class, 'startLiveStream'])->name('meet.room.livestream.start'); +Route::post('/room/{session}/live-stream/stop', [MeetingFeaturesController::class, 'stopLiveStream'])->name('meet.room.livestream.stop'); +Route::post('/room/{session}/town-hall/green-room', [MeetingFeaturesController::class, 'startGreenRoom'])->name('meet.room.townhall.green'); +Route::post('/room/{session}/town-hall/go-live', [MeetingFeaturesController::class, 'goLiveTownHall'])->name('meet.room.townhall.live'); +Route::post('/room/{session}/town-hall/handoff', [MeetingFeaturesController::class, 'handoffCoHost'])->name('meet.room.townhall.handoff'); + Route::middleware(['auth', 'platform.session'])->group(function () { Route::get('/notifications', [NotificationController::class, 'index'])->name('notifications.index'); Route::get('/notifications/unread', [NotificationController::class, 'unread'])->name('notifications.unread'); @@ -131,44 +169,6 @@ Route::middleware(['auth', 'platform.session'])->group(function () { Route::get('/settings', [SettingsController::class, 'edit'])->name('meet.settings'); Route::put('/settings', [SettingsController::class, 'update'])->name('meet.settings.update'); - Route::get('/room/{session}', [MeetingRoomController::class, 'show'])->name('meet.room'); - Route::post('/room/{session}/end', [MeetingRoomController::class, 'end'])->name('meet.room.end'); - Route::post('/room/{session}/leave', [MeetingRoomController::class, 'leave'])->name('meet.room.leave'); - Route::post('/room/{session}/raise-hand', [MeetingRoomController::class, 'raiseHand'])->name('meet.room.raise-hand'); - Route::post('/room/{session}/chat', [MeetingRoomController::class, 'sendMessage'])->name('meet.room.chat'); - Route::post('/room/{session}/reaction', [MeetingRoomController::class, 'sendReaction'])->name('meet.room.reaction'); - Route::get('/room/{session}/poll', [MeetingRoomController::class, 'poll'])->name('meet.room.poll'); - Route::post('/room/{session}/recording/start', [MeetingRoomController::class, 'startRecording'])->name('meet.room.recording.start'); - Route::post('/room/{session}/recording/stop', [MeetingRoomController::class, 'stopRecording'])->name('meet.room.recording.stop'); - Route::post('/room/{session}/lock', [MeetingRoomController::class, 'lock'])->name('meet.room.lock'); - Route::post('/room/{session}/unlock', [MeetingRoomController::class, 'unlock'])->name('meet.room.unlock'); - Route::post('/room/{session}/caption', [MeetingRoomController::class, 'caption'])->name('meet.room.caption'); - - Route::post('/room/{session}/qa', [MeetingFeaturesController::class, 'submitQuestion'])->name('meet.room.qa.submit'); - Route::post('/room/{session}/qa/{question}/approve', [MeetingFeaturesController::class, 'approveQuestion'])->name('meet.room.qa.approve'); - Route::post('/room/{session}/qa/{question}/answer', [MeetingFeaturesController::class, 'answerQuestion'])->name('meet.room.qa.answer'); - Route::post('/room/{session}/qa/{question}/upvote', [MeetingFeaturesController::class, 'upvoteQuestion'])->name('meet.room.qa.upvote'); - Route::post('/room/{session}/polls', [MeetingFeaturesController::class, 'createPoll'])->name('meet.room.polls.create'); - Route::post('/room/{session}/polls/{poll}/vote', [MeetingFeaturesController::class, 'votePoll'])->name('meet.room.polls.vote'); - Route::post('/room/{session}/polls/{poll}/close', [MeetingFeaturesController::class, 'closePoll'])->name('meet.room.polls.close'); - Route::post('/room/{session}/breakouts', [MeetingFeaturesController::class, 'createBreakouts'])->name('meet.room.breakouts.create'); - Route::post('/room/{session}/breakouts/{breakout}/join', [MeetingFeaturesController::class, 'moveToBreakout'])->name('meet.room.breakouts.join'); - Route::post('/room/{session}/breakouts/return', [MeetingFeaturesController::class, 'returnFromBreakout'])->name('meet.room.breakouts.return'); - Route::post('/room/{session}/breakouts/broadcast', [MeetingFeaturesController::class, 'broadcastBreakout'])->name('meet.room.breakouts.broadcast'); - Route::post('/room/{session}/breakouts/close', [MeetingFeaturesController::class, 'closeBreakouts'])->name('meet.room.breakouts.close'); - Route::post('/room/{session}/files', [MeetingFeaturesController::class, 'uploadFile'])->name('meet.room.files.upload'); - Route::get('/room/{session}/files/{file}', [MeetingFeaturesController::class, 'downloadFile'])->name('meet.room.files.download'); - Route::get('/room/{session}/whiteboard', [MeetingFeaturesController::class, 'showWhiteboard'])->name('meet.room.whiteboard.show'); - Route::post('/room/{session}/whiteboard', [MeetingFeaturesController::class, 'saveWhiteboard'])->name('meet.room.whiteboard.save'); - Route::post('/room/{session}/whiteboard/export', [MeetingFeaturesController::class, 'exportWhiteboard'])->name('meet.room.whiteboard.export'); - - Route::post('/room/{session}/live-stream', [MeetingFeaturesController::class, 'configureLiveStream'])->name('meet.room.livestream.configure'); - Route::post('/room/{session}/live-stream/start', [MeetingFeaturesController::class, 'startLiveStream'])->name('meet.room.livestream.start'); - Route::post('/room/{session}/live-stream/stop', [MeetingFeaturesController::class, 'stopLiveStream'])->name('meet.room.livestream.stop'); - Route::post('/room/{session}/town-hall/green-room', [MeetingFeaturesController::class, 'startGreenRoom'])->name('meet.room.townhall.green'); - Route::post('/room/{session}/town-hall/go-live', [MeetingFeaturesController::class, 'goLiveTownHall'])->name('meet.room.townhall.live'); - Route::post('/room/{session}/town-hall/handoff', [MeetingFeaturesController::class, 'handoffCoHost'])->name('meet.room.townhall.handoff'); - Route::get('/members', [MemberController::class, 'index'])->name('meet.members.index'); Route::get('/members/create', [MemberController::class, 'create'])->name('meet.members.create'); Route::post('/members', [MemberController::class, 'store'])->name('meet.members.store'); diff --git a/tests/Feature/MeetWebTest.php b/tests/Feature/MeetWebTest.php index 2c66d4a..7ff770a 100644 --- a/tests/Feature/MeetWebTest.php +++ b/tests/Feature/MeetWebTest.php @@ -126,6 +126,32 @@ class MeetWebTest extends TestCase ->assertSessionHasErrors('join'); } + public function test_guest_can_join_live_meeting_and_enter_room(): void + { + $room = $this->createRoom([ + 'settings' => array_merge(config('meet.default_settings'), [ + 'join_before_host' => true, + ]), + ]); + $session = Session::create([ + 'owner_ref' => $this->user->public_id, + 'room_id' => $room->id, + 'media_room_name' => 'room-'.$room->uuid, + 'status' => 'live', + 'started_at' => now(), + ]); + $room->update(['status' => 'live']); + + $this->post('/r/'.$room->uuid.'/enter', [ + 'display_name' => 'Guest User', + ]) + ->assertRedirect(route('meet.room', $session)); + + $this->get('/room/'.$session->uuid) + ->assertOk() + ->assertSee('data-display-name="Guest User"', false); + } + public function test_authenticated_user_can_join_live_meeting_without_display_name(): void { $room = $this->createRoom();