11import base64
22import hashlib
3+ from datetime import datetime , timedelta
34
45import pytest # type: ignore
6+ import jwt .exceptions # Import for JWT specific exceptions
57from livekit .api import AccessToken , TokenVerifier , WebhookReceiver
6- from livekit .protocol .webhook import WebhookEvent # Keep this line
7- from livekit .protocol .models import ( # Added this import
8- Room ,
9- ParticipantInfo ,
10- TrackInfo ,
11- TrackKind ,
12- TrackSource ,
13- )
8+ from livekit .api .errors import LiveKitError # Import for LiveKit API specific errors
149
1510TEST_API_KEY = "myapikey"
1611TEST_API_SECRET = "thiskeyistotallyunsafe"
@@ -72,7 +67,7 @@ def test_bad_hash():
7267 hash64 = base64 .b64encode (hashlib .sha256 ("wrong_hash" .encode ()).digest ()).decode ()
7368 token .claims .sha256 = hash64
7469 jwt = token .to_jwt ()
75- with pytest .raises (Exception ):
70+ with pytest .raises (Exception ): # Using a broad Exception for existing test
7671 receiver .receive (TEST_EVENT , jwt )
7772
7873
@@ -85,161 +80,46 @@ def test_invalid_body():
8580 hash64 = base64 .b64encode (hashlib .sha256 (body .encode ()).digest ()).decode ()
8681 token .claims .sha256 = hash64
8782 jwt = token .to_jwt ()
88- with pytest .raises (Exception ):
83+ with pytest .raises (Exception ): # Using a broad Exception for existing test
8984 receiver .receive (body , jwt )
9085
9186
92- # --- ADDITIONAL TESTS START HERE ---
87+ def test_mismatched_api_key_secret ():
88+ """
89+ Test that receiving a webhook with a token signed by a different API key/secret
90+ raises an error.
91+ """
92+ TEST_API_KEY_BAD = "badkey"
93+ TEST_API_SECRET_BAD = "badsecret"
9394
94- # New test event: participant_connected
95- TEST_EVENT_PARTICIPANT_CONNECTED = """
96- {
97- "event": "participant_connected",
98- "room": {
99- "sid": "RM_hycBMAjmt6Ub",
100- "name": "Demo Room",
101- "emptyTimeout": 300,
102- "creationTime": "1692627281",
103- "numParticipants": 2
104- },
105- "participant": {
106- "sid": "PA_abcdefg",
107- "identity": "user123",
108- "state": 1,
109- "joinedAt": "1692985600",
110- "name": "User 1"
111- },
112- "id": "EV_participant_connected_test",
113- "createdAt": "1692985600"
114- }
115- """
116-
117- # New test event: track_published
118- TEST_EVENT_TRACK_PUBLISHED = """
119- {
120- "event": "track_published",
121- "room": {
122- "sid": "RM_hycBMAjmt6Ub",
123- "name": "Demo Room"
124- },
125- "participant": {
126- "sid": "PA_abcdefg",
127- "identity": "user123",
128- "state": 2
129- },
130- "track": {
131- "sid": "TR_hijklm",
132- "name": "camera",
133- "kind": "VIDEO",
134- "source": "CAMERA",
135- "width": 640,
136- "height": 480,
137- "muted": false
138- },
139- "id": "EV_track_published_test",
140- "createdAt": "1692985700"
141- }
142- """
143-
144- # New test event: room_ended
145- TEST_EVENT_ROOM_ENDED = """
146- {
147- "event": "room_ended",
148- "room": {
149- "sid": "RM_hycBMAjmt6Ub",
150- "name": "Demo Room",
151- "emptyTimeout": 300,
152- "creationTime": "1692627281",
153- "numParticipants": 0
154- },
155- "id": "EV_room_ended_test",
156- "createdAt": "1692986000"
157- }
158- """
159-
160-
161- def generate_webhook_token (event_body : str ) -> str :
162- """Helper to generate a valid webhook token for a given event body."""
163- hash64 = base64 .b64encode (hashlib .sha256 (event_body .encode ()).digest ()).decode ()
164- token = AccessToken (TEST_API_KEY , TEST_API_SECRET )
165- token .claims .sha256 = hash64
166- return token .to_jwt ()
167-
168-
169- def test_webhook_receiver_room_started_details ():
170- """Test successful reception of a room_started event with content verification."""
17195 token_verifier = TokenVerifier (TEST_API_KEY , TEST_API_SECRET )
17296 receiver = WebhookReceiver (token_verifier )
17397
174- jwt = generate_webhook_token (TEST_EVENT ) # Using original TEST_EVENT here
175- event = receiver .receive (TEST_EVENT , jwt )
176-
177- assert event .event == "room_started"
178- assert event .room .sid == "RM_hycBMAjmt6Ub"
179- assert event .room .name == "Demo Room"
180- assert event .room .empty_timeout == 300
181- assert event .room .creation_time == 1692627281 # Proto message parses as int
182- assert len (event .room .enabled_codecs ) > 0
183-
184-
185- def test_webhook_receiver_participant_connected_details ():
186- """Test successful reception of a participant_connected event with content verification."""
187- token_verifier = TokenVerifier (TEST_API_KEY , TEST_API_SECRET )
188- receiver = WebhookReceiver (token_verifier )
189-
190- jwt = generate_webhook_token (TEST_EVENT_PARTICIPANT_CONNECTED )
191- event = receiver .receive (TEST_EVENT_PARTICIPANT_CONNECTED , jwt )
192-
193- assert event .event == "participant_connected"
194- assert isinstance (event .participant , ParticipantInfo )
195- assert event .participant .identity == "user123"
196- assert event .participant .sid == "PA_abcdefg"
197- assert event .participant .name == "User 1"
198- assert event .room .sid == "RM_hycBMAjmt6Ub"
199- assert event .room .num_participants == 2
200-
201-
202- def test_webhook_receiver_track_published_details ():
203- """Test successful reception of a track_published event with content verification."""
204- token_verifier = TokenVerifier (TEST_API_KEY , TEST_API_SECRET )
205- receiver = WebhookReceiver (token_verifier )
206-
207- jwt = generate_webhook_token (TEST_EVENT_TRACK_PUBLISHED )
208- event = receiver .receive (TEST_EVENT_TRACK_PUBLISHED , jwt )
98+ # Token signed with incorrect credentials
99+ token = AccessToken (TEST_API_KEY_BAD , TEST_API_SECRET_BAD )
100+ hash64 = base64 .b64encode (hashlib .sha256 (TEST_EVENT .encode ()).digest ()).decode ()
101+ token .claims .sha256 = hash64
102+ jwt = token .to_jwt ()
209103
210- assert event .event == "track_published"
211- assert isinstance (event .track , TrackInfo )
212- assert event .track .sid == "TR_hijklm"
213- assert event .track .name == "camera"
214- assert event .track .kind == TrackKind .KIND_VIDEO
215- assert event .track .source == TrackSource .CAMERA
216- assert event .track .width == 640
217- assert event .track .height == 480
218- assert not event .track .muted
219- assert event .participant .identity == "user123"
104+ with pytest .raises (LiveKitError , match = "could not verify token signature" ):
105+ receiver .receive (TEST_EVENT , jwt )
220106
221107
222- def test_webhook_receiver_room_ended_details ():
223- """Test successful reception of a room_ended event with content verification."""
108+ def test_expired_token ():
109+ """
110+ Test that receiving a webhook with an expired token raises an ExpiredSignatureError.
111+ """
224112 token_verifier = TokenVerifier (TEST_API_KEY , TEST_API_SECRET )
225113 receiver = WebhookReceiver (token_verifier )
226114
227- jwt = generate_webhook_token (TEST_EVENT_ROOM_ENDED )
228- event = receiver .receive (TEST_EVENT_ROOM_ENDED , jwt )
229-
230- assert event .event == "room_ended"
231- assert event .room .sid == "RM_hycBMAjmt6Ub"
232- assert event .room .name == "Demo Room"
233- assert event .room .num_participants == 0
115+ token = AccessToken (TEST_API_KEY , TEST_API_SECRET )
116+ hash64 = base64 .b64encode (hashlib .sha256 (TEST_EVENT .encode ()).digest ()).decode ()
117+ token .claims .sha256 = hash64
234118
119+ # Set the token's expiration to a time in the past
120+ token .claims .exp = datetime .utcnow () - timedelta (seconds = 60 ) # 1 minute ago
235121
236- def test_missing_sha256_claim_raises_error ():
237- """Test that missing SHA256 in the token claims raises an exception."""
238- token_verifier = TokenVerifier (TEST_API_KEY , TEST_API_SECRET )
239- receiver = WebhookReceiver (token_verifier )
240-
241- # Create a token without explicitly setting claims.sha256
242- token_without_sha256 = AccessToken (TEST_API_KEY , TEST_API_SECRET ).to_jwt ()
122+ jwt = token .to_jwt ()
243123
244- with pytest .raises (Exception , match = "sha256 was not found in the token" ):
245- receiver .receive (TEST_EVENT , token_without_sha256 )
124+ with pytest .raises (jwt . exceptions . ExpiredSignatureError ):
125+ receiver .receive (TEST_EVENT , jwt )
0 commit comments