Fix speaker token ignored after badge verification or active join.
Deploy Ladill Meet / deploy (push) Successful in 1m0s
Deploy Ladill Meet / deploy (push) Successful in 1m0s
Process ?speaker= before registration gate and already-joined redirects so Events speakers get panelist role even when they previously registered as attendees. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -61,6 +61,31 @@ class JoinController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$speakerToken = trim((string) $request->query('speaker', ''));
|
||||||
|
if ($speakerToken !== '' && $this->acceptsEventsSpeakerToken($room, $request)) {
|
||||||
|
if ($this->verifyAndStoreSpeaker($request, $room, $speakerToken)) {
|
||||||
|
$session = $room->activeSession();
|
||||||
|
if ($session) {
|
||||||
|
$participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
|
||||||
|
$participant = $participantUuid
|
||||||
|
? Participant::where('uuid', $participantUuid)->where('session_id', $session->id)->first()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if ($participant?->status === 'joined') {
|
||||||
|
$this->sessions->syncEventsSpeakerRole($request, $participant);
|
||||||
|
|
||||||
|
return redirect()->route('meet.room', $session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('meet.join', $room);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->gateView($request, $room)->withErrors([
|
||||||
|
'speaker' => 'Speaker invitation link is invalid or expired.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$session = $room->activeSession();
|
$session = $room->activeSession();
|
||||||
if ($session) {
|
if ($session) {
|
||||||
$participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
|
$participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
|
||||||
@@ -88,17 +113,6 @@ class JoinController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$speakerToken = trim((string) $request->query('speaker', ''));
|
|
||||||
if ($speakerToken !== '' && $this->requiresEventsRegistration($room, $request)) {
|
|
||||||
if ($this->verifyAndStoreSpeaker($request, $room, $speakerToken)) {
|
|
||||||
return redirect()->route('meet.join', $room);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->gateView($request, $room)->withErrors([
|
|
||||||
'speaker' => 'Speaker invitation link is invalid or expired.',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->requiresEventsRegistration($room, $request)) {
|
if ($this->requiresEventsRegistration($room, $request)) {
|
||||||
return $this->gateView($request, $room);
|
return $this->gateView($request, $room);
|
||||||
}
|
}
|
||||||
@@ -142,6 +156,11 @@ class JoinController extends Controller
|
|||||||
return redirect()->route('meet.join', $room);
|
return redirect()->route('meet.join', $room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$speakerToken = trim((string) $request->query('speaker', ''));
|
||||||
|
if ($speakerToken !== '' && $this->acceptsEventsSpeakerToken($room, $request)) {
|
||||||
|
$this->verifyAndStoreSpeaker($request, $room, $speakerToken);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->requiresEventsRegistration($room, $request)) {
|
if ($this->requiresEventsRegistration($room, $request)) {
|
||||||
return redirect()->route('meet.join', $room);
|
return redirect()->route('meet.join', $room);
|
||||||
}
|
}
|
||||||
@@ -164,9 +183,15 @@ class JoinController extends Controller
|
|||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$registration = EventsRegistrationSession::get($request, $room);
|
$registration = EventsRegistrationSession::get($request, $room);
|
||||||
|
$speakerVerified = EventsRegistrationSession::isSpeakerVerified($request, $room);
|
||||||
$displayName = $user?->name ?? ($validated['display_name'] ?? ($registration['name'] ?? 'Guest'));
|
$displayName = $user?->name ?? ($validated['display_name'] ?? ($registration['name'] ?? 'Guest'));
|
||||||
$email = $user?->email ?? ($registration['email'] ?? null);
|
$email = $user?->email ?? ($registration['email'] ?? null);
|
||||||
|
|
||||||
|
if ($speakerVerified && filled($registration['email'] ?? null)) {
|
||||||
|
$email = (string) $registration['email'];
|
||||||
|
$displayName = (string) ($registration['name'] ?? $displayName);
|
||||||
|
}
|
||||||
|
|
||||||
$kind = $room->isConference() ? 'conference' : ($room->isWebinar() ? 'webinar' : 'meeting');
|
$kind = $room->isConference() ? 'conference' : ($room->isWebinar() ? 'webinar' : 'meeting');
|
||||||
|
|
||||||
[$allowed, $reason] = $this->security->canJoin($room, $user, $email ?? $displayName.'@guest.local');
|
[$allowed, $reason] = $this->security->canJoin($room, $user, $email ?? $displayName.'@guest.local');
|
||||||
@@ -353,6 +378,15 @@ class JoinController extends Controller
|
|||||||
return ! EventsRegistrationSession::canJoinEventsLinked($request, $room);
|
return ! EventsRegistrationSession::canJoinEventsLinked($request, $room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function acceptsEventsSpeakerToken(Room $room, Request $request): bool
|
||||||
|
{
|
||||||
|
if (! $room->isEventsLinked() || $this->isRoomHost($request, $room)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $room->isWebinar() || $room->isConference();
|
||||||
|
}
|
||||||
|
|
||||||
protected function verifyAndStoreSpeaker(Request $request, Room $room, string $speakerToken): bool
|
protected function verifyAndStoreSpeaker(Request $request, Room $room, string $speakerToken): bool
|
||||||
{
|
{
|
||||||
$eventId = EventsSourceLink::eventId($room);
|
$eventId = EventsSourceLink::eventId($room);
|
||||||
|
|||||||
@@ -42,11 +42,13 @@ class EventsRegistrationSession
|
|||||||
/** @param array{email?: ?string, name?: ?string, badge_code?: ?string, speaker_token?: ?string} $data */
|
/** @param array{email?: ?string, name?: ?string, badge_code?: ?string, speaker_token?: ?string} $data */
|
||||||
public static function store(Request $request, Room $room, array $data): void
|
public static function store(Request $request, Room $room, array $data): void
|
||||||
{
|
{
|
||||||
|
$existing = self::get($request, $room) ?? [];
|
||||||
|
|
||||||
$request->session()->put(self::key($room), [
|
$request->session()->put(self::key($room), [
|
||||||
'email' => $data['email'] ?? null,
|
'email' => array_key_exists('email', $data) ? $data['email'] : ($existing['email'] ?? null),
|
||||||
'name' => $data['name'] ?? null,
|
'name' => array_key_exists('name', $data) ? $data['name'] : ($existing['name'] ?? null),
|
||||||
'badge_code' => $data['badge_code'] ?? null,
|
'badge_code' => array_key_exists('badge_code', $data) ? $data['badge_code'] : ($existing['badge_code'] ?? null),
|
||||||
'speaker_token' => $data['speaker_token'] ?? null,
|
'speaker_token' => array_key_exists('speaker_token', $data) ? $data['speaker_token'] : ($existing['speaker_token'] ?? null),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -267,6 +267,81 @@ class EventsJoinGateTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_speaker_token_applies_after_attendee_badge_verification(): void
|
||||||
|
{
|
||||||
|
$room = $this->createEventsLinkedRoom('webinar', [
|
||||||
|
'settings' => array_merge(config('meet.default_settings'), [
|
||||||
|
'join_before_host' => true,
|
||||||
|
'waiting_room' => false,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$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.'/verify-badge', ['badge_code' => 'BADGE123'])
|
||||||
|
->assertRedirect(route('meet.join', $room));
|
||||||
|
|
||||||
|
$this->get('/r/'.$room->uuid.'?speaker=speaker-token-123')
|
||||||
|
->assertRedirect(route('meet.join', $room));
|
||||||
|
|
||||||
|
$this->post('/r/'.$room->uuid.'/enter', [
|
||||||
|
'display_name' => 'Keynote Speaker',
|
||||||
|
])->assertRedirect(route('meet.room', $session));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('meet_participants', [
|
||||||
|
'session_id' => $session->id,
|
||||||
|
'display_name' => 'Keynote Speaker',
|
||||||
|
'email' => 'speaker@example.com',
|
||||||
|
'role' => 'panelist',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_speaker_link_promotes_already_joined_attendee(): void
|
||||||
|
{
|
||||||
|
$room = $this->createEventsLinkedRoom('webinar', [
|
||||||
|
'settings' => array_merge(config('meet.default_settings'), [
|
||||||
|
'join_before_host' => true,
|
||||||
|
'waiting_room' => false,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$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']);
|
||||||
|
|
||||||
|
$participant = \App\Models\Participant::create([
|
||||||
|
'owner_ref' => $this->user->public_id,
|
||||||
|
'session_id' => $session->id,
|
||||||
|
'display_name' => 'Keynote Speaker',
|
||||||
|
'email' => 'speaker@example.com',
|
||||||
|
'role' => 'attendee',
|
||||||
|
'status' => 'joined',
|
||||||
|
'joined_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
|
||||||
|
->get('/r/'.$room->uuid.'?speaker=speaker-token-123')
|
||||||
|
->assertRedirect(route('meet.room', $session));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('meet_participants', [
|
||||||
|
'session_id' => $session->id,
|
||||||
|
'email' => 'speaker@example.com',
|
||||||
|
'role' => 'panelist',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/** @param array<string, mixed> $overrides */
|
/** @param array<string, mixed> $overrides */
|
||||||
protected function createEventsLinkedRoom(string $type, array $overrides = []): Room
|
protected function createEventsLinkedRoom(string $type, array $overrides = []): Room
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user