From 479e94add88becd97eb281323dc26d1ed6cd074a Mon Sep 17 00:00:00 2001 From: juancarmore Date: Fri, 10 Oct 2025 18:23:47 +0200 Subject: [PATCH] test: refactor authentication in integration tests to use header-based access tokens; add coverage for cookie-based mode --- backend/tests/helpers/assertion-helpers.ts | 26 - backend/tests/helpers/request-helpers.ts | 244 +++++---- backend/tests/helpers/test-scenarios.ts | 28 +- .../api/global-config/security.test.ts | 8 +- .../api/meetings/end-meeting.test.ts | 6 +- .../api/meetings/kick-participant.test.ts | 6 +- .../api/meetings/update-participant.test.ts | 16 +- .../api/participants/generate-token.test.ts | 77 ++- .../api/participants/refresh-token.test.ts | 62 ++- .../recordings/bulk-delete-recording.test.ts | 16 +- .../api/recordings/delete-recording.test.ts | 20 +- .../recordings/download-recordings.test.ts | 10 +- .../recordings/get-media-recording.test.ts | 4 +- .../api/recordings/get-recording.test.ts | 4 +- .../api/recordings/get-recordings.test.ts | 6 +- .../api/recordings/race-conditions.test.ts | 71 ++- .../api/recordings/start-recording.test.ts | 52 +- .../api/recordings/stop-recording.test.ts | 26 +- .../api/rooms/bulk-delete-rooms.test.ts | 4 +- .../integration/api/rooms/create-room.test.ts | 91 +++- .../integration/api/rooms/delete-room.test.ts | 22 +- .../api/rooms/garbage-collector.test.ts | 10 +- .../rooms/generate-recording-token.test.ts | 47 +- .../integration/api/rooms/get-room.test.ts | 2 +- .../api/rooms/update-room-status.test.ts | 2 +- .../integration/api/security/auth.test.ts | 138 ++++-- .../security/global-config-security.test.ts | 74 ++- .../api/security/meeting-security.test.ts | 93 +++- .../api/security/participant-security.test.ts | 101 +++- .../api/security/recording-security.test.ts | 467 ++++++++++++------ .../api/security/room-security.test.ts | 178 ++++++- .../api/security/user-security.test.ts | 52 +- .../api/users/change-password.test.ts | 12 +- .../integration/api/users/get-profile.test.ts | 6 +- .../integration/webhooks/webhook.test.ts | 4 +- 35 files changed, 1380 insertions(+), 605 deletions(-) diff --git a/backend/tests/helpers/assertion-helpers.ts b/backend/tests/helpers/assertion-helpers.ts index 5c2cdb8..18448cc 100644 --- a/backend/tests/helpers/assertion-helpers.ts +++ b/backend/tests/helpers/assertion-helpers.ts @@ -598,19 +598,6 @@ export const expectValidParticipantTokenResponse = ( expect(metadata).toHaveProperty('roles'); expect(metadata.roles).toEqual(expect.arrayContaining(rolesAndPermissions)); expect(metadata).toHaveProperty('selectedRole', participantRole); - - // Check that the token is included in a cookie - expect(response.headers['set-cookie']).toBeDefined(); - const cookies = response.headers['set-cookie'] as unknown as string[]; - const participantTokenCookie = cookies.find((cookie) => - cookie.startsWith(`${INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME}=`) - ) as string; - expect(participantTokenCookie).toBeDefined(); - expect(participantTokenCookie).toContain(token); - expect(participantTokenCookie).toContain('HttpOnly'); - expect(participantTokenCookie).toContain('SameSite=None'); - expect(participantTokenCookie).toContain('Secure'); - expect(participantTokenCookie).toContain('Path=/'); }; export const expectValidRecordingTokenResponse = ( @@ -636,19 +623,6 @@ export const expectValidRecordingTokenResponse = ( canRetrieveRecordings, canDeleteRecordings }); - - // Check that the token is included in a cookie - expect(response.headers['set-cookie']).toBeDefined(); - const cookies = response.headers['set-cookie'] as unknown as string[]; - const recordingTokenCookie = cookies.find((cookie) => - cookie.startsWith(`${INTERNAL_CONFIG.RECORDING_TOKEN_COOKIE_NAME}=`) - ) as string; - expect(recordingTokenCookie).toBeDefined(); - expect(recordingTokenCookie).toContain(token); - expect(recordingTokenCookie).toContain('HttpOnly'); - expect(recordingTokenCookie).toContain('SameSite=None'); - expect(recordingTokenCookie).toContain('Secure'); - expect(recordingTokenCookie).toContain('Path=/'); }; const decodeJWTToken = (token: string) => { diff --git a/backend/tests/helpers/request-helpers.ts b/backend/tests/helpers/request-helpers.ts index f1f350a..928e0de 100644 --- a/backend/tests/helpers/request-helpers.ts +++ b/backend/tests/helpers/request-helpers.ts @@ -18,7 +18,6 @@ import { RecordingService, RoomService } from '../../src/services/index.js'; import { AuthMode, AuthTransportMode, - AuthType, MeetRecordingAccess, MeetRecordingInfo, MeetRecordingStatus, @@ -57,10 +56,10 @@ export const startTestServer = (): Express => { export const generateApiKey = async (): Promise => { checkAppIsRunning(); - const adminCookie = await loginUser(); + const accessToken = await loginUser(); const response = await request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth/api-keys`) - .set('Cookie', adminCookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken), accessToken) .send(); expect(response.status).toBe(201); expect(response.body).toHaveProperty('key'); @@ -70,10 +69,10 @@ export const generateApiKey = async (): Promise => { export const getApiKeys = async () => { checkAppIsRunning(); - const adminCookie = await loginUser(); + const accessToken = await loginUser(); const response = await request(app) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth/api-keys`) - .set('Cookie', adminCookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken), accessToken) .send(); return response; }; @@ -90,10 +89,10 @@ export const getRoomsAppearanceConfig = async () => { export const updateRoomsAppearanceConfig = async (config: any) => { checkAppIsRunning(); - const adminCookie = await loginUser(); + const accessToken = await loginUser(); const response = await request(app) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/rooms/appearance`) - .set('Cookie', adminCookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken), accessToken) .send(config); return response; }; @@ -101,10 +100,10 @@ export const updateRoomsAppearanceConfig = async (config: any) => { export const getWebbhookConfig = async () => { checkAppIsRunning(); - const adminCookie = await loginUser(); + const accessToken = await loginUser(); const response = await request(app) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/webhooks`) - .set('Cookie', adminCookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken), accessToken) .send(); return response; }; @@ -112,10 +111,10 @@ export const getWebbhookConfig = async () => { export const updateWebbhookConfig = async (config: WebhookConfig) => { checkAppIsRunning(); - const adminCookie = await loginUser(); + const accessToken = await loginUser(); const response = await request(app) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/webhooks`) - .set('Cookie', adminCookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken), accessToken) .send(config); return response; @@ -133,40 +132,51 @@ export const testWebhookUrl = async (url: string) => { export const getSecurityConfig = async () => { checkAppIsRunning(); - const adminCookie = await loginUser(); - const response = await request(app) - .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/security`) - .set('Cookie', adminCookie) - .send(); + const response = await request(app).get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/security`).send(); return response; }; export const updateSecurityConfig = async (config: any) => { checkAppIsRunning(); - const adminCookie = await loginUser(); + const accessToken = await loginUser(); const response = await request(app) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/security`) - .set('Cookie', adminCookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken), accessToken) .send(config); return response; }; export const changeSecurityConfig = async (authMode: AuthMode) => { - const response = await updateSecurityConfig({ - authentication: { - authMethod: { - type: AuthType.SINGLE_USER - }, - authTransportMode: AuthTransportMode.COOKIE, - authModeToAccessRoom: authMode - } - }); + // Get current config to avoid overwriting other properties + let response = await getSecurityConfig(); + expect(response.status).toBe(200); + const currentConfig = response.body; + + currentConfig.authentication.authModeToAccessRoom = authMode; + response = await updateSecurityConfig(currentConfig); expect(response.status).toBe(200); }; +export const changeAuthTransportMode = async (authTransportMode: AuthTransportMode) => { + // Get current config to avoid overwriting other properties + let response = await getSecurityConfig(); + expect(response.status).toBe(200); + const currentConfig = response.body; + + currentConfig.authentication.authTransportMode = authTransportMode; + response = await updateSecurityConfig(currentConfig); + expect(response.status).toBe(200); +}; + +const getAuthTransportMode = async (): Promise => { + const response = await getSecurityConfig(); + return response.body.authentication.authTransportMode; +}; + /** - * Logs in a user and returns the access token cookie + * Logs in a user and returns the access token in the format + * "Bearer " or the cookie string if in cookie mode */ export const loginUser = async (): Promise => { checkAppIsRunning(); @@ -176,28 +186,56 @@ export const loginUser = async (): Promise => { .send(CREDENTIALS.admin) .expect(200); - const cookies = response.headers['set-cookie'] as unknown as string[]; - const accessTokenCookie = cookies.find((cookie) => - cookie.startsWith(`${INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME}=`) - ) as string; - return accessTokenCookie; + const authTransportMode = await getAuthTransportMode(); + + // Return token in header or cookie based on transport mode + if (authTransportMode === AuthTransportMode.COOKIE) { + const cookie = extractCookieFromHeaders(response, INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME); + return cookie!; + } + + expect(response.body).toHaveProperty('accessToken'); + return `Bearer ${response.body.accessToken}`; }; -export const getProfile = async (cookie: string) => { +/** + * Extracts cookie from response headers + * + * @param response - The supertest response + * @param cookieName - Name of the cookie to extract + * @returns The cookie string + */ +export const extractCookieFromHeaders = (response: Response, cookieName: string): string | undefined => { + expect(response.headers['set-cookie']).toBeDefined(); + const cookies = response.headers['set-cookie'] as unknown as string[]; + return cookies?.find((cookie) => cookie.startsWith(`${cookieName}=`)); +}; + +/** + * Selects the appropriate HTTP header name based on the format of the provided access token. + * + * If the access token starts with 'Bearer ', the specified header name is returned (typically 'Authorization'). + * Otherwise, 'Cookie' is returned, indicating that the token should be sent as a cookie. + */ +const selectHeaderBasedOnToken = (headerName: string, accessToken: string): string => { + return accessToken.startsWith('Bearer ') ? headerName : 'Cookie'; +}; + +export const getProfile = async (accessToken: string) => { checkAppIsRunning(); return await request(app) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users/profile`) - .set('Cookie', cookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken), accessToken) .send(); }; -export const changePassword = async (currentPassword: string, newPassword: string, cookie: string) => { +export const changePassword = async (currentPassword: string, newPassword: string, accessToken: string) => { checkAppIsRunning(); return await request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users/change-password`) - .set('Cookie', cookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken), accessToken) .send({ currentPassword, newPassword }); }; @@ -229,13 +267,16 @@ export const getRooms = async (query: Record = {}) => { * @returns A Promise that resolves to the room data * @throws Error if the app instance is not defined */ -export const getRoom = async (roomId: string, fields?: string, cookie?: string, role?: ParticipantRole) => { +export const getRoom = async (roomId: string, fields?: string, participantToken?: string, role?: ParticipantRole) => { checkAppIsRunning(); const req = request(app).get(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms/${roomId}`).query({ fields }); - if (cookie && role) { - req.set('Cookie', cookie).set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, role); + if (participantToken && role) { + req.set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, participantToken).set( + INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, + role + ); } else { req.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY); } @@ -372,49 +413,51 @@ export const getRoomRoleBySecret = async (roomId: string, secret: string) => { return response; }; -export const generateParticipantToken = async (participantOptions: any, cookie?: string) => { +export const generateParticipantTokenRequest = async (participantOptions: any, previousToken?: string) => { checkAppIsRunning(); // Disable authentication to generate the token await changeSecurityConfig(AuthMode.NONE); // Generate the participant token - const response = await request(app) - .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants/token`) - .set('Cookie', cookie || '') - .send(participantOptions); - return response; + const req = request(app).post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants/token`); + + if (previousToken) { + req.set(selectHeaderBasedOnToken(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, previousToken), previousToken); + } + + req.send(participantOptions); + return await req; }; /** - * Generates a participant token for a room and returns the cookie containing the token + * Generates a participant token for a room and returns the JWT token in the format "Bearer " */ -export const generateParticipantTokenCookie = async ( +export const generateParticipantToken = async ( roomId: string, secret: string, - participantName: string, - cookie?: string + participantName: string ): Promise => { - // Generate the participant token - const response = await generateParticipantToken( - { - roomId, - secret, - participantName - }, - cookie - ); + const response = await generateParticipantTokenRequest({ + roomId, + secret, + participantName + }); expect(response.status).toBe(200); - // Return the participant token cookie - const cookies = response.headers['set-cookie'] as unknown as string[]; - const participantTokenCookie = cookies.find((cookie) => - cookie.startsWith(`${INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME}=`) - ) as string; - return participantTokenCookie; + const authTransportMode = await getAuthTransportMode(); + + // Return token in header or cookie based on transport mode + if (authTransportMode === AuthTransportMode.COOKIE) { + const cookie = extractCookieFromHeaders(response, INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME); + return cookie!; + } + + expect(response.body).toHaveProperty('token'); + return `Bearer ${response.body.token}`; }; -export const refreshParticipantToken = async (participantOptions: any, cookie: string) => { +export const refreshParticipantToken = async (participantOptions: any, previousToken: string) => { checkAppIsRunning(); // Disable authentication to generate the token @@ -422,7 +465,7 @@ export const refreshParticipantToken = async (participantOptions: any, cookie: s const response = await request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants/token/refresh`) - .set('Cookie', cookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, previousToken), previousToken) .send(participantOptions); return response; }; @@ -541,42 +584,42 @@ export const updateParticipant = async ( roomId: string, participantIdentity: string, newRole: ParticipantRole, - moderatorCookie: string + moderatorToken: string ) => { checkAppIsRunning(); const response = await request(app) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings/${roomId}/participants/${participantIdentity}/role`) - .set('Cookie', moderatorCookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, moderatorToken), moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) .send({ role: newRole }); return response; }; -export const kickParticipant = async (roomId: string, participantIdentity: string, moderatorCookie: string) => { +export const kickParticipant = async (roomId: string, participantIdentity: string, moderatorToken: string) => { checkAppIsRunning(); const response = await request(app) .delete(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings/${roomId}/participants/${participantIdentity}`) - .set('Cookie', moderatorCookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, moderatorToken), moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) .send(); return response; }; -export const endMeeting = async (roomId: string, moderatorCookie: string) => { +export const endMeeting = async (roomId: string, moderatorToken: string) => { checkAppIsRunning(); const response = await request(app) .delete(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings/${roomId}`) - .set('Cookie', moderatorCookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, moderatorToken), moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) .send(); await sleep('1s'); return response; }; -export const generateRecordingToken = async (roomId: string, secret: string) => { +export const generateRecordingTokenRequest = async (roomId: string, secret: string) => { checkAppIsRunning(); // Disable authentication to generate the token @@ -591,39 +634,42 @@ export const generateRecordingToken = async (roomId: string, secret: string) => }; /** - * Generates a token for retrieving/deleting recordings from a room and returns the cookie containing the token + * Generates a token for retrieving/deleting recordings from a room and returns the JWT token in the format "Bearer " */ -export const generateRecordingTokenCookie = async (roomId: string, secret: string) => { - // Generate the recording token - const response = await generateRecordingToken(roomId, secret); +export const generateRecordingToken = async (roomId: string, secret: string) => { + const response = await generateRecordingTokenRequest(roomId, secret); expect(response.status).toBe(200); - // Return the recording token cookie - const cookies = response.headers['set-cookie'] as unknown as string[]; - const recordingTokenCookie = cookies.find((cookie) => - cookie.startsWith(`${INTERNAL_CONFIG.RECORDING_TOKEN_COOKIE_NAME}=`) - ) as string; - return recordingTokenCookie; + const authTransportMode = await getAuthTransportMode(); + + // Return token in header or cookie based on transport mode + if (authTransportMode === AuthTransportMode.COOKIE) { + const cookie = extractCookieFromHeaders(response, INTERNAL_CONFIG.RECORDING_TOKEN_COOKIE_NAME); + return cookie!; + } + + expect(response.body).toHaveProperty('token'); + return `Bearer ${response.body.token}`; }; -export const startRecording = async (roomId: string, moderatorCookie = '') => { +export const startRecording = async (roomId: string, moderatorToken: string) => { checkAppIsRunning(); return await request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings`) - .set('Cookie', moderatorCookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, moderatorToken), moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) .send({ roomId }); }; -export const stopRecording = async (recordingId: string, moderatorCookie = '') => { +export const stopRecording = async (recordingId: string, moderatorToken: string) => { checkAppIsRunning(); const response = await request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings/${recordingId}/stop`) - .set('Cookie', moderatorCookie) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, moderatorToken), moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) .send(); await sleep('2.5s'); @@ -670,15 +716,15 @@ export const deleteRecording = async (recordingId: string) => { .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY); }; -export const bulkDeleteRecordings = async (recordingIds: any[], recordingTokenCookie?: string): Promise => { +export const bulkDeleteRecordings = async (recordingIds: any[], recordingToken?: string): Promise => { checkAppIsRunning(); const req = request(app) .delete(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings`) .query({ recordingIds: recordingIds.join(',') }); - if (recordingTokenCookie) { - req.set('Cookie', recordingTokenCookie); + if (recordingToken) { + req.set(selectHeaderBasedOnToken(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken), recordingToken); } else { req.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY); } @@ -689,7 +735,7 @@ export const bulkDeleteRecordings = async (recordingIds: any[], recordingTokenCo export const downloadRecordings = async ( recordingIds: string[], asBuffer = true, - recordingTokenCookie?: string + recordingToken?: string ): Promise => { checkAppIsRunning(); @@ -697,8 +743,8 @@ export const downloadRecordings = async ( .get(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/download`) .query({ recordingIds: recordingIds.join(',') }); - if (recordingTokenCookie) { - req.set('Cookie', recordingTokenCookie); + if (recordingToken) { + req.set(selectHeaderBasedOnToken(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken), recordingToken); } else { req.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY); } @@ -714,7 +760,7 @@ export const downloadRecordings = async ( return await req; }; -export const stopAllRecordings = async (moderatorCookie: string) => { +export const stopAllRecordings = async (moderatorToken: string) => { checkAppIsRunning(); const response = await getAllRecordings(); @@ -731,8 +777,8 @@ export const stopAllRecordings = async (moderatorCookie: string) => { const tasks = recordingIds.map((recordingId: string) => request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings/${recordingId}/stop`) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, moderatorToken), moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) - .set('Cookie', moderatorCookie) .send() ); const results = await Promise.all(tasks); @@ -753,10 +799,12 @@ export const getAllRecordings = async (query: Record = {}) => { .query(query); }; -export const getAllRecordingsFromRoom = async (recordingTokenCookie: string) => { +export const getAllRecordingsFromRoom = async (recordingToken: string) => { checkAppIsRunning(); - return await request(app).get(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings`).set('Cookie', recordingTokenCookie); + return await request(app) + .get(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings`) + .set(selectHeaderBasedOnToken(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken), recordingToken); }; export const deleteAllRecordings = async () => { diff --git a/backend/tests/helpers/test-scenarios.ts b/backend/tests/helpers/test-scenarios.ts index a92af73..eef52a7 100644 --- a/backend/tests/helpers/test-scenarios.ts +++ b/backend/tests/helpers/test-scenarios.ts @@ -6,7 +6,7 @@ import { MeetRoom, MeetRoomConfig } from '../../src/typings/ce'; import { expectValidStartRecordingResponse } from './assertion-helpers'; import { createRoom, - generateParticipantTokenCookie, + generateParticipantToken, joinFakeParticipant, sleep, startRecording, @@ -18,9 +18,9 @@ let mockWebhookServer: http.Server; export interface RoomData { room: MeetRoom; moderatorSecret: string; - moderatorCookie: string; + moderatorToken: string; speakerSecret: string; - speakerCookie: string; + speakerToken: string; recordingId?: string; } @@ -36,7 +36,7 @@ export interface TestContext { * @param withParticipant Whether to join a fake participant in the room. * @param roomName Name of the room to create. * @param config Optional room config. - * @returns Room data including secrets and cookies. + * @returns Room data including secrets and tokens. */ export const setupSingleRoom = async ( withParticipant = false, @@ -48,11 +48,11 @@ export const setupSingleRoom = async ( config }); - // Extract the room secrets and generate participant tokens, saved as cookies + // Extract the room secrets and generate participant tokens const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(room); - const [moderatorCookie, speakerCookie] = await Promise.all([ - generateParticipantTokenCookie(room.roomId, moderatorSecret, 'MODERATOR'), - generateParticipantTokenCookie(room.roomId, speakerSecret, 'SPEAKER') + const [moderatorToken, speakerToken] = await Promise.all([ + generateParticipantToken(room.roomId, moderatorSecret, 'MODERATOR'), + generateParticipantToken(room.roomId, speakerSecret, 'SPEAKER') ]); // Join participant if needed @@ -63,9 +63,9 @@ export const setupSingleRoom = async ( return { room, moderatorSecret, - moderatorCookie, + moderatorToken, speakerSecret, - speakerCookie + speakerToken }; }; @@ -109,7 +109,7 @@ export const setupSingleRoomWithRecording = async ( stopDelay?: StringValue ): Promise => { const roomData = await setupSingleRoom(true, 'TEST_ROOM'); - const response = await startRecording(roomData.room.roomId, roomData.moderatorCookie); + const response = await startRecording(roomData.room.roomId, roomData.moderatorToken); expectValidStartRecordingResponse(response, roomData.room.roomId, roomData.room.roomName); roomData.recordingId = response.body.recordingId; @@ -119,7 +119,7 @@ export const setupSingleRoomWithRecording = async ( } if (stopRecordingCond) { - await stopRecording(roomData.recordingId!, roomData.moderatorCookie); + await stopRecording(roomData.recordingId!, roomData.moderatorToken); } return roomData; @@ -154,7 +154,7 @@ export const setupMultiRecordingsTestContext = async ( } // Send start recording request - const response = await startRecording(roomData.room.roomId, roomData.moderatorCookie); + const response = await startRecording(roomData.room.roomId, roomData.moderatorToken); expectValidStartRecordingResponse(response, roomData.room.roomId, roomData.room.roomName); // Store the recordingId in context @@ -171,7 +171,7 @@ export const setupMultiRecordingsTestContext = async ( // Stop recordings for the first numStops rooms const stopPromises = startedRooms.slice(0, numStops).map(async (roomData) => { if (roomData.recordingId) { - await stopRecording(roomData.recordingId, roomData.moderatorCookie); + await stopRecording(roomData.recordingId, roomData.moderatorToken); console.log(`Recording stopped for room ${roomData.room.roomId}`); return roomData.recordingId; } diff --git a/backend/tests/integration/api/global-config/security.test.ts b/backend/tests/integration/api/global-config/security.test.ts index fb317f1..2ec0ebd 100644 --- a/backend/tests/integration/api/global-config/security.test.ts +++ b/backend/tests/integration/api/global-config/security.test.ts @@ -10,7 +10,7 @@ const defaultConfig = { authMethod: { type: AuthType.SINGLE_USER }, - authTransportMode: AuthTransportMode.COOKIE, + authTransportMode: AuthTransportMode.HEADER, authModeToAccessRoom: AuthMode.NONE } }; @@ -36,7 +36,7 @@ describe('Security Config API Tests', () => { authMethod: { type: AuthType.SINGLE_USER }, - authTransportMode: AuthTransportMode.COOKIE, + authTransportMode: AuthTransportMode.HEADER, authModeToAccessRoom: AuthMode.ALL_USERS } }; @@ -108,7 +108,7 @@ describe('Security Config API Tests', () => { let response = await updateSecurityConfig({ authentication: { authMode: AuthMode.NONE, - authTransportMode: AuthTransportMode.COOKIE + authTransportMode: AuthTransportMode.HEADER } }); expectValidationError(response, 'authentication.authMethod', 'Required'); @@ -128,7 +128,7 @@ describe('Security Config API Tests', () => { authMethod: { type: AuthType.SINGLE_USER }, - authTransportMode: AuthTransportMode.COOKIE + authTransportMode: AuthTransportMode.HEADER } }); expectValidationError(response, 'authentication.authModeToAccessRoom', 'Required'); diff --git a/backend/tests/integration/api/meetings/end-meeting.test.ts b/backend/tests/integration/api/meetings/end-meeting.test.ts index ce8c52f..94ed08d 100644 --- a/backend/tests/integration/api/meetings/end-meeting.test.ts +++ b/backend/tests/integration/api/meetings/end-meeting.test.ts @@ -35,7 +35,7 @@ describe('Meetings API Tests', () => { expect(lkRoom.name).toBe(roomData.room.roomId); // End the meeting - let response = await endMeeting(roomData.room.roomId, roomData.moderatorCookie); + let response = await endMeeting(roomData.room.roomId, roomData.moderatorToken); expect(response.status).toBe(200); // Check if the LiveKit room has been removed @@ -62,7 +62,7 @@ describe('Meetings API Tests', () => { } // End the meeting - const response = await endMeeting(roomData.room.roomId, roomData.moderatorCookie); + const response = await endMeeting(roomData.room.roomId, roomData.moderatorToken); expect(response.status).toBe(200); // Recreate the room with a participant @@ -74,7 +74,7 @@ describe('Meetings API Tests', () => { let response = await deleteRoom(roomData.room.roomId, { withMeeting: 'force' }); expect(response.status).toBe(200); - response = await endMeeting(roomData.room.roomId, roomData.moderatorCookie); + response = await endMeeting(roomData.room.roomId, roomData.moderatorToken); expect(response.status).toBe(404); }); }); diff --git a/backend/tests/integration/api/meetings/kick-participant.test.ts b/backend/tests/integration/api/meetings/kick-participant.test.ts index c0cedd5..e8a80ff 100644 --- a/backend/tests/integration/api/meetings/kick-participant.test.ts +++ b/backend/tests/integration/api/meetings/kick-participant.test.ts @@ -39,7 +39,7 @@ describe('Meetings API Tests', () => { expect(participant.identity).toBe(participantIdentity); // Delete the participant - const response = await kickParticipant(roomData.room.roomId, participantIdentity, roomData.moderatorCookie); + const response = await kickParticipant(roomData.room.roomId, participantIdentity, roomData.moderatorToken); expect(response.status).toBe(200); // Check if the participant has been removed from LiveKit @@ -54,7 +54,7 @@ describe('Meetings API Tests', () => { const response = await kickParticipant( roomData.room.roomId, 'NON_EXISTENT_PARTICIPANT', - roomData.moderatorCookie + roomData.moderatorToken ); expect(response.status).toBe(404); expect(response.body.error).toBe('Participant Error'); @@ -65,7 +65,7 @@ describe('Meetings API Tests', () => { let response = await deleteRoom(roomData.room.roomId, { withMeeting: 'force' }); expect(response.status).toBe(200); - response = await kickParticipant(roomData.room.roomId, participantIdentity, roomData.moderatorCookie); + response = await kickParticipant(roomData.room.roomId, participantIdentity, roomData.moderatorToken); expect(response.status).toBe(404); expect(response.body.error).toBe('Room Error'); }); diff --git a/backend/tests/integration/api/meetings/update-participant.test.ts b/backend/tests/integration/api/meetings/update-participant.test.ts index b4211e6..b9efde5 100644 --- a/backend/tests/integration/api/meetings/update-participant.test.ts +++ b/backend/tests/integration/api/meetings/update-participant.test.ts @@ -2,6 +2,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it, jest } from '@je import { container } from '../../../../src/config/index.js'; import { LIVEKIT_URL } from '../../../../src/environment.js'; import { FrontendEventService, LiveKitService } from '../../../../src/services/index.js'; +import { MeetSignalType } from '../../../../src/typings/ce/event.model.js'; import { MeetTokenMetadata, ParticipantRole } from '../../../../src/typings/ce/index.js'; import { getPermissions } from '../../../helpers/assertion-helpers.js'; import { @@ -13,7 +14,6 @@ import { updateParticipantMetadata } from '../../../helpers/request-helpers.js'; import { RoomData, setupSingleRoom } from '../../../helpers/test-scenarios.js'; -import { MeetSignalType } from '../../../../src/typings/ce/event.model.js'; const participantIdentity = 'TEST_PARTICIPANT'; @@ -60,7 +60,7 @@ describe('Meetings API Tests', () => { roomData.room.roomId, participantIdentity, ParticipantRole.MODERATOR, - roomData.moderatorCookie + roomData.moderatorToken ); expect(response.status).toBe(200); @@ -76,7 +76,8 @@ describe('Meetings API Tests', () => { // Verify sendSignal method has been called twice expect(sendSignalSpy).toHaveBeenCalledTimes(2); - expect(sendSignalSpy).toHaveBeenNthCalledWith(1, + expect(sendSignalSpy).toHaveBeenNthCalledWith( + 1, roomData.room.roomId, { roomId: roomData.room.roomId, @@ -91,7 +92,8 @@ describe('Meetings API Tests', () => { } ); - expect(sendSignalSpy).toHaveBeenNthCalledWith(2, + expect(sendSignalSpy).toHaveBeenNthCalledWith( + 2, roomData.room.roomId, { roomId: roomData.room.roomId, @@ -114,7 +116,7 @@ describe('Meetings API Tests', () => { roomData.room.roomId, participantIdentity, ParticipantRole.SPEAKER, - roomData.moderatorCookie + roomData.moderatorToken ); expect(response.status).toBe(200); @@ -133,7 +135,7 @@ describe('Meetings API Tests', () => { roomData.room.roomId, 'NON_EXISTENT_PARTICIPANT', ParticipantRole.MODERATOR, - roomData.moderatorCookie + roomData.moderatorToken ); expect(response.status).toBe(404); expect(response.body.error).toBe('Participant Error'); @@ -148,7 +150,7 @@ describe('Meetings API Tests', () => { roomData.room.roomId, participantIdentity, ParticipantRole.MODERATOR, - roomData.moderatorCookie + roomData.moderatorToken ); expect(response.status).toBe(404); expect(response.body.error).toBe('Room Error'); diff --git a/backend/tests/integration/api/participants/generate-token.test.ts b/backend/tests/integration/api/participants/generate-token.test.ts index e9f72ff..a48e33c 100644 --- a/backend/tests/integration/api/participants/generate-token.test.ts +++ b/backend/tests/integration/api/participants/generate-token.test.ts @@ -1,12 +1,16 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; +import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; +import { AuthTransportMode } from '../../../../src/typings/ce/index.js'; import { ParticipantRole } from '../../../../src/typings/ce/participant.js'; import { expectValidationError, expectValidParticipantTokenResponse } from '../../../helpers/assertion-helpers.js'; import { + changeAuthTransportMode, deleteAllRooms, disconnectFakeParticipants, endMeeting, + extractCookieFromHeaders, generateParticipantToken, - generateParticipantTokenCookie, + generateParticipantTokenRequest, startTestServer, updateRoomStatus } from '../../../helpers/request-helpers.js'; @@ -33,7 +37,7 @@ describe('Participant API Tests', () => { describe('Generate Participant Token Tests', () => { it('should generate a participant token without join permissions when not specifying participant name', async () => { - const response = await generateParticipantToken({ + const response = await generateParticipantTokenRequest({ roomId: roomData.room.roomId, secret: roomData.moderatorSecret }); @@ -41,7 +45,7 @@ describe('Participant API Tests', () => { }); it('should generate a participant token with moderator permissions when using the moderator secret', async () => { - const response = await generateParticipantToken({ + const response = await generateParticipantTokenRequest({ roomId: roomData.room.roomId, secret: roomData.moderatorSecret, participantName @@ -55,7 +59,7 @@ describe('Participant API Tests', () => { }); it('should generate a participant token with speaker permissions when using the speaker secret', async () => { - const response = await generateParticipantToken({ + const response = await generateParticipantTokenRequest({ roomId: roomData.room.roomId, secret: roomData.speakerSecret, participantName @@ -69,19 +73,22 @@ describe('Participant API Tests', () => { }); it(`should generate a participant token with both speaker and moderator permissions - when using the speaker secret after having a moderator token`, async () => { - const moderatorCookie = await generateParticipantTokenCookie( + when using the speaker secret after having a moderator token in cookie mode`, async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + const moderatorToken = await generateParticipantToken( roomData.room.roomId, roomData.moderatorSecret, `${participantName}_MODERATOR` ); - const speakerResponse = await generateParticipantToken( + const speakerResponse = await generateParticipantTokenRequest( { roomId: roomData.room.roomId, secret: roomData.speakerSecret, participantName: `${participantName}_SPEAKER` }, - moderatorCookie + moderatorToken ); expectValidParticipantTokenResponse( speakerResponse, @@ -91,11 +98,47 @@ describe('Participant API Tests', () => { undefined, [ParticipantRole.MODERATOR] ); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + + it('should generate a participant token and store it in a cookie when in cookie mode', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Generate the participant token + const response = await generateParticipantTokenRequest({ + roomId: roomData.room.roomId, + secret: roomData.moderatorSecret, + participantName + }); + expectValidParticipantTokenResponse( + response, + roomData.room.roomId, + ParticipantRole.MODERATOR, + participantName + ); + + // Check that the token is included in a cookie + const participantTokenCookie = extractCookieFromHeaders( + response, + INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME + ); + expect(participantTokenCookie).toBeDefined(); + expect(participantTokenCookie).toContain(response.body.token); + expect(participantTokenCookie).toContain('HttpOnly'); + expect(participantTokenCookie).toContain('SameSite=None'); + expect(participantTokenCookie).toContain('Secure'); + expect(participantTokenCookie).toContain('Path=/'); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should success when participant already exists in the room', async () => { roomData = await setupSingleRoom(true); - let response = await generateParticipantToken({ + let response = await generateParticipantTokenRequest({ roomId: roomData.room.roomId, secret: roomData.moderatorSecret, participantName @@ -109,7 +152,7 @@ describe('Participant API Tests', () => { participantName ); - response = await generateParticipantToken({ + response = await generateParticipantTokenRequest({ roomId: roomData.room.roomId, secret: roomData.moderatorSecret, participantName @@ -128,9 +171,9 @@ describe('Participant API Tests', () => { }); it('should fail with 409 when room is closed', async () => { - await endMeeting(roomData.room.roomId, roomData.moderatorCookie); + await endMeeting(roomData.room.roomId, roomData.moderatorToken); await updateRoomStatus(roomData.room.roomId, 'closed'); - const response = await generateParticipantToken({ + const response = await generateParticipantTokenRequest({ roomId: roomData.room.roomId, secret: roomData.moderatorSecret, participantName @@ -139,7 +182,7 @@ describe('Participant API Tests', () => { }); it('should fail with 404 when room does not exist', async () => { - const response = await generateParticipantToken({ + const response = await generateParticipantTokenRequest({ roomId: 'non_existent_room', secret: roomData.moderatorSecret, participantName @@ -148,7 +191,7 @@ describe('Participant API Tests', () => { }); it('should fail with 400 when secret is invalid', async () => { - const response = await generateParticipantToken({ + const response = await generateParticipantTokenRequest({ roomId: roomData.room.roomId, secret: 'invalid_secret', participantName @@ -159,7 +202,7 @@ describe('Participant API Tests', () => { describe('Generate Participant Token Validation Tests', () => { it('should fail when roomId is not provided', async () => { - const response = await generateParticipantToken({ + const response = await generateParticipantTokenRequest({ secret: roomData.moderatorSecret, participantName }); @@ -167,7 +210,7 @@ describe('Participant API Tests', () => { }); it('should fail when secret is not provided', async () => { - const response = await generateParticipantToken({ + const response = await generateParticipantTokenRequest({ roomId: roomData.room.roomId, participantName }); @@ -175,7 +218,7 @@ describe('Participant API Tests', () => { }); it('should fail when secret is empty', async () => { - const response = await generateParticipantToken({ + const response = await generateParticipantTokenRequest({ roomId: roomData.room.roomId, secret: '', participantName diff --git a/backend/tests/integration/api/participants/refresh-token.test.ts b/backend/tests/integration/api/participants/refresh-token.test.ts index 9e65b4a..de097b1 100644 --- a/backend/tests/integration/api/participants/refresh-token.test.ts +++ b/backend/tests/integration/api/participants/refresh-token.test.ts @@ -1,10 +1,13 @@ import { afterAll, beforeAll, describe, expect, it } from '@jest/globals'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; +import { AuthTransportMode } from '../../../../src/typings/ce/index.js'; import { ParticipantRole } from '../../../../src/typings/ce/participant.js'; import { expectValidationError, expectValidParticipantTokenResponse } from '../../../helpers/assertion-helpers.js'; import { + changeAuthTransportMode, deleteAllRooms, disconnectFakeParticipants, + extractCookieFromHeaders, refreshParticipantToken, sleep, startTestServer @@ -44,7 +47,7 @@ describe('Participant API Tests', () => { participantName, participantIdentity: participantName }, - roomData.moderatorCookie + roomData.moderatorToken ); expectValidParticipantTokenResponse( response, @@ -63,7 +66,7 @@ describe('Participant API Tests', () => { participantName, participantIdentity: participantName }, - roomData.speakerCookie + roomData.speakerToken ); expectValidParticipantTokenResponse( response, @@ -74,6 +77,47 @@ describe('Participant API Tests', () => { ); }); + it('should refresh participant token and store it in a cookie when in cookie mode', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Create a new room to obtain participant token in cookie mode + const newRoomData = await setupSingleRoom(true); + + // Refresh the participant token + const response = await refreshParticipantToken( + { + roomId: newRoomData.room.roomId, + secret: newRoomData.moderatorSecret, + participantName, + participantIdentity: participantName + }, + newRoomData.moderatorToken + ); + expectValidParticipantTokenResponse( + response, + newRoomData.room.roomId, + ParticipantRole.MODERATOR, + participantName, + participantName + ); + + // Check that the token is included in a cookie + const participantTokenCookie = extractCookieFromHeaders( + response, + INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME + ); + expect(participantTokenCookie).toBeDefined(); + expect(participantTokenCookie).toContain(response.body.token); + expect(participantTokenCookie).toContain('HttpOnly'); + expect(participantTokenCookie).toContain('SameSite=None'); + expect(participantTokenCookie).toContain('Secure'); + expect(participantTokenCookie).toContain('Path=/'); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should fail with 400 when secret is invalid', async () => { const response = await refreshParticipantToken( { @@ -82,7 +126,7 @@ describe('Participant API Tests', () => { participantName, participantIdentity: participantName }, - roomData.moderatorCookie + roomData.moderatorToken ); expect(response.status).toBe(400); }); @@ -108,7 +152,7 @@ describe('Participant API Tests', () => { secret: 'invalid_secret', participantName }, - roomData.moderatorCookie + roomData.moderatorToken ); expect(response.status).toBe(400); }); @@ -122,7 +166,7 @@ describe('Participant API Tests', () => { participantName, participantIdentity: participantName }, - roomData.moderatorCookie + roomData.moderatorToken ); expect(response.status).toBe(404); }); @@ -135,7 +179,7 @@ describe('Participant API Tests', () => { participantName, participantIdentity: participantName }, - roomData.moderatorCookie + roomData.moderatorToken ); expect(response.status).toBe(404); }); @@ -149,7 +193,7 @@ describe('Participant API Tests', () => { participantName, participantIdentity: participantName }, - roomData.moderatorCookie + roomData.moderatorToken ); expectValidationError(response, 'roomId', 'Required'); }); @@ -161,7 +205,7 @@ describe('Participant API Tests', () => { participantName, participantIdentity: participantName }, - roomData.moderatorCookie + roomData.moderatorToken ); expectValidationError(response, 'secret', 'Required'); }); @@ -174,7 +218,7 @@ describe('Participant API Tests', () => { participantName, participantIdentity: participantName }, - roomData.moderatorCookie + roomData.moderatorToken ); expectValidationError(response, 'secret', 'Secret is required'); }); diff --git a/backend/tests/integration/api/recordings/bulk-delete-recording.test.ts b/backend/tests/integration/api/recordings/bulk-delete-recording.test.ts index 104f110..b263883 100644 --- a/backend/tests/integration/api/recordings/bulk-delete-recording.test.ts +++ b/backend/tests/integration/api/recordings/bulk-delete-recording.test.ts @@ -7,7 +7,7 @@ import { deleteAllRecordings, deleteAllRooms, disconnectFakeParticipants, - generateRecordingTokenCookie, + generateRecordingToken, getAllRecordings, startRecording, startTestServer, @@ -73,7 +73,7 @@ describe('Recording API Tests', () => { ] }); - await stopRecording(activeRecordingId!, activeRecordingRoom!.moderatorCookie); + await stopRecording(activeRecordingId!, activeRecordingRoom!.moderatorToken); deleteResponse = await bulkDeleteRecordings([activeRecordingId]); @@ -102,7 +102,7 @@ describe('Recording API Tests', () => { await Promise.all( recordingIds.map((id, index) => { - return stopRecording(id!, testContext.getRoomByIndex(index)!.moderatorCookie); + return stopRecording(id!, testContext.getRoomByIndex(index)!.moderatorToken); }) ); }); @@ -122,14 +122,14 @@ describe('Recording API Tests', () => { const recordingId = roomData.recordingId; // Generate a recording token for the room - const recordingCookie = await generateRecordingTokenCookie(roomId, roomData.moderatorSecret); + const recordingToken = await generateRecordingToken(roomId, roomData.moderatorSecret); // Create another room and start a recording const otherRoomData = await setupSingleRoomWithRecording(true); const otherRecordingId = otherRoomData.recordingId; // Intenta eliminar ambas grabaciones usando el token de la primera sala - const deleteResponse = await bulkDeleteRecordings([recordingId, otherRecordingId], recordingCookie); + const deleteResponse = await bulkDeleteRecordings([recordingId, otherRecordingId], recordingToken); expect(deleteResponse.status).toBe(400); expect(deleteResponse.body).toEqual({ @@ -181,7 +181,7 @@ describe('Recording API Tests', () => { const meetStorageService = container.get(MeetStorageService); // Create two recordings in the same room const testContext = await setupMultiRecordingsTestContext(1, 1, 1); - const { room, recordingId: firstRecordingId, moderatorCookie } = testContext.rooms[0]; + const { room, recordingId: firstRecordingId, moderatorToken } = testContext.rooms[0]; let roomMetadata = await meetStorageService.getArchivedRoomMetadata(room.roomId); @@ -195,11 +195,11 @@ describe('Recording API Tests', () => { expect(roomMetadata!.moderatorUrl).toContain(room.roomId); expect(roomMetadata!.speakerUrl).toContain(room.roomId); - const response = await startRecording(room.roomId, moderatorCookie); + const response = await startRecording(room.roomId, moderatorToken); expectValidStartRecordingResponse(response, room.roomId, room.roomName); const secondRecordingId = response.body.recordingId; - await stopRecording(secondRecordingId, moderatorCookie); + await stopRecording(secondRecordingId, moderatorToken); // Delete first recording - room metadata should remain const bulkResponse = await bulkDeleteRecordings([firstRecordingId, secondRecordingId]); expect(bulkResponse.status).toBe(200); diff --git a/backend/tests/integration/api/recordings/delete-recording.test.ts b/backend/tests/integration/api/recordings/delete-recording.test.ts index 6ac613a..18c4e7f 100644 --- a/backend/tests/integration/api/recordings/delete-recording.test.ts +++ b/backend/tests/integration/api/recordings/delete-recording.test.ts @@ -27,17 +27,17 @@ describe('Recording API Tests', () => { }); describe('Delete Recording Tests', () => { - let room: MeetRoom, recordingId: string, moderatorCookie: string; + let room: MeetRoom, recordingId: string, moderatorToken: string; beforeEach(async () => { const testContext = await setupMultiRecordingsTestContext(1, 1, 1); const roomData = testContext.getRoomByIndex(0)!; - ({ room, recordingId = '', moderatorCookie } = roomData); + ({ room, recordingId = '', moderatorToken } = roomData); }); afterAll(async () => { - await stopAllRecordings(moderatorCookie); + await stopAllRecordings(moderatorToken); await Promise.all([deleteAllRecordings(), deleteAllRooms()]); }); @@ -77,11 +77,11 @@ describe('Recording API Tests', () => { expect(roomMetadata!.speakerUrl).toContain(room.roomId); // Generate a new recording - const response = await startRecording(room.roomId, moderatorCookie); + const response = await startRecording(room.roomId, moderatorToken); console.log('Start recording response:', response.body); expectValidStartRecordingResponse(response, room.roomId, room.roomName); const secondRecordingId = response.body.recordingId; - await stopRecording(secondRecordingId, moderatorCookie); + await stopRecording(secondRecordingId, moderatorToken); // Check that the room metadata still exists after deleteing the first recording let deleteResponse = await deleteRecording(recordingId!); @@ -104,17 +104,17 @@ describe('Recording API Tests', () => { }); describe('Delete Recording Validation', () => { - let room: MeetRoom, recordingId: string, moderatorCookie: string; + let room: MeetRoom, recordingId: string, moderatorToken: string; beforeAll(async () => { await deleteAllRecordings(); const testContext = await setupMultiRecordingsTestContext(1, 1, 1); const roomData = testContext.getRoomByIndex(0)!; - ({ room, recordingId = '', moderatorCookie } = roomData); + ({ room, recordingId = '', moderatorToken } = roomData); }); afterAll(async () => { - await stopAllRecordings(moderatorCookie); + await stopAllRecordings(moderatorToken); await Promise.all([deleteAllRecordings(), deleteAllRooms()]); }); it('should fail when recordingId has incorrect format', async () => { @@ -154,13 +154,13 @@ describe('Recording API Tests', () => { it('should return 409 when attempting to delete an active recording', async () => { const testContext = await setupMultiRecordingsTestContext(1, 1, 0); - const { recordingId: activeRecordingId = '', moderatorCookie } = testContext.rooms[0]; + const { recordingId: activeRecordingId = '', moderatorToken } = testContext.rooms[0]; // Attempt to delete the active recording let deleteResponse = await deleteRecording(activeRecordingId); expect(deleteResponse.status).toBe(409); - await stopRecording(activeRecordingId, moderatorCookie); + await stopRecording(activeRecordingId, moderatorToken); // Attempt to delete the recording again deleteResponse = await deleteRecording(activeRecordingId); expect(deleteResponse.status).toBe(200); diff --git a/backend/tests/integration/api/recordings/download-recordings.test.ts b/backend/tests/integration/api/recordings/download-recordings.test.ts index 1b18c61..57a5587 100644 --- a/backend/tests/integration/api/recordings/download-recordings.test.ts +++ b/backend/tests/integration/api/recordings/download-recordings.test.ts @@ -7,7 +7,7 @@ import { deleteAllRooms, disconnectFakeParticipants, downloadRecordings, - generateRecordingTokenCookie, + generateRecordingToken, startTestServer } from '../../../helpers/request-helpers'; import { setupMultiRecordingsTestContext, setupSingleRoomWithRecording } from '../../../helpers/test-scenarios'; @@ -59,12 +59,12 @@ describe('Recording API Tests', () => { const roomData = await setupSingleRoomWithRecording(true); const roomId = roomData.room.roomId; const recordingId = roomData.recordingId!; - const recordingCookie = await generateRecordingTokenCookie(roomId, roomData.moderatorSecret); + const recordingToken = await generateRecordingToken(roomId, roomData.moderatorSecret); const otherRoomData = await setupSingleRoomWithRecording(true); const otherRecordingId = otherRoomData.recordingId!; - const res = await downloadRecordings([recordingId, otherRecordingId], true, recordingCookie); + const res = await downloadRecordings([recordingId, otherRecordingId], true, recordingToken); expect(res.status).toBe(200); const entries = await getZipEntries(res.body); @@ -75,12 +75,12 @@ describe('Recording API Tests', () => { it('should return an error if none of the recordings belong to the room in the token', async () => { const roomData = await setupSingleRoomWithRecording(true); const roomId = roomData.room.roomId; - const recordingCookie = await generateRecordingTokenCookie(roomId, roomData.moderatorSecret); + const recordingToken = await generateRecordingToken(roomId, roomData.moderatorSecret); const otherRoomData = await setupSingleRoomWithRecording(true); const otherRecordingId = otherRoomData.recordingId!; - const res = await downloadRecordings([otherRecordingId], false, recordingCookie); + const res = await downloadRecordings([otherRecordingId], false, recordingToken); expect(res.status).toBe(400); expect(res.body).toHaveProperty('error'); diff --git a/backend/tests/integration/api/recordings/get-media-recording.test.ts b/backend/tests/integration/api/recordings/get-media-recording.test.ts index 0c617e4..2ee585e 100644 --- a/backend/tests/integration/api/recordings/get-media-recording.test.ts +++ b/backend/tests/integration/api/recordings/get-media-recording.test.ts @@ -167,7 +167,7 @@ describe('Recording API Tests', () => { it('should return a 409 when the recording is in progress', async () => { const testContext = await setupMultiRecordingsTestContext(1, 1, 0); - const { recordingId: activeRecordingId = '', moderatorCookie } = testContext.rooms[0]; + const { recordingId: activeRecordingId = '', moderatorToken } = testContext.rooms[0]; // Attempt to get the media of an active recording const response = await getRecordingMedia(activeRecordingId); @@ -176,7 +176,7 @@ describe('Recording API Tests', () => { expect(response.body).toHaveProperty('message'); expect(response.body.message).toContain(`Recording '${activeRecordingId}' is not stopped yet`); - await stopRecording(activeRecordingId, moderatorCookie); + await stopRecording(activeRecordingId, moderatorToken); }); it('should return 404 when recording not found', async () => { diff --git a/backend/tests/integration/api/recordings/get-recording.test.ts b/backend/tests/integration/api/recordings/get-recording.test.ts index 430d22a..c9efc2b 100644 --- a/backend/tests/integration/api/recordings/get-recording.test.ts +++ b/backend/tests/integration/api/recordings/get-recording.test.ts @@ -50,7 +50,7 @@ describe('Recording API Tests', () => { const { room: roomAux, recordingId: recordingIdAux = '', - moderatorCookie: moderatorCookieAux + moderatorToken: moderatorTokenAux } = contextAux.getRoomByIndex(0)!; const response = await getRecording(recordingIdAux); @@ -62,7 +62,7 @@ describe('Recording API Tests', () => { MeetRecordingStatus.ACTIVE ); - await stopAllRecordings(moderatorCookieAux); + await stopAllRecordings(moderatorTokenAux); }); it('should return 404 when recording does not exist', async () => { diff --git a/backend/tests/integration/api/recordings/get-recordings.test.ts b/backend/tests/integration/api/recordings/get-recordings.test.ts index 6872d09..2e127e4 100644 --- a/backend/tests/integration/api/recordings/get-recordings.test.ts +++ b/backend/tests/integration/api/recordings/get-recordings.test.ts @@ -10,7 +10,7 @@ import { deleteAllRecordings, deleteAllRooms, disconnectFakeParticipants, - generateRecordingTokenCookie, + generateRecordingToken, getAllRecordings, getAllRecordingsFromRoom, startTestServer @@ -63,12 +63,12 @@ describe('Recordings API Tests', () => { const roomId = roomData.room.roomId; // Generate a recording token for the room - const recordingCookie = await generateRecordingTokenCookie(roomId, roomData.speakerSecret); + const recordingToken = await generateRecordingToken(roomId, roomData.speakerSecret); // Create a new room and start a recording roomData = await setupSingleRoomWithRecording(true); - const response = await getAllRecordingsFromRoom(recordingCookie); + const response = await getAllRecordingsFromRoom(recordingToken); expectSuccessListRecordingResponse(response, 1, false, false); expect(response.body.recordings[0].roomId).toBe(roomId); }); diff --git a/backend/tests/integration/api/recordings/race-conditions.test.ts b/backend/tests/integration/api/recordings/race-conditions.test.ts index 817cd4c..f7fb945 100644 --- a/backend/tests/integration/api/recordings/race-conditions.test.ts +++ b/backend/tests/integration/api/recordings/race-conditions.test.ts @@ -41,10 +41,10 @@ describe('Recording API Race Conditions Tests', () => { }); afterEach(async () => { - const moderatorCookie = context?.getRoomByIndex(0)?.moderatorCookie; + const moderatorToken = context?.getRoomByIndex(0)?.moderatorToken; - if (moderatorCookie) { - await stopAllRecordings(moderatorCookie); + if (moderatorToken) { + await stopAllRecordings(moderatorToken); } eventController.reset(); @@ -67,7 +67,7 @@ describe('Recording API Race Conditions Tests', () => { try { // Attempt to start recording - const result = await startRecording(roomData.room.roomId, roomData.moderatorCookie); + const result = await startRecording(roomData.room.roomId, roomData.moderatorToken); expect(eventServiceOffSpy).toHaveBeenCalledWith( DistributedEventType.RECORDING_ACTIVE, expect.any(Function) @@ -122,7 +122,7 @@ describe('Recording API Race Conditions Tests', () => { try { // Start recording with a short timeout - const result = await startRecording(roomData.room.roomId, roomData.moderatorCookie); + const result = await startRecording(roomData.room.roomId, roomData.moderatorToken); expect(eventServiceOffSpy).toHaveBeenCalledWith( DistributedEventType.RECORDING_ACTIVE, @@ -182,7 +182,7 @@ describe('Recording API Race Conditions Tests', () => { try { // Start recording in room1 (should timeout) - const rec1 = await startRecording(room1.room.roomId, room1.moderatorCookie); + const rec1 = await startRecording(room1.room.roomId, room1.moderatorToken); expect(rec1.status).toBe(503); setInternalConfig({ @@ -190,18 +190,18 @@ describe('Recording API Race Conditions Tests', () => { }); // ✅ EXPECTED BEHAVIOR: System should remain stable // Recording in different room should work normally - const rec2 = await startRecording(room2.room.roomId, room2.moderatorCookie); + const rec2 = await startRecording(room2.room.roomId, room2.moderatorToken); expect(rec2.status).toBe(201); expectValidStartRecordingResponse(rec2, room2.room.roomId, room2.room.roomName); - let response = await stopRecording(rec2.body.recordingId!, room2.moderatorCookie); + let response = await stopRecording(rec2.body.recordingId!, room2.moderatorToken); expectValidStopRecordingResponse(response, rec2.body.recordingId!, room2.room.roomId, room2.room.roomName); // ✅ EXPECTED BEHAVIOR: After timeout cleanup, room1 should be available again - const rec3 = await startRecording(room1.room.roomId, room1.moderatorCookie); + const rec3 = await startRecording(room1.room.roomId, room1.moderatorToken); expect(rec3.status).toBe(201); expectValidStartRecordingResponse(rec3, room1.room.roomId, room1.room.roomName); - response = await stopRecording(rec3.body.recordingId!, room1.moderatorCookie); + response = await stopRecording(rec3.body.recordingId!, room1.moderatorToken); expectValidStopRecordingResponse(response, rec3.body.recordingId!, room1.room.roomId, room1.room.roomName); } finally { startRoomCompositeSpy.mockRestore(); @@ -226,7 +226,7 @@ describe('Recording API Race Conditions Tests', () => { try { // Start recordings in all rooms simultaneously (all should timeout) const results = await Promise.all( - rooms.map((room) => startRecording(room.room.roomId, room.moderatorCookie)) + rooms.map((room) => startRecording(room.room.roomId, room.moderatorToken)) ); // All should timeout @@ -241,14 +241,14 @@ describe('Recording API Race Conditions Tests', () => { // ✅ EXPECTED BEHAVIOR: After timeouts, all rooms should be available again const retryResults = await Promise.all( - rooms.map((room) => startRecording(room.room.roomId, room.moderatorCookie)) + rooms.map((room) => startRecording(room.room.roomId, room.moderatorToken)) ); for (const startResult of retryResults) { expect(startResult.status).toBe(201); const room = rooms.find((r) => r.room.roomId === startResult.body.roomId)!; expectValidStartRecordingResponse(startResult, room.room.roomId, room.room.roomName); - const stopResult = await stopRecording(startResult.body.recordingId!, room.moderatorCookie); + const stopResult = await stopRecording(startResult.body.recordingId!, room.moderatorToken); expectValidStopRecordingResponse( stopResult, startResult.body.recordingId!, @@ -272,18 +272,18 @@ describe('Recording API Race Conditions Tests', () => { eventController.initialize(); eventController.pauseEventsForRoom(roomDataA!.room.roomId); - const recordingPromiseA = startRecording(roomDataA!.room.roomId, roomDataA!.moderatorCookie); + const recordingPromiseA = startRecording(roomDataA!.room.roomId, roomDataA!.moderatorToken); // Brief delay to ensure both recordings start in the right order await sleep('1s'); // Step 2: Start recording in roomB (this will complete quickly) - const recordingResponseB = await startRecording(roomDataB!.room.roomId, roomDataB!.moderatorCookie); + const recordingResponseB = await startRecording(roomDataB!.room.roomId, roomDataB!.moderatorToken); expectValidStartRecordingResponse(recordingResponseB, roomDataB!.room.roomId, roomDataB!.room.roomName); const recordingIdB = recordingResponseB.body.recordingId; // Step 3: Stop recording in roomB while roomA is still waiting for its event - const stopResponseB = await stopRecording(recordingIdB, roomDataB!.moderatorCookie); + const stopResponseB = await stopRecording(recordingIdB, roomDataB!.moderatorToken); expectValidStopRecordingResponse(stopResponseB, recordingIdB, roomDataB!.room.roomId, roomDataB!.room.roomName); eventController.releaseEventsForRoom(roomDataA!.room.roomId); @@ -303,7 +303,7 @@ describe('Recording API Race Conditions Tests', () => { const roomDataList = Array.from({ length: 5 }, (_, index) => context!.getRoomByIndex(index)!); const startResponses = await Promise.all( - roomDataList.map((roomData) => startRecording(roomData.room.roomId, roomData.moderatorCookie)) + roomDataList.map((roomData) => startRecording(roomData.room.roomId, roomData.moderatorToken)) ); startResponses.forEach((response, index) => { @@ -317,7 +317,7 @@ describe('Recording API Race Conditions Tests', () => { const recordingIds = startResponses.map((res) => res.body.recordingId); const stopResponses = await Promise.all( - recordingIds.map((recordingId, index) => stopRecording(recordingId, roomDataList[index].moderatorCookie)) + recordingIds.map((recordingId, index) => stopRecording(recordingId, roomDataList[index].moderatorToken)) ); stopResponses.forEach((response, index) => { @@ -334,14 +334,14 @@ describe('Recording API Race Conditions Tests', () => { context = await setupMultiRoomTestContext(2, true); const roomDataA = context.getRoomByIndex(0); const roomDataB = context.getRoomByIndex(1); - const responseA = await startRecording(roomDataA!.room.roomId, roomDataA!.moderatorCookie); - const responseB = await startRecording(roomDataB!.room.roomId, roomDataB!.moderatorCookie); + const responseA = await startRecording(roomDataA!.room.roomId, roomDataA!.moderatorToken); + const responseB = await startRecording(roomDataB!.room.roomId, roomDataB!.moderatorToken); const recordingIdA = responseA.body.recordingId; const recordingIdB = responseB.body.recordingId; const [stopResponseA, stopResponseB] = await Promise.all([ - stopRecording(recordingIdA, roomDataA!.moderatorCookie), - stopRecording(recordingIdB, roomDataB!.moderatorCookie) + stopRecording(recordingIdA, roomDataA!.moderatorToken), + stopRecording(recordingIdB, roomDataB!.moderatorToken) ]); expectValidStopRecordingResponse(stopResponseA, recordingIdA, roomDataA!.room.roomId, roomDataA!.room.roomName); expectValidStopRecordingResponse(stopResponseB, recordingIdB, roomDataB!.room.roomId, roomDataB!.room.roomName); @@ -352,8 +352,8 @@ describe('Recording API Race Conditions Tests', () => { const roomData = context.getRoomByIndex(0)!; const [firstRecordingResponse, secondRecordingResponse] = await Promise.all([ - startRecording(roomData.room.roomId, roomData.moderatorCookie), - startRecording(roomData.room.roomId, roomData.moderatorCookie) + startRecording(roomData.room.roomId, roomData.moderatorToken), + startRecording(roomData.room.roomId, roomData.moderatorToken) ]); console.log('First recording response:', firstRecordingResponse.body); @@ -368,7 +368,7 @@ describe('Recording API Race Conditions Tests', () => { if (firstRecordingResponse.status === 201) { expectValidStartRecordingResponse(firstRecordingResponse, roomData.room.roomId, roomData.room.roomName); // stop the first recording - const stopResponse = await stopRecording(firstRecordingResponse.body.recordingId, roomData.moderatorCookie); + const stopResponse = await stopRecording(firstRecordingResponse.body.recordingId, roomData.moderatorToken); expectValidStopRecordingResponse( stopResponse, firstRecordingResponse.body.recordingId, @@ -378,10 +378,7 @@ describe('Recording API Race Conditions Tests', () => { } else { expectValidStartRecordingResponse(secondRecordingResponse, roomData.room.roomId, roomData.room.roomName); // stop the second recording - const stopResponse = await stopRecording( - secondRecordingResponse.body.recordingId, - roomData.moderatorCookie - ); + const stopResponse = await stopRecording(secondRecordingResponse.body.recordingId, roomData.moderatorToken); expectValidStopRecordingResponse( stopResponse, secondRecordingResponse.body.recordingId, @@ -397,12 +394,12 @@ describe('Recording API Race Conditions Tests', () => { const gcSpy = jest.spyOn(recordingService as any, 'performRecordingLocksGarbageCollection'); - const startResponse = await startRecording(roomData.room.roomId, roomData.moderatorCookie); + const startResponse = await startRecording(roomData.room.roomId, roomData.moderatorToken); expectValidStartRecordingResponse(startResponse, roomData.room.roomId, roomData.room.roomName); const recordingId = startResponse.body.recordingId; // Execute garbage collection while stopping the recording - const stopPromise = stopRecording(recordingId, roomData.moderatorCookie); + const stopPromise = stopRecording(recordingId, roomData.moderatorToken); const gcPromise = recordingService['performRecordingLocksGarbageCollection'](); // Both operations should complete @@ -469,18 +466,18 @@ describe('Recording API Race Conditions Tests', () => { const room2 = context.getRoomByIndex(1)!; const room3 = context.getRoomByIndex(2)!; - const start1 = await startRecording(room1.room.roomId, room1.moderatorCookie); - const start2 = await startRecording(room2.room.roomId, room2.moderatorCookie); + const start1 = await startRecording(room1.room.roomId, room1.moderatorToken); + const start2 = await startRecording(room2.room.roomId, room2.moderatorToken); const recordingId1 = start1.body.recordingId; const recordingId2 = start2.body.recordingId; - await stopRecording(recordingId1, room1.moderatorCookie); - await stopRecording(recordingId2, room2.moderatorCookie); + await stopRecording(recordingId1, room1.moderatorToken); + await stopRecording(recordingId2, room2.moderatorToken); // Bulk delete the recordings while starting a new one const bulkDeletePromise = bulkDeleteRecordings([recordingId1, recordingId2]); - const startNewRecordingPromise = startRecording(room3.room.roomId, room3.moderatorCookie); + const startNewRecordingPromise = startRecording(room3.room.roomId, room3.moderatorToken); // Both operations should complete successfully const [bulkDeleteResult, newRecordingResult] = await Promise.all([bulkDeletePromise, startNewRecordingPromise]); @@ -490,7 +487,7 @@ describe('Recording API Race Conditions Tests', () => { // Check that the new recording started successfully expectValidStartRecordingResponse(newRecordingResult, room3.room.roomId, room3.room.roomName); - const newStopResponse = await stopRecording(newRecordingResult.body.recordingId, room3.moderatorCookie); + const newStopResponse = await stopRecording(newRecordingResult.body.recordingId, room3.moderatorToken); expectValidStopRecordingResponse( newStopResponse, newRecordingResult.body.recordingId, diff --git a/backend/tests/integration/api/recordings/start-recording.test.ts b/backend/tests/integration/api/recordings/start-recording.test.ts index 6cbe31d..89fec07 100644 --- a/backend/tests/integration/api/recordings/start-recording.test.ts +++ b/backend/tests/integration/api/recordings/start-recording.test.ts @@ -23,7 +23,7 @@ import { setupMultiRoomTestContext, TestContext } from '../../../helpers/test-sc describe('Recording API Tests', () => { let context: TestContext | null = null; - let room: MeetRoom, moderatorCookie: string; + let room: MeetRoom, moderatorToken: string; beforeAll(async () => { startTestServer(); @@ -39,7 +39,7 @@ describe('Recording API Tests', () => { beforeAll(async () => { // Create a room and join a participant context = await setupMultiRoomTestContext(1, true); - ({ room, moderatorCookie } = context.getRoomByIndex(0)!); + ({ room, moderatorToken } = context.getRoomByIndex(0)!); }); afterAll(async () => { @@ -49,16 +49,16 @@ describe('Recording API Tests', () => { }); it('should return 201 with proper response and location header when recording starts successfully', async () => { - const response = await startRecording(room.roomId, moderatorCookie); + const response = await startRecording(room.roomId, moderatorToken); const recordingId = response.body.recordingId; expectValidStartRecordingResponse(response, room.roomId, room.roomName); - const stopResponse = await stopRecording(recordingId, moderatorCookie); + const stopResponse = await stopRecording(recordingId, moderatorToken); expectValidStopRecordingResponse(stopResponse, recordingId, room.roomId, room.roomName); }); it('should secrets and archived room files be created when recording starts', async () => { - const response = await startRecording(room.roomId, moderatorCookie); + const response = await startRecording(room.roomId, moderatorToken); const recordingId = response.body.recordingId; expectValidStartRecordingResponse(response, room.roomId, room.roomName); @@ -75,24 +75,24 @@ describe('Recording API Tests', () => { expect(archivedRoom?.speakerUrl).toBeDefined(); expect(archivedRoom?.config).toBeDefined(); - const secretsResponse = await stopRecording(recordingId, moderatorCookie); + const secretsResponse = await stopRecording(recordingId, moderatorToken); expectValidStopRecordingResponse(secretsResponse, recordingId, room.roomId, room.roomName); }); it('should successfully start recording, stop it, and start again (sequential operations)', async () => { - const firstStartResponse = await startRecording(room.roomId, moderatorCookie); + const firstStartResponse = await startRecording(room.roomId, moderatorToken); const firstRecordingId = firstStartResponse.body.recordingId; expectValidStartRecordingResponse(firstStartResponse, room.roomId, room.roomName); - const firstStopResponse = await stopRecording(firstRecordingId, moderatorCookie); + const firstStopResponse = await stopRecording(firstRecordingId, moderatorToken); expectValidStopRecordingResponse(firstStopResponse, firstRecordingId, room.roomId, room.roomName); - const secondStartResponse = await startRecording(room.roomId, moderatorCookie); + const secondStartResponse = await startRecording(room.roomId, moderatorToken); expectValidStartRecordingResponse(secondStartResponse, room.roomId, room.roomName); const secondRecordingId = secondStartResponse.body.recordingId; - const secondStopResponse = await stopRecording(secondRecordingId, moderatorCookie); + const secondStopResponse = await stopRecording(secondRecordingId, moderatorToken); expectValidStopRecordingResponse(secondStopResponse, secondRecordingId, room.roomId, room.roomName); }); @@ -102,8 +102,8 @@ describe('Recording API Tests', () => { const roomDataA = context.getRoomByIndex(0)!; const roomDataB = context.getRoomByIndex(1)!; - const firstResponse = await startRecording(roomDataA.room.roomId, roomDataA.moderatorCookie); - const secondResponse = await startRecording(roomDataB.room.roomId, roomDataB.moderatorCookie); + const firstResponse = await startRecording(roomDataA.room.roomId, roomDataA.moderatorToken); + const secondResponse = await startRecording(roomDataB.room.roomId, roomDataB.moderatorToken); expectValidStartRecordingResponse(firstResponse, roomDataA.room.roomId, roomDataA.room.roomName); expectValidStartRecordingResponse(secondResponse, roomDataB.room.roomId, roomDataB.room.roomName); @@ -112,8 +112,8 @@ describe('Recording API Tests', () => { const secondRecordingId = secondResponse.body.recordingId; const [firstStopResponse, secondStopResponse] = await Promise.all([ - stopRecording(firstRecordingId, roomDataA.moderatorCookie), - stopRecording(secondRecordingId, roomDataB.moderatorCookie) + stopRecording(firstRecordingId, roomDataA.moderatorToken), + stopRecording(secondRecordingId, roomDataB.moderatorToken) ]); expectValidStopRecordingResponse( firstStopResponse, @@ -134,16 +134,16 @@ describe('Recording API Tests', () => { beforeAll(async () => { // Create a room without participants context = await setupMultiRoomTestContext(1, false); - ({ room, moderatorCookie } = context.getRoomByIndex(0)!); + ({ room, moderatorToken } = context.getRoomByIndex(0)!); }); afterEach(async () => { await disconnectFakeParticipants(); - await stopAllRecordings(moderatorCookie); + await stopAllRecordings(moderatorToken); }); it('should accept valid roomId but reject with 409', async () => { - const response = await startRecording(room.roomId, moderatorCookie); + const response = await startRecording(room.roomId, moderatorToken); // Room exists but it has no participants expect(response.status).toBe(409); expect(response.body.message).toContain(`Room '${room.roomId}' has no participants`); @@ -151,7 +151,7 @@ describe('Recording API Tests', () => { it('should sanitize roomId and reject the request with 409 due to no participants', async () => { const malformedRoomId = ' . { }); it('should reject request with roomId that becomes empty after sanitization', async () => { - const response = await startRecording('!@#$%^&*()', moderatorCookie); + const response = await startRecording('!@#$%^&*()', moderatorToken); expectValidationError(response, 'roomId', 'cannot be empty after sanitization'); }); it('should reject request with non-string roomId', async () => { - const response = await startRecording(123 as unknown as string, moderatorCookie); + const response = await startRecording(123 as unknown as string, moderatorToken); expectValidationError(response, 'roomId', 'Expected string'); }); it('should reject request with very long roomId', async () => { const longRoomId = 'a'.repeat(101); - const response = await startRecording(longRoomId, moderatorCookie); + const response = await startRecording(longRoomId, moderatorToken); expectValidationError(response, 'roomId', 'cannot exceed 100 characters'); }); it('should handle room that does not exist', async () => { - const response = await startRecording('non-existing-room-id', moderatorCookie); + const response = await startRecording('non-existing-room-id', moderatorToken); const error = errorRoomNotFound('non-existing-room-id'); expect(response.status).toBe(404); expect(response.body).toEqual({ @@ -188,14 +188,14 @@ describe('Recording API Tests', () => { it('should return 409 when recording is already in progress', async () => { await joinFakeParticipant(room.roomId, 'fakeParticipantId'); - const firstResponse = await startRecording(room.roomId, moderatorCookie); + const firstResponse = await startRecording(room.roomId, moderatorToken); const recordingId = firstResponse.body.recordingId; expectValidStartRecordingResponse(firstResponse, room.roomId, room.roomName); - const secondResponse = await startRecording(room!.roomId, moderatorCookie); + const secondResponse = await startRecording(room!.roomId, moderatorToken); expect(secondResponse.status).toBe(409); expect(secondResponse.body.message).toContain('already'); - const stopResponse = await stopRecording(recordingId, moderatorCookie); + const stopResponse = await stopRecording(recordingId, moderatorToken); expectValidStopRecordingResponse(stopResponse, recordingId, room.roomId, room.roomName); }); @@ -204,7 +204,7 @@ describe('Recording API Tests', () => { RECORDING_STARTED_TIMEOUT: '1s' }); await joinFakeParticipant(room.roomId, 'fakeParticipantId'); - const response = await startRecording(room.roomId, moderatorCookie); + const response = await startRecording(room.roomId, moderatorToken); expect(response.status).toBe(503); expect(response.body.message).toContain('timed out while starting'); setInternalConfig({ diff --git a/backend/tests/integration/api/recordings/stop-recording.test.ts b/backend/tests/integration/api/recordings/stop-recording.test.ts index e42e8fe..0c09d21 100644 --- a/backend/tests/integration/api/recordings/stop-recording.test.ts +++ b/backend/tests/integration/api/recordings/stop-recording.test.ts @@ -14,7 +14,7 @@ import { setupMultiRoomTestContext, TestContext } from '../../../helpers/test-sc describe('Recording API Tests', () => { let context: TestContext | null = null; - let room: MeetRoom, moderatorCookie: string; + let room: MeetRoom, moderatorToken: string; beforeAll(async () => { startTestServer(); @@ -22,7 +22,7 @@ describe('Recording API Tests', () => { }); afterAll(async () => { - await stopAllRecordings(moderatorCookie); + await stopAllRecordings(moderatorToken); await disconnectFakeParticipants(); await Promise.all([deleteAllRooms(), deleteAllRecordings()]); }); @@ -32,13 +32,13 @@ describe('Recording API Tests', () => { beforeAll(async () => { // Create a room and join a participant context = await setupMultiRoomTestContext(1, true); - ({ room, moderatorCookie } = context.getRoomByIndex(0)!); - const response = await startRecording(room.roomId, moderatorCookie); + ({ room, moderatorToken } = context.getRoomByIndex(0)!); + const response = await startRecording(room.roomId, moderatorToken); recordingId = response.body.recordingId; }); it('should stop an active recording and return 202', async () => { - const response = await stopRecording(recordingId, moderatorCookie); + const response = await stopRecording(recordingId, moderatorToken); expectValidStopRecordingResponse(response, recordingId, room.roomId, room.roomName); }); @@ -46,18 +46,18 @@ describe('Recording API Tests', () => { const context = await setupMultiRoomTestContext(2, true); const roomDataA = context.getRoomByIndex(0); const roomDataB = context.getRoomByIndex(1); - const responseA = await startRecording(roomDataA!.room.roomId, roomDataA?.moderatorCookie); - const responseB = await startRecording(roomDataB!.room.roomId, roomDataB?.moderatorCookie); + const responseA = await startRecording(roomDataA!.room.roomId, roomDataA!.moderatorToken); + const responseB = await startRecording(roomDataB!.room.roomId, roomDataB!.moderatorToken); const recordingIdA = responseA.body.recordingId; const recordingIdB = responseB.body.recordingId; - const stopResponseA = await stopRecording(recordingIdA, roomDataA?.moderatorCookie); + const stopResponseA = await stopRecording(recordingIdA, roomDataA!.moderatorToken); expectValidStopRecordingResponse( stopResponseA, recordingIdA, roomDataA!.room.roomId, roomDataA!.room.roomName ); - const stopResponseB = await stopRecording(recordingIdB, roomDataB?.moderatorCookie); + const stopResponseB = await stopRecording(recordingIdB, roomDataB!.moderatorToken); expectValidStopRecordingResponse( stopResponseB, recordingIdB, @@ -68,7 +68,7 @@ describe('Recording API Tests', () => { describe('Stop Recording Validation failures', () => { it('should return 404 when recordingId does not exist', async () => { - const response = await stopRecording(`${room.roomId}--EG_123--444`, moderatorCookie); + const response = await stopRecording(`${room.roomId}--EG_123--444`, moderatorToken); expect(response.status).toBe(404); expect(response.body.error).toBe('Recording Error'); expect(response.body.message).toContain('not found'); @@ -76,17 +76,17 @@ describe('Recording API Tests', () => { it('should return 400 when recording is already stopped', async () => { // First stop the recording - await stopRecording(recordingId, moderatorCookie); + await stopRecording(recordingId, moderatorToken); // Try to stop it again - const response = await stopRecording(recordingId, moderatorCookie); + const response = await stopRecording(recordingId, moderatorToken); console.log('Response:', response.body); expectErrorResponse(response, 409, '', `Recording '${recordingId}' is already stopped`); }); it('should return 404 when recordingId is not in the correct format', async () => { - const response = await stopRecording('invalid-recording-id', moderatorCookie); + const response = await stopRecording('invalid-recording-id', moderatorToken); expect(response.status).toBe(422); expect(response.body.error).toBe('Unprocessable Entity'); expect(response.body.message).toContain('Invalid request'); diff --git a/backend/tests/integration/api/rooms/bulk-delete-rooms.test.ts b/backend/tests/integration/api/rooms/bulk-delete-rooms.test.ts index b5ff799..b16887c 100644 --- a/backend/tests/integration/api/rooms/bulk-delete-rooms.test.ts +++ b/backend/tests/integration/api/rooms/bulk-delete-rooms.test.ts @@ -154,13 +154,13 @@ describe('Room API Tests', () => { }); it('should handle deletion when specifying withMeeting and withRecordings parameters', async () => { - const [room1, { room: room2 }, { room: room3 }, { room: room4, moderatorCookie }] = await Promise.all([ + const [room1, { room: room2 }, { room: room3 }, { room: room4, moderatorToken }] = await Promise.all([ createRoom(), // Room without active meeting or recordings setupSingleRoom(true), // Room with active meeting setupSingleRoomWithRecording(true), // Room with active meeting and recordings setupSingleRoomWithRecording(true) // Room with recordings ]); - await endMeeting(room4.roomId, moderatorCookie); + await endMeeting(room4.roomId, moderatorToken); const fakeRoomId = 'fakeRoomId'; // Non-existing room const response = await bulkDeleteRooms( diff --git a/backend/tests/integration/api/rooms/create-room.test.ts b/backend/tests/integration/api/rooms/create-room.test.ts index 7bcbc37..c87c900 100644 --- a/backend/tests/integration/api/rooms/create-room.test.ts +++ b/backend/tests/integration/api/rooms/create-room.test.ts @@ -3,13 +3,14 @@ import { Express } from 'express'; import ms from 'ms'; import request from 'supertest'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; +import { MEET_INITIAL_API_KEY } from '../../../../src/environment.js'; import { MeetRecordingAccess, MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings } from '../../../../src/typings/ce/index.js'; import { expectValidRoom } from '../../../helpers/assertion-helpers.js'; -import { createRoom, deleteAllRooms, loginUser, startTestServer } from '../../../helpers/request-helpers.js'; +import { createRoom, deleteAllRooms, startTestServer } from '../../../helpers/request-helpers.js'; const ROOMS_PATH = `${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms`; @@ -17,11 +18,9 @@ describe('Room API Tests', () => { const validAutoDeletionDate = Date.now() + ms('2h'); let app: Express; - let adminCookie: string; beforeAll(async () => { app = startTestServer(); - adminCookie = await loginUser(); }); afterAll(async () => { @@ -81,7 +80,11 @@ describe('Room API Tests', () => { roomName: 'TestRoom' }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); // Check that the error message contains the positive number validation expect(response.body.error).toContain('Unprocessable Entity'); @@ -94,7 +97,11 @@ describe('Room API Tests', () => { roomName: 'TestRoom' }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(response.body.error).toContain('Unprocessable Entity'); expect(JSON.stringify(response.body.details)).toContain( @@ -108,7 +115,11 @@ describe('Room API Tests', () => { roomName: 'TestRoom' }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain('Expected number'); }); @@ -119,7 +130,11 @@ describe('Room API Tests', () => { roomName: 'TestRoom' }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain('Expected number'); }); @@ -130,7 +145,11 @@ describe('Room API Tests', () => { roomName: 'TestRoom' }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain('Expected number'); }); @@ -142,7 +161,11 @@ describe('Room API Tests', () => { autoDeletionPolicy: 'invalid-policy' }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain('Expected object'); }); @@ -157,7 +180,11 @@ describe('Room API Tests', () => { } }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain('Invalid enum value'); }); @@ -172,7 +199,11 @@ describe('Room API Tests', () => { } }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain( 'FAIL policy is not allowed for withMeeting auto-deletion policy' @@ -189,7 +220,11 @@ describe('Room API Tests', () => { } }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain( 'FAIL policy is not allowed for withRecordings auto-deletion policy' @@ -202,7 +237,11 @@ describe('Room API Tests', () => { autoDeletionDate: validAutoDeletionDate }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain('Expected string'); }); @@ -213,7 +252,11 @@ describe('Room API Tests', () => { autoDeletionDate: validAutoDeletionDate }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain('Expected string'); }); @@ -225,7 +268,11 @@ describe('Room API Tests', () => { config: 'invalid-config' }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain('Expected object'); }); @@ -246,7 +293,11 @@ describe('Room API Tests', () => { } }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain('Expected boolean'); }); @@ -255,7 +306,7 @@ describe('Room API Tests', () => { // In this case, instead of sending JSON object, send an invalid JSON string. const response = await request(app) .post(ROOMS_PATH) - .set('Cookie', adminCookie) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) .set('Content-Type', 'application/json') .send('{"roomName": "TestRoom",') // invalid JSON syntax .expect(400); @@ -271,7 +322,11 @@ describe('Room API Tests', () => { autoDeletionDate: validAutoDeletionDate }; - const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send(payload).expect(422); + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) + .send(payload) + .expect(422); expect(JSON.stringify(response.body.details)).toContain('roomName cannot exceed 50 characters'); }); diff --git a/backend/tests/integration/api/rooms/delete-room.test.ts b/backend/tests/integration/api/rooms/delete-room.test.ts index 4412251..2ddef0d 100644 --- a/backend/tests/integration/api/rooms/delete-room.test.ts +++ b/backend/tests/integration/api/rooms/delete-room.test.ts @@ -51,14 +51,14 @@ describe('Room API Tests', () => { describe('with active meeting but no recordings', () => { let roomId: string; let roomName: string; - let moderatorCookie: string; + let moderatorToken: string; beforeEach(async () => { // Create a room with an active meeting - const { room, moderatorCookie: cookie } = await setupSingleRoom(true); + const { room, moderatorToken: token } = await setupSingleRoom(true); roomId = room.roomId; roomName = room.roomName; - moderatorCookie = cookie; + moderatorToken = token; }); it('should return 200 with successCode=room_with_active_meeting_deleted when withMeeting=force', async () => { @@ -96,7 +96,7 @@ describe('Room API Tests', () => { ); // End meeting and check the room is deleted - await endMeeting(roomId, moderatorCookie); + await endMeeting(roomId, moderatorToken); const getResponse = await getRoom(roomId); expect(getResponse.status).toBe(404); }); @@ -114,10 +114,10 @@ describe('Room API Tests', () => { beforeEach(async () => { // Create a room with recordings and end the meeting - const { room, moderatorCookie } = await setupSingleRoomWithRecording(true); + const { room, moderatorToken } = await setupSingleRoomWithRecording(true); roomId = room.roomId; roomName = room.roomName; - await endMeeting(roomId, moderatorCookie); + await endMeeting(roomId, moderatorToken); }); it('should return 200 with successCode=room_and_recordings_deleted when withRecording=force', async () => { @@ -171,14 +171,14 @@ describe('Room API Tests', () => { describe('with active meeting and recordings', () => { let roomId: string; let roomName: string; - let moderatorCookie: string; + let moderatorToken: string; beforeEach(async () => { // Create a room with recordings, keep the meeting active - const { room, moderatorCookie: cookie } = await setupSingleRoomWithRecording(true); + const { room, moderatorToken: token } = await setupSingleRoomWithRecording(true); roomId = room.roomId; roomName = room.roomName; - moderatorCookie = cookie; + moderatorToken = token; }); it('should return 200 with successCode=room_with_active_meeting_and_recordings_deleted when withMeeting=force and withRecording=force', async () => { @@ -269,7 +269,7 @@ describe('Room API Tests', () => { ); // End meeting and check the room and recordings are deleted - await endMeeting(roomId, moderatorCookie); + await endMeeting(roomId, moderatorToken); const roomResponse = await getRoom(roomId); expect(roomResponse.status).toBe(404); const recordingsResponse = await getAllRecordings({ roomId, maxItems: 1 }); @@ -297,7 +297,7 @@ describe('Room API Tests', () => { ); // End meeting and check that the room is closed and recordings are not deleted - await endMeeting(roomId, moderatorCookie); + await endMeeting(roomId, moderatorToken); const roomResponse = await getRoom(roomId); expect(roomResponse.status).toBe(200); expectValidRoom( diff --git a/backend/tests/integration/api/rooms/garbage-collector.test.ts b/backend/tests/integration/api/rooms/garbage-collector.test.ts index 734edba..0c58fa8 100644 --- a/backend/tests/integration/api/rooms/garbage-collector.test.ts +++ b/backend/tests/integration/api/rooms/garbage-collector.test.ts @@ -12,7 +12,7 @@ import { deleteAllRooms, disconnectFakeParticipants, endMeeting, - generateParticipantTokenCookie, + generateParticipantToken, getRoom, joinFakeParticipant, runRoomGarbageCollector, @@ -102,8 +102,8 @@ describe('Room Garbage Collector Tests', () => { // End the meeting const { moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(room); - const moderatorCookie = await generateParticipantTokenCookie(room.roomId, moderatorSecret, 'moderator'); - await endMeeting(room.roomId, moderatorCookie); + const moderatorToken = await generateParticipantToken(room.roomId, moderatorSecret, 'moderator'); + await endMeeting(room.roomId, moderatorToken); // Verify that the room is deleted response = await getRoom(room.roomId); @@ -180,8 +180,8 @@ describe('Room Garbage Collector Tests', () => { // Start recording const { moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(room1); - const moderatorCookie = await generateParticipantTokenCookie(room1.roomId, moderatorSecret, 'moderator'); - await startRecording(room1.roomId, moderatorCookie); + const moderatorToken = await generateParticipantToken(room1.roomId, moderatorSecret, 'moderator'); + await startRecording(room1.roomId, moderatorToken); await runRoomGarbageCollector(); diff --git a/backend/tests/integration/api/rooms/generate-recording-token.test.ts b/backend/tests/integration/api/rooms/generate-recording-token.test.ts index 769a947..d7cf803 100644 --- a/backend/tests/integration/api/rooms/generate-recording-token.test.ts +++ b/backend/tests/integration/api/rooms/generate-recording-token.test.ts @@ -1,13 +1,16 @@ import { afterAll, beforeAll, describe, expect, it } from '@jest/globals'; +import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; +import { AuthTransportMode } from '../../../../src/typings/ce/index.js'; import { ParticipantRole } from '../../../../src/typings/ce/participant.js'; import { MeetRecordingAccess } from '../../../../src/typings/ce/room-config.js'; import { expectValidRecordingTokenResponse } from '../../../helpers/assertion-helpers.js'; import { + changeAuthTransportMode, deleteAllRecordings, deleteAllRooms, - deleteRoom, disconnectFakeParticipants, - generateRecordingToken, + extractCookieFromHeaders, + generateRecordingTokenRequest, startTestServer, updateRecordingAccessConfigInRoom } from '../../../helpers/request-helpers.js'; @@ -30,46 +33,60 @@ describe('Room API Tests', () => { it('should generate a recording token with canRetrieve and canDelete permissions when using the moderator secret and recording access is admin_moderator', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const response = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); + const response = await generateRecordingTokenRequest(roomData.room.roomId, roomData.moderatorSecret); expectValidRecordingTokenResponse(response, roomData.room.roomId, ParticipantRole.MODERATOR, true, true); }); it('should generate a recording token with canRetrieve and canDelete permissions when using the moderator secret and recording access is admin_moderator_speaker', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER); - const response = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); + const response = await generateRecordingTokenRequest(roomData.room.roomId, roomData.moderatorSecret); expectValidRecordingTokenResponse(response, roomData.room.roomId, ParticipantRole.MODERATOR, true, true); }); it('should generate a recording token without any permissions when using the speaker secret and recording access is admin_moderator', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const response = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); + const response = await generateRecordingTokenRequest(roomData.room.roomId, roomData.speakerSecret); expectValidRecordingTokenResponse(response, roomData.room.roomId, ParticipantRole.SPEAKER, false, false); }); it('should generate a recording token with canRetrieve permission but not canDelete when using the speaker secret and recording access is admin_moderator_speaker', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER); - const response = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); + const response = await generateRecordingTokenRequest(roomData.room.roomId, roomData.speakerSecret); expectValidRecordingTokenResponse(response, roomData.room.roomId, ParticipantRole.SPEAKER, true, false); }); - it('should succeed even if the room is deleted', async () => { - await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER); - await deleteRoom(roomData.room.roomId); + it('should generate a recording token and store it in a cookie when in cookie mode', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); - const response = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); + // Generate the recording token + await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER); + const response = await generateRecordingTokenRequest(roomData.room.roomId, roomData.moderatorSecret); expectValidRecordingTokenResponse(response, roomData.room.roomId, ParticipantRole.MODERATOR, true, true); - // Recreate the room with recording - roomData = await setupSingleRoomWithRecording(true); + // Check that the token is included in a cookie + const recordingTokenCookie = extractCookieFromHeaders( + response, + INTERNAL_CONFIG.RECORDING_TOKEN_COOKIE_NAME + ); + expect(recordingTokenCookie).toBeDefined(); + expect(recordingTokenCookie).toContain(response.body.token); + expect(recordingTokenCookie).toContain('HttpOnly'); + expect(recordingTokenCookie).toContain('SameSite=None'); + expect(recordingTokenCookie).toContain('Secure'); + expect(recordingTokenCookie).toContain('Path=/'); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail with a 404 error if there are no recordings in the room', async () => { await deleteAllRecordings(); - const response = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); + const response = await generateRecordingTokenRequest(roomData.room.roomId, roomData.moderatorSecret); expect(response.status).toBe(404); // Recreate the room with recording @@ -77,12 +94,12 @@ describe('Room API Tests', () => { }); it('should fail with a 404 error if the room does not exist', async () => { - const response = await generateRecordingToken('non-existent-room-id', roomData.moderatorSecret); + const response = await generateRecordingTokenRequest('non-existent-room-id', roomData.moderatorSecret); expect(response.status).toBe(404); }); it('should fail with a 400 error if the secret is invalid', async () => { - const response = await generateRecordingToken(roomData.room.roomId, 'invalid-secret'); + const response = await generateRecordingTokenRequest(roomData.room.roomId, 'invalid-secret'); expect(response.status).toBe(400); }); }); diff --git a/backend/tests/integration/api/rooms/get-room.test.ts b/backend/tests/integration/api/rooms/get-room.test.ts index 3ba4059..6ef7182 100644 --- a/backend/tests/integration/api/rooms/get-room.test.ts +++ b/backend/tests/integration/api/rooms/get-room.test.ts @@ -102,7 +102,7 @@ describe('Room API Tests', () => { const response = await getRoom( roomData.room.roomId, undefined, - roomData.speakerCookie, + roomData.speakerToken, ParticipantRole.SPEAKER ); expect(response.status).toBe(200); diff --git a/backend/tests/integration/api/rooms/update-room-status.test.ts b/backend/tests/integration/api/rooms/update-room-status.test.ts index 8f09e12..4121013 100644 --- a/backend/tests/integration/api/rooms/update-room-status.test.ts +++ b/backend/tests/integration/api/rooms/update-room-status.test.ts @@ -68,7 +68,7 @@ describe('Room API Tests', () => { expect(getResponse.body.meetingEndAction).toEqual('close'); // End meeting and verify closed status - await endMeeting(roomData.room.roomId, roomData.moderatorCookie); + await endMeeting(roomData.room.roomId, roomData.moderatorToken); getResponse = await getRoom(roomData.room.roomId); expect(getResponse.status).toBe(200); diff --git a/backend/tests/integration/api/security/auth.test.ts b/backend/tests/integration/api/security/auth.test.ts index b9d0fae..e46e698 100644 --- a/backend/tests/integration/api/security/auth.test.ts +++ b/backend/tests/integration/api/security/auth.test.ts @@ -5,7 +5,15 @@ import { container } from '../../../../src/config/dependency-injector.config.js' import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import { MeetStorageService } from '../../../../src/services/index.js'; import { expectValidationError } from '../../../helpers/assertion-helpers.js'; -import { generateApiKey, getApiKeys, loginUser, startTestServer } from '../../../helpers/request-helpers.js'; +import { + changeAuthTransportMode, + extractCookieFromHeaders, + generateApiKey, + getApiKeys, + loginUser, + startTestServer +} from '../../../helpers/request-helpers.js'; +import { AuthTransportMode } from '../../../../src/typings/ce/index.js'; const AUTH_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth`; @@ -28,17 +36,31 @@ describe('Authentication API Tests', () => { expect(response.body).toHaveProperty('message'); - // Check for access token and refresh token cookies - expect(response.headers['set-cookie']).toBeDefined(); - const cookies = response.headers['set-cookie'] as unknown as string[]; - const accessTokenCookie = cookies.find((cookie) => - cookie.startsWith(`${INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME}=`) - ); - const refreshTokenCookie = cookies.find((cookie) => - cookie.startsWith(`${INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME}=`) - ); + // Check for access and refresh tokens + expect(response.body).toHaveProperty('accessToken'); + expect(response.body).toHaveProperty('refreshToken'); + }); + + it('should successfully login and set cookies in cookie mode', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + const response = await request(app) + .post(`${AUTH_PATH}/login`) + .send({ + username: 'admin', + password: 'admin' + }) + .expect(200); + + // Check for access and refresh token cookies + const accessTokenCookie = extractCookieFromHeaders(response, INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME); + const refreshTokenCookie = extractCookieFromHeaders(response, INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME); expect(accessTokenCookie).toBeDefined(); expect(refreshTokenCookie).toBeDefined(); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should return 404 for invalid credentials', async () => { @@ -107,17 +129,24 @@ describe('Authentication API Tests', () => { expect(response.body).toHaveProperty('message'); expect(response.body.message).toBe('Logout successful'); + }); - // Check for cleared cookies - const cookies = response.headers['set-cookie'] as unknown as string[]; - const accessTokenCookie = cookies.find((cookie) => - cookie.startsWith(`${INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME}=;`) - ); - const refreshTokenCookie = cookies.find((cookie) => - cookie.startsWith(`${INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME}=;`) - ); + it('should successfully logout and clear cookies in cookie mode', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + const response = await request(app).post(`${AUTH_PATH}/logout`).expect(200); + + // Check that the access and refresh token cookies are cleared + const accessTokenCookie = extractCookieFromHeaders(response, INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME); + const refreshTokenCookie = extractCookieFromHeaders(response, INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME); expect(accessTokenCookie).toBeDefined(); + expect(accessTokenCookie).toContain('Expires=Thu, 01 Jan 1970 00:00:00 GMT'); expect(refreshTokenCookie).toBeDefined(); + expect(refreshTokenCookie).toContain('Expires=Thu, 01 Jan 1970 00:00:00 GMT'); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); }); @@ -132,24 +161,48 @@ describe('Authentication API Tests', () => { }) .expect(200); - const cookies = loginResponse.headers['set-cookie'] as unknown as string[]; - const refreshTokenCookie = cookies.find((cookie) => - cookie.startsWith(`${INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME}=`) - ) as string; + expect(loginResponse.body).toHaveProperty('refreshToken'); + const refreshToken = loginResponse.body.refreshToken; const response = await request(app) .post(`${AUTH_PATH}/refresh`) - .set('Cookie', [refreshTokenCookie]) + .set(INTERNAL_CONFIG.REFRESH_TOKEN_HEADER, `Bearer ${refreshToken}`) .expect(200); expect(response.body).toHaveProperty('message'); + expect(response.body).toHaveProperty('accessToken'); + }); - // Check for new access token cookie - const newCookies = response.headers['set-cookie'] as unknown as string[]; - const newAccessTokenCookie = newCookies.find((cookie) => - cookie.startsWith(`${INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME}=`) + it('should successfully refresh token and set new access token cookie in cookie mode', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // First, login to get a valid refresh token cookie + const loginResponse = await request(app) + .post(`${AUTH_PATH}/login`) + .send({ + username: 'admin', + password: 'admin' + }) + .expect(200); + + const refreshTokenCookie = extractCookieFromHeaders( + loginResponse, + INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME ); + expect(refreshTokenCookie).toBeDefined(); + + const response = await request(app) + .post(`${AUTH_PATH}/refresh`) + .set('Cookie', refreshTokenCookie!) + .expect(200); + + // Check that a new access token cookie is set + const newAccessTokenCookie = extractCookieFromHeaders(response, INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME); expect(newAccessTokenCookie).toBeDefined(); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should return 400 when no refresh token is provided', async () => { @@ -162,7 +215,7 @@ describe('Authentication API Tests', () => { it('should return 400 when refresh token is invalid', async () => { const response = await request(app) .post(`${AUTH_PATH}/refresh`) - .set('Cookie', `${INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME}=invalidtoken`) + .set(INTERNAL_CONFIG.REFRESH_TOKEN_HEADER, 'Bearer invalidtoken') .expect(400); expect(response.body).toHaveProperty('message'); @@ -171,10 +224,10 @@ describe('Authentication API Tests', () => { }); describe('API Keys Management', () => { - let adminCookie: string; + let adminAccessToken: string; beforeAll(async () => { - adminCookie = await loginUser(); + adminAccessToken = await loginUser(); }); afterAll(async () => { @@ -190,7 +243,10 @@ describe('Authentication API Tests', () => { }; it('should create a new API key', async () => { - const response = await request(app).post(`${AUTH_PATH}/api-keys`).set('Cookie', adminCookie).expect(201); + const response = await request(app) + .post(`${AUTH_PATH}/api-keys`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .expect(201); expect(response.body).toHaveProperty('key'); expect(response.body).toHaveProperty('creationDate'); @@ -233,7 +289,10 @@ describe('Authentication API Tests', () => { it('should delete all API keys', async () => { const apiKey = await generateApiKey(); - await request(app).delete(`${AUTH_PATH}/api-keys`).set('Cookie', adminCookie).expect(200); + await request(app) + .delete(`${AUTH_PATH}/api-keys`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .expect(200); // Confirm deletion const getResponse = await getApiKeys(); @@ -246,6 +305,21 @@ describe('Authentication API Tests', () => { expect(apiResponse.status).toBe(401); }); + it('should succeed API key endpoints for authenticated admin user in cookie mode', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + + await request(app).post(`${AUTH_PATH}/api-keys`).set('Cookie', adminCookie).expect(201); + await request(app).get(`${AUTH_PATH}/api-keys`).set('Cookie', adminCookie).expect(200); + await request(app).delete(`${AUTH_PATH}/api-keys`).set('Cookie', adminCookie).expect(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should reject API key endpoints for unauthenticated users', async () => { await request(app).post(`${AUTH_PATH}/api-keys`).expect(401); await request(app).get(`${AUTH_PATH}/api-keys`).expect(401); diff --git a/backend/tests/integration/api/security/global-config-security.test.ts b/backend/tests/integration/api/security/global-config-security.test.ts index e5ee20a..1bdfca8 100644 --- a/backend/tests/integration/api/security/global-config-security.test.ts +++ b/backend/tests/integration/api/security/global-config-security.test.ts @@ -6,7 +6,7 @@ import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import { MEET_INITIAL_API_KEY } from '../../../../src/environment.js'; import { MeetStorageService } from '../../../../src/services/index.js'; import { AuthMode, AuthTransportMode, AuthType, MeetRoomThemeMode } from '../../../../src/typings/ce/index.js'; -import { loginUser, startTestServer } from '../../../helpers/request-helpers.js'; +import { changeAuthTransportMode, loginUser, startTestServer } from '../../../helpers/request-helpers.js'; const CONFIG_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config`; @@ -17,11 +17,11 @@ const restoreGlobalConfig = async () => { describe('Global Config API Security Tests', () => { let app: Express; - let adminCookie: string; + let adminAccessToken: string; beforeAll(async () => { app = startTestServer(); - adminCookie = await loginUser(); + adminAccessToken = await loginUser(); }); describe('Update Webhook Config Tests', () => { @@ -39,12 +39,29 @@ describe('Global Config API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .put(`${CONFIG_PATH}/webhooks`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .send(webhookConfig); + expect(response.status).toBe(200); + + await restoreGlobalConfig(); + }); + + it('should succeed when user is authenticated as admin in cookie mode', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app) .put(`${CONFIG_PATH}/webhooks`) .set('Cookie', adminCookie) .send(webhookConfig); expect(response.status).toBe(200); + // This method already restores the config to default (header mode) await restoreGlobalConfig(); }); @@ -63,8 +80,24 @@ describe('Global Config API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .get(`${CONFIG_PATH}/webhooks`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as admin in cookie mode', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app).get(`${CONFIG_PATH}/webhooks`).set('Cookie', adminCookie); expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when user is not authenticated', async () => { @@ -79,7 +112,7 @@ describe('Global Config API Security Tests', () => { authMethod: { type: AuthType.SINGLE_USER }, - authTransportMode: AuthTransportMode.COOKIE, + authTransportMode: AuthTransportMode.HEADER, authModeToAccessRoom: AuthMode.ALL_USERS } }; @@ -93,12 +126,29 @@ describe('Global Config API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .put(`${CONFIG_PATH}/security`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .send(securityConfig); + expect(response.status).toBe(200); + + await restoreGlobalConfig(); + }); + + it('should succeed when user is authenticated as admin in cookie mode', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app) .put(`${CONFIG_PATH}/security`) .set('Cookie', adminCookie) .send(securityConfig); expect(response.status).toBe(200); + // This method already restores the config to default (header mode) await restoreGlobalConfig(); }); @@ -137,6 +187,22 @@ describe('Global Config API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .put(`${CONFIG_PATH}/rooms/appearance`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .send(appearanceConfig); + expect(response.status).toBe(200); + + await restoreGlobalConfig(); + }); + + it('should succeed when user is authenticated as admin in cookie mode', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app) .put(`${CONFIG_PATH}/rooms/appearance`) .set('Cookie', adminCookie) diff --git a/backend/tests/integration/api/security/meeting-security.test.ts b/backend/tests/integration/api/security/meeting-security.test.ts index 4f3d8d0..2a69734 100644 --- a/backend/tests/integration/api/security/meeting-security.test.ts +++ b/backend/tests/integration/api/security/meeting-security.test.ts @@ -3,9 +3,10 @@ import { Express } from 'express'; import request from 'supertest'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import { LIVEKIT_URL, MEET_INITIAL_API_KEY } from '../../../../src/environment.js'; -import { MeetTokenMetadata, ParticipantRole } from '../../../../src/typings/ce'; +import { AuthTransportMode, MeetTokenMetadata, ParticipantRole } from '../../../../src/typings/ce'; import { getPermissions } from '../../../helpers/assertion-helpers.js'; import { + changeAuthTransportMode, deleteAllRooms, disconnectFakeParticipants, loginUser, @@ -18,12 +19,12 @@ const MEETINGS_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings`; describe('Meeting API Security Tests', () => { let app: Express; - let adminCookie: string; + let adminAccessToken: string; let roomData: RoomData; beforeAll(async () => { app = startTestServer(); - adminCookie = await loginUser(); + adminAccessToken = await loginUser(); }); beforeEach(async () => { @@ -46,24 +47,41 @@ describe('Meeting API Security Tests', () => { it('should fail when user is authenticated as admin', async () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) - .set('Cookie', adminCookie); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(401); }); it('should succeed when participant is moderator', async () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) - .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(200); }); + it('should succeed when participant is moderator and token is sent in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Create a new room to obtain participant token in cookie mode + const newRoomData = await setupSingleRoom(true); + + const response = await request(app) + .delete(`${MEETINGS_PATH}/${newRoomData.room.roomId}`) + .set('Cookie', newRoomData.moderatorToken) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); + expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should fail when participant is moderator of a different room', async () => { const newRoomData = await setupSingleRoom(); const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) - .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, newRoomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); @@ -71,7 +89,7 @@ describe('Meeting API Security Tests', () => { it('should fail when participant is speaker', async () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) - .set('Cookie', roomData.speakerCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.speakerToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.SPEAKER); expect(response.status).toBe(403); }); @@ -106,7 +124,7 @@ describe('Meeting API Security Tests', () => { it('should fail when user is authenticated as admin', async () => { const response = await request(app) .put(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}/role`) - .set('Cookie', adminCookie) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) .send({ role }); expect(response.status).toBe(401); }); @@ -114,18 +132,46 @@ describe('Meeting API Security Tests', () => { it('should succeed when participant is moderator', async () => { const response = await request(app) .put(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}/role`) - .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) .send({ role }); expect(response.status).toBe(200); }); + it('should succeed when participant is moderator and token is sent in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Create a new room to obtain participant token in cookie mode + const newRoomData = await setupSingleRoom(true); + await updateParticipantMetadata(newRoomData.room.roomId, PARTICIPANT_NAME, { + livekitUrl: LIVEKIT_URL, + roles: [ + { + role: ParticipantRole.SPEAKER, + permissions: getPermissions(newRoomData.room.roomId, ParticipantRole.SPEAKER).openvidu + } + ], + selectedRole: ParticipantRole.SPEAKER + }); + + const response = await request(app) + .put(`${MEETINGS_PATH}/${newRoomData.room.roomId}/participants/${PARTICIPANT_NAME}/role`) + .set('Cookie', newRoomData.moderatorToken) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) + .send({ role }); + expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should fail when participant is moderator of a different room', async () => { const newRoomData = await setupSingleRoom(); const response = await request(app) .put(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}/role`) - .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, newRoomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) .send({ role }); expect(response.status).toBe(403); @@ -134,7 +180,7 @@ describe('Meeting API Security Tests', () => { it('should fail when participant is speaker', async () => { const response = await request(app) .put(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}/role`) - .set('Cookie', roomData.speakerCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.speakerToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.SPEAKER) .send({ role }); expect(response.status).toBe(403); @@ -154,24 +200,41 @@ describe('Meeting API Security Tests', () => { it('should fail when user is authenticated as admin', async () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_IDENTITY}`) - .set('Cookie', adminCookie); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(401); }); it('should succeed when participant is moderator', async () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_IDENTITY}`) - .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(200); }); + it('should succeed when participant is moderator and token is sent in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Create a new room to obtain participant token in cookie mode + const newRoomData = await setupSingleRoom(true); + + const response = await request(app) + .delete(`${MEETINGS_PATH}/${newRoomData.room.roomId}/participants/${PARTICIPANT_IDENTITY}`) + .set('Cookie', newRoomData.moderatorToken) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); + expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should fail when participant is moderator of a different room', async () => { const newRoomData = await setupSingleRoom(); const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_IDENTITY}`) - .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, newRoomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); @@ -179,7 +242,7 @@ describe('Meeting API Security Tests', () => { it('should fail when participant is speaker', async () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_IDENTITY}`) - .set('Cookie', roomData.speakerCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.speakerToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.SPEAKER); expect(response.status).toBe(403); }); diff --git a/backend/tests/integration/api/security/participant-security.test.ts b/backend/tests/integration/api/security/participant-security.test.ts index e7bc342..0109f86 100644 --- a/backend/tests/integration/api/security/participant-security.test.ts +++ b/backend/tests/integration/api/security/participant-security.test.ts @@ -2,8 +2,9 @@ import { afterAll, beforeAll, describe, expect, it } from '@jest/globals'; import { Express } from 'express'; import request from 'supertest'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; -import { AuthMode } from '../../../../src/typings/ce/index.js'; +import { AuthMode, AuthTransportMode } from '../../../../src/typings/ce/index.js'; import { + changeAuthTransportMode, changeSecurityConfig, deleteAllRooms, disconnectFakeParticipants, @@ -19,11 +20,11 @@ describe('Participant API Security Tests', () => { const PARTICIPANT_NAME = 'TEST_PARTICIPANT'; let app: Express; - let adminCookie: string; + let adminAccessToken: string; beforeAll(async () => { app = startTestServer(); - adminCookie = await loginUser(); + adminAccessToken = await loginUser(); }); afterAll(async () => { @@ -74,12 +75,33 @@ describe('Participant API Security Tests', () => { it('should succeed when authentication is required for moderator, participant is moderator and authenticated', async () => { await changeSecurityConfig(AuthMode.MODERATORS_ONLY); + const response = await request(app) + .post(`${PARTICIPANTS_PATH}/token`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .send({ + roomId: roomData.room.roomId, + secret: roomData.moderatorSecret, + participantName: PARTICIPANT_NAME + }); + expect(response.status).toBe(200); + }); + + it('should succeed when authentication is required for moderator, participant is moderator and authenticated via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({ roomId: roomData.room.roomId, secret: roomData.moderatorSecret, participantName: PARTICIPANT_NAME }); expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when authentication is required for moderator and participant is moderator but not authenticated', async () => { @@ -96,11 +118,14 @@ describe('Participant API Security Tests', () => { it('should succeed when authentication is required for all users, participant is speaker and authenticated', async () => { await changeSecurityConfig(AuthMode.ALL_USERS); - const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({ - roomId: roomData.room.roomId, - secret: roomData.speakerSecret, - participantName: PARTICIPANT_NAME - }); + const response = await request(app) + .post(`${PARTICIPANTS_PATH}/token`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .send({ + roomId: roomData.room.roomId, + secret: roomData.speakerSecret, + participantName: PARTICIPANT_NAME + }); expect(response.status).toBe(200); }); @@ -118,11 +143,14 @@ describe('Participant API Security Tests', () => { it('should succeed when authentication is required for all users, participant is moderator and authenticated', async () => { await changeSecurityConfig(AuthMode.ALL_USERS); - const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({ - roomId: roomData.room.roomId, - secret: roomData.moderatorSecret, - participantName: PARTICIPANT_NAME - }); + const response = await request(app) + .post(`${PARTICIPANTS_PATH}/token`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .send({ + roomId: roomData.room.roomId, + secret: roomData.moderatorSecret, + participantName: PARTICIPANT_NAME + }); expect(response.status).toBe(200); }); @@ -158,7 +186,7 @@ describe('Participant API Security Tests', () => { const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', roomData.speakerCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.speakerToken) .send({ roomId: roomData.room.roomId, secret: roomData.speakerSecret, @@ -173,7 +201,7 @@ describe('Participant API Security Tests', () => { const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .send({ roomId: roomData.room.roomId, secret: roomData.moderatorSecret, @@ -188,7 +216,7 @@ describe('Participant API Security Tests', () => { const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', roomData.speakerCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.speakerToken) .send({ roomId: roomData.room.roomId, secret: roomData.speakerSecret, @@ -203,7 +231,8 @@ describe('Participant API Security Tests', () => { const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', [adminCookie, roomData.moderatorCookie]) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .send({ roomId: roomData.room.roomId, secret: roomData.moderatorSecret, @@ -213,12 +242,38 @@ describe('Participant API Security Tests', () => { expect(response.status).toBe(200); }); + it('should succeed when authentication is required for moderator, participant is moderator and authenticated via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + + // Create a new room to obtain participant token in cookie mode + const newRoomData = await setupSingleRoom(true); + + const response = await request(app) + .post(`${PARTICIPANTS_PATH}/token/refresh`) + .set('Cookie', adminCookie) + .set('Cookie', newRoomData.moderatorToken) + .send({ + roomId: newRoomData.room.roomId, + secret: newRoomData.moderatorSecret, + participantName: PARTICIPANT_NAME, + participantIdentity: PARTICIPANT_NAME + }); + expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should fail when authentication is required for moderator and participant is moderator but not authenticated', async () => { await changeSecurityConfig(AuthMode.MODERATORS_ONLY); const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .send({ roomId: roomData.room.roomId, secret: roomData.moderatorSecret, @@ -233,7 +288,8 @@ describe('Participant API Security Tests', () => { const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', [adminCookie, roomData.speakerCookie]) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.speakerToken) .send({ roomId: roomData.room.roomId, secret: roomData.speakerSecret, @@ -248,7 +304,7 @@ describe('Participant API Security Tests', () => { const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', roomData.speakerCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.speakerToken) .send({ roomId: roomData.room.roomId, secret: roomData.speakerSecret, @@ -263,7 +319,8 @@ describe('Participant API Security Tests', () => { const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', [adminCookie, roomData.moderatorCookie]) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .send({ roomId: roomData.room.roomId, secret: roomData.moderatorSecret, @@ -278,7 +335,7 @@ describe('Participant API Security Tests', () => { const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .send({ roomId: roomData.room.roomId, secret: roomData.moderatorSecret, diff --git a/backend/tests/integration/api/security/recording-security.test.ts b/backend/tests/integration/api/security/recording-security.test.ts index 3e5373b..ede7a15 100644 --- a/backend/tests/integration/api/security/recording-security.test.ts +++ b/backend/tests/integration/api/security/recording-security.test.ts @@ -3,13 +3,14 @@ import { Express } from 'express'; import request from 'supertest'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import { MEET_INITIAL_API_KEY } from '../../../../src/environment.js'; -import { MeetRecordingAccess, ParticipantRole } from '../../../../src/typings/ce/index.js'; +import { AuthTransportMode, MeetRecordingAccess, ParticipantRole } from '../../../../src/typings/ce/index.js'; import { expectValidStopRecordingResponse } from '../../../helpers/assertion-helpers.js'; import { + changeAuthTransportMode, deleteAllRecordings, deleteAllRooms, disconnectFakeParticipants, - generateRecordingTokenCookie, + generateRecordingToken, getRecordingUrl, loginUser, startTestServer, @@ -24,11 +25,11 @@ const INTERNAL_RECORDINGS_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/r describe('Recording API Security Tests', () => { let app: Express; - let adminCookie: string; + let adminAccessToken: string; beforeAll(async () => { app = startTestServer(); - adminCookie = await loginUser(); + adminAccessToken = await loginUser(); }); afterAll(async () => { @@ -55,7 +56,7 @@ describe('Recording API Security Tests', () => { const response = await request(app) .post(INTERNAL_RECORDINGS_PATH) .send({ roomId: roomData.room.roomId }) - .set('Cookie', adminCookie); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(401); }); @@ -63,23 +64,51 @@ describe('Recording API Security Tests', () => { const response = await request(app) .post(INTERNAL_RECORDINGS_PATH) .send({ roomId: roomData.room.roomId }) - .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(201); // Stop recording to clean up const recordingId = response.body.recordingId; - const stopResponse = await stopRecording(recordingId, roomData.moderatorCookie); + const stopResponse = await stopRecording(recordingId, roomData.moderatorToken); expectValidStopRecordingResponse(stopResponse, recordingId, roomData.room.roomId, roomData.room.roomName); }); + it('should succeed when participant is moderator and token is sent in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Create a new room to obtain participant token in cookie mode + const newRoomData = await setupSingleRoom(true); + + const response = await request(app) + .post(INTERNAL_RECORDINGS_PATH) + .send({ roomId: newRoomData.room.roomId }) + .set('Cookie', newRoomData.moderatorToken) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); + expect(response.status).toBe(201); + + // Stop recording to clean up + const recordingId = response.body.recordingId; + const stopResponse = await stopRecording(recordingId, newRoomData.moderatorToken); + expectValidStopRecordingResponse( + stopResponse, + recordingId, + newRoomData.room.roomId, + newRoomData.room.roomName + ); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should fail when participant is moderator of a different room', async () => { const newRoomData = await setupSingleRoom(); const response = await request(app) .post(INTERNAL_RECORDINGS_PATH) .send({ roomId: roomData.room.roomId }) - .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, newRoomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); @@ -88,7 +117,7 @@ describe('Recording API Security Tests', () => { const response = await request(app) .post(INTERNAL_RECORDINGS_PATH) .send({ roomId: roomData.room.roomId }) - .set('Cookie', roomData.speakerCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.speakerToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.SPEAKER); expect(response.status).toBe(403); }); @@ -102,7 +131,7 @@ describe('Recording API Security Tests', () => { }); afterAll(async () => { - await stopAllRecordings(roomData.moderatorCookie); + await stopAllRecordings(roomData.moderatorToken); }); it('should fail when request includes API key', async () => { @@ -116,24 +145,41 @@ describe('Recording API Security Tests', () => { it('should fail when user is authenticated as admin', async () => { const response = await request(app) .post(`${INTERNAL_RECORDINGS_PATH}/${roomData.recordingId}/stop`) - .set('Cookie', adminCookie); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(401); }); it('should succeed when participant is moderator', async () => { const response = await request(app) .post(`${INTERNAL_RECORDINGS_PATH}/${roomData.recordingId}/stop`) - .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(202); }); + it('should succeed when participant is moderator and token is sent in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Create a new room to obtain participant token in cookie mode + const newRoomData = await setupSingleRoomWithRecording(); + + const response = await request(app) + .post(`${INTERNAL_RECORDINGS_PATH}/${newRoomData.recordingId}/stop`) + .set('Cookie', newRoomData.moderatorToken) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); + expect(response.status).toBe(202); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should fail when participant is moderator of a different room', async () => { const newRoomData = await setupSingleRoom(); const response = await request(app) .post(`${INTERNAL_RECORDINGS_PATH}/${roomData.recordingId}/stop`) - .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, newRoomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); @@ -141,7 +187,7 @@ describe('Recording API Security Tests', () => { it('should fail when participant is speaker', async () => { const response = await request(app) .post(`${INTERNAL_RECORDINGS_PATH}/${roomData.recordingId}/stop`) - .set('Cookie', roomData.speakerCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.speakerToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.SPEAKER); expect(response.status).toBe(403); }); @@ -165,7 +211,9 @@ describe('Recording API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { - const response = await request(app).get(RECORDINGS_PATH).set('Cookie', adminCookie); + const response = await request(app) + .get(RECORDINGS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(200); }); @@ -174,48 +222,61 @@ describe('Recording API Security Tests', () => { roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); - const response = await request(app).get(RECORDINGS_PATH).set('Cookie', recordingCookie); + const response = await request(app) + .get(RECORDINGS_PATH) + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); + it('should succeed when recording access is admin_moderator_speaker and participant is speaker, token in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + await updateRecordingAccessConfigInRoom( + roomData.room.roomId, + MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER + ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); + + const response = await request(app).get(RECORDINGS_PATH).set('Cookie', recordingToken); + expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should succeed when recording access is admin_moderator_speaker and participant is moderator', async () => { await updateRecordingAccessConfigInRoom( roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); - const response = await request(app).get(RECORDINGS_PATH).set('Cookie', recordingCookie); + const response = await request(app) + .get(RECORDINGS_PATH) + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); it('should fail when recording access is admin_moderator and participant is speaker', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); - const response = await request(app).get(RECORDINGS_PATH).set('Cookie', recordingCookie); + const response = await request(app) + .get(RECORDINGS_PATH) + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(403); }); it('should succeed when recording access is admin_moderator and participant is moderator', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); - const response = await request(app).get(RECORDINGS_PATH).set('Cookie', recordingCookie); + const response = await request(app) + .get(RECORDINGS_PATH) + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); }); @@ -229,7 +290,9 @@ describe('Recording API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { - const response = await request(app).get(`${RECORDINGS_PATH}/${recordingId}`).set('Cookie', adminCookie); + const response = await request(app) + .get(`${RECORDINGS_PATH}/${recordingId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(200); }); @@ -238,56 +301,61 @@ describe('Recording API Security Tests', () => { roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); + it('should succeed when recording access is admin_moderator_speaker and participant is speaker, token in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + await updateRecordingAccessConfigInRoom( + roomData.room.roomId, + MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER + ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); + + const response = await request(app).get(RECORDINGS_PATH).set('Cookie', recordingToken); + expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should succeed when recording access is admin_moderator_speaker and participant is moderator', async () => { await updateRecordingAccessConfigInRoom( roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); it('should fail when recording access is admin_moderator and participant is speaker', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(403); }); it('should succeed when recording access is admin_moderator and participant is moderator', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); @@ -331,7 +399,7 @@ describe('Recording API Security Tests', () => { const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}?secret=${secret}`) - .set('Cookie', adminCookie); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(200); }); }); @@ -358,7 +426,7 @@ describe('Recording API Security Tests', () => { it('should succeed when user is authenticated as admin', async () => { const response = await request(app) .delete(`${RECORDINGS_PATH}/${fakeRecordingId}`) - .set('Cookie', adminCookie); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(404); }); @@ -367,14 +435,11 @@ describe('Recording API Security Tests', () => { roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .delete(`${RECORDINGS_PATH}/${fakeRecordingId}`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(403); }); @@ -383,40 +448,50 @@ describe('Recording API Security Tests', () => { roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .delete(`${RECORDINGS_PATH}/${fakeRecordingId}`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(404); }); + it('should succeed when recording access is admin_moderator_speaker and participant is moderator, token in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + await updateRecordingAccessConfigInRoom( + roomData.room.roomId, + MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER + ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); + + const response = await request(app) + .delete(`${RECORDINGS_PATH}/${fakeRecordingId}`) + .set('Cookie', recordingToken); + expect(response.status).toBe(404); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should fail when recording access is admin_moderator and participant is speaker', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .delete(`${RECORDINGS_PATH}/${fakeRecordingId}`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(403); }); it('should succeed when recording access is admin_moderator and participant is moderator', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .delete(`${RECORDINGS_PATH}/${fakeRecordingId}`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(404); }); }); @@ -445,7 +520,7 @@ describe('Recording API Security Tests', () => { const response = await request(app) .delete(RECORDINGS_PATH) .query({ recordingIds: fakeRecordingId }) - .set('Cookie', adminCookie); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(400); }); @@ -454,15 +529,12 @@ describe('Recording API Security Tests', () => { roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .delete(RECORDINGS_PATH) .query({ recordingIds: fakeRecordingId }) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(403); }); @@ -471,43 +543,54 @@ describe('Recording API Security Tests', () => { roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .delete(RECORDINGS_PATH) .query({ recordingIds: fakeRecordingId }) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(400); }); + it('should succeed when recording access is admin_moderator_speaker and participant is moderator, token in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + await updateRecordingAccessConfigInRoom( + roomData.room.roomId, + MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER + ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); + + const response = await request(app) + .delete(RECORDINGS_PATH) + .query({ recordingIds: fakeRecordingId }) + .set('Cookie', recordingToken); + expect(response.status).toBe(400); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should fail when recording access is admin_moderator and participant is speaker', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .delete(RECORDINGS_PATH) .query({ recordingIds: fakeRecordingId }) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(403); }); it('should succeed when recording access is admin_moderator and participant is moderator', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .delete(RECORDINGS_PATH) .query({ recordingIds: fakeRecordingId }) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(400); }); }); @@ -523,7 +606,7 @@ describe('Recording API Security Tests', () => { it('should succeed when user is authenticated as admin', async () => { const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}/media`) - .set('Cookie', adminCookie); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(200); }); @@ -532,14 +615,48 @@ describe('Recording API Security Tests', () => { roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}/media`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); + expect(response.status).toBe(200); + }); + + it('should succeed when recording access is admin_moderator_speaker and participant is speaker, token in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + await updateRecordingAccessConfigInRoom( + roomData.room.roomId, + MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER + ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); + + const response = await request(app) + .get(`${RECORDINGS_PATH}/${recordingId}/media`) + .set('Cookie', recordingToken); + expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + + it('should succeed when recording access is admin_moderator_speaker and participant is speaker, token in query param', async () => { + await updateRecordingAccessConfigInRoom( + roomData.room.roomId, + MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER + ); + let recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); + + // Remove the "Bearer " prefix if present + if (recordingToken.startsWith('Bearer ')) { + recordingToken = recordingToken.slice(7); + } + + const response = await request(app) + .get(`${RECORDINGS_PATH}/${recordingId}/media`) + .query({ recordingToken }); expect(response.status).toBe(200); }); @@ -548,40 +665,31 @@ describe('Recording API Security Tests', () => { roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}/media`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); it('should fail when recording access is admin_moderator and participant is speaker', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}/media`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(403); }); it('should succeed when recording access is admin_moderator and participant is moderator', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}/media`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); @@ -625,7 +733,7 @@ describe('Recording API Security Tests', () => { const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}/media?secret=${secret}`) - .set('Cookie', adminCookie); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(200); }); @@ -647,7 +755,7 @@ describe('Recording API Security Tests', () => { it('should succeed when user is authenticated as admin', async () => { const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}/url`) - .set('Cookie', adminCookie); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(200); }); @@ -656,56 +764,63 @@ describe('Recording API Security Tests', () => { roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}/url`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); + it('should succeed when recording access is admin_moderator_speaker and participant is speaker, token in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + await updateRecordingAccessConfigInRoom( + roomData.room.roomId, + MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER + ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); + + const response = await request(app) + .get(`${RECORDINGS_PATH}/${recordingId}/url`) + .set('Cookie', recordingToken); + expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should succeed when recording access is admin_moderator_speaker and participant is moderator', async () => { await updateRecordingAccessConfigInRoom( roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}/url`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); it('should fail when recording access is admin_moderator and participant is speaker', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}/url`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(403); }); it('should succeed when recording access is admin_moderator and participant is moderator', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/${recordingId}/url`) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); }); @@ -723,7 +838,7 @@ describe('Recording API Security Tests', () => { const response = await request(app) .get(`${RECORDINGS_PATH}/download`) .query({ recordingIds: recordingId }) - .set('Cookie', adminCookie); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(200); }); @@ -732,15 +847,50 @@ describe('Recording API Security Tests', () => { roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/download`) .query({ recordingIds: recordingId }) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); + expect(response.status).toBe(200); + }); + + it('should succeed when recording access is admin_moderator_speaker and participant is speaker, token in cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + await updateRecordingAccessConfigInRoom( + roomData.room.roomId, + MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER + ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); + + const response = await request(app) + .get(`${RECORDINGS_PATH}/download`) + .query({ recordingIds: recordingId }) + .set('Cookie', recordingToken); + expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + + it('should succeed when recording access is admin_moderator_speaker and participant is speaker, token in query param', async () => { + await updateRecordingAccessConfigInRoom( + roomData.room.roomId, + MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER + ); + let recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); + + // Remove the "Bearer " prefix if present + if (recordingToken.startsWith('Bearer ')) { + recordingToken = recordingToken.slice(7); + } + + const response = await request(app) + .get(`${RECORDINGS_PATH}/download`) + .query({ recordingIds: recordingId, recordingToken }); expect(response.status).toBe(200); }); @@ -749,43 +899,34 @@ describe('Recording API Security Tests', () => { roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER ); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/download`) .query({ recordingIds: recordingId }) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); it('should fail when recording access is admin_moderator and participant is speaker', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.speakerSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.speakerSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/download`) .query({ recordingIds: recordingId }) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(403); }); it('should succeed when recording access is admin_moderator and participant is moderator', async () => { await updateRecordingAccessConfigInRoom(roomData.room.roomId, MeetRecordingAccess.ADMIN_MODERATOR); - const recordingCookie = await generateRecordingTokenCookie( - roomData.room.roomId, - roomData.moderatorSecret - ); + const recordingToken = await generateRecordingToken(roomData.room.roomId, roomData.moderatorSecret); const response = await request(app) .get(`${RECORDINGS_PATH}/download`) .query({ recordingIds: recordingId }) - .set('Cookie', recordingCookie); + .set(INTERNAL_CONFIG.RECORDING_TOKEN_HEADER, recordingToken); expect(response.status).toBe(200); }); }); diff --git a/backend/tests/integration/api/security/room-security.test.ts b/backend/tests/integration/api/security/room-security.test.ts index ea593db..844c6af 100644 --- a/backend/tests/integration/api/security/room-security.test.ts +++ b/backend/tests/integration/api/security/room-security.test.ts @@ -3,8 +3,9 @@ import { Express } from 'express'; import request from 'supertest'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import { MEET_INITIAL_API_KEY } from '../../../../src/environment.js'; -import { AuthMode, MeetRecordingAccess, ParticipantRole } from '../../../../src/typings/ce/index.js'; +import { AuthMode, AuthTransportMode, MeetRecordingAccess, ParticipantRole } from '../../../../src/typings/ce/index.js'; import { + changeAuthTransportMode, changeSecurityConfig, createRoom, deleteAllRecordings, @@ -21,11 +22,11 @@ const INTERNAL_ROOMS_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/rooms` describe('Room API Security Tests', () => { let app: Express; - let adminCookie: string; + let adminAccessToken: string; beforeAll(async () => { app = startTestServer(); - adminCookie = await loginUser(); + adminAccessToken = await loginUser(); }); afterAll(async () => { @@ -43,8 +44,25 @@ describe('Room API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .post(ROOMS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .send({}); + expect(response.status).toBe(201); + }); + + it('should succeed when user is authenticated as admin via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send({}); expect(response.status).toBe(201); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when user is not authenticated', async () => { @@ -62,10 +80,26 @@ describe('Room API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { - const response = await request(app).get(ROOMS_PATH).set('Cookie', adminCookie); + const response = await request(app) + .get(ROOMS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); expect(response.status).toBe(200); }); + it('should succeed when user is authenticated as admin via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + + const response = await request(app).post(ROOMS_PATH).set('Cookie', adminCookie).send({}); + expect(response.status).toBe(201); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); + }); + it('should fail when user is not authenticated', async () => { const response = await request(app).get(ROOMS_PATH); expect(response.status).toBe(401); @@ -89,11 +123,28 @@ describe('Room API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .delete(ROOMS_PATH) + .query({ roomIds: roomId }) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as admin via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app) .delete(ROOMS_PATH) .query({ roomIds: roomId }) .set('Cookie', adminCookie); expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when user is not authenticated', async () => { @@ -117,8 +168,24 @@ describe('Room API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .get(`${ROOMS_PATH}/${roomData.room.roomId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as admin via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app).get(`${ROOMS_PATH}/${roomData.room.roomId}`).set('Cookie', adminCookie); expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when user is not authenticated', async () => { @@ -129,7 +196,7 @@ describe('Room API Security Tests', () => { it('should succeed when participant is moderator', async () => { const response = await request(app) .get(`${ROOMS_PATH}/${roomData.room.roomId}`) - .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(200); }); @@ -139,7 +206,7 @@ describe('Room API Security Tests', () => { const response = await request(app) .get(`${ROOMS_PATH}/${roomData.room.roomId}`) - .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, newRoomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); @@ -147,7 +214,7 @@ describe('Room API Security Tests', () => { it('should succeed when participant is speaker', async () => { const response = await request(app) .get(`${ROOMS_PATH}/${roomData.room.roomId}`) - .set('Cookie', roomData.speakerCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.speakerToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.SPEAKER); expect(response.status).toBe(200); }); @@ -169,8 +236,24 @@ describe('Room API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .delete(`${ROOMS_PATH}/${roomId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as admin via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app).delete(`${ROOMS_PATH}/${roomId}`).set('Cookie', adminCookie); expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when user is not authenticated', async () => { @@ -194,10 +277,26 @@ describe('Room API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .get(`${ROOMS_PATH}/${roomData.room.roomId}/config`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as admin via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app) .get(`${ROOMS_PATH}/${roomData.room.roomId}/config`) .set('Cookie', adminCookie); expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when user is not authenticated', async () => { @@ -208,7 +307,7 @@ describe('Room API Security Tests', () => { it('should succeed when participant is moderator', async () => { const response = await request(app) .get(`${ROOMS_PATH}/${roomData.room.roomId}/config`) - .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(200); }); @@ -218,7 +317,7 @@ describe('Room API Security Tests', () => { const response = await request(app) .get(`${ROOMS_PATH}/${roomData.room.roomId}/config`) - .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, newRoomData.moderatorToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); @@ -226,7 +325,7 @@ describe('Room API Security Tests', () => { it('should succeed when participant is speaker', async () => { const response = await request(app) .get(`${ROOMS_PATH}/${roomData.room.roomId}/config`) - .set('Cookie', roomData.speakerCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, roomData.speakerToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.SPEAKER); expect(response.status).toBe(200); }); @@ -236,7 +335,7 @@ describe('Room API Security Tests', () => { const response = await request(app) .get(`${ROOMS_PATH}/${roomData.room.roomId}/config`) - .set('Cookie', newRoomData.speakerCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_TOKEN_HEADER, newRoomData.speakerToken) .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.SPEAKER); expect(response.status).toBe(403); }); @@ -268,11 +367,28 @@ describe('Room API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .put(`${ROOMS_PATH}/${roomId}/config`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .send({ config: roomConfig }); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as admin via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app) .put(`${ROOMS_PATH}/${roomId}/config`) .set('Cookie', adminCookie) .send({ config: roomConfig }); expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when user is not authenticated', async () => { @@ -298,11 +414,28 @@ describe('Room API Security Tests', () => { }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .put(`${ROOMS_PATH}/${roomId}/status`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .send({ status: 'open' }); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as admin via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app) .put(`${ROOMS_PATH}/${roomId}/status`) .set('Cookie', adminCookie) .send({ status: 'open' }); expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when user is not authenticated', async () => { @@ -352,11 +485,30 @@ describe('Room API Security Tests', () => { it('should succeed when authentication is required for moderator, participant is moderator and authenticated', async () => { await changeSecurityConfig(AuthMode.MODERATORS_ONLY); + const response = await request(app) + .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .send({ secret: roomData.moderatorSecret }); + expect(response.status).toBe(200); + }); + + it('should succeed when authentication is required for moderator, participant is moderator and authenticated via cookie', async () => { + await changeSecurityConfig(AuthMode.MODERATORS_ONLY); + + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) .set('Cookie', adminCookie) .send({ secret: roomData.moderatorSecret }); expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when authentication is required for moderator and participant is moderator but not authenticated', async () => { @@ -373,7 +525,7 @@ describe('Room API Security Tests', () => { const response = await request(app) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) - .set('Cookie', adminCookie) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) .send({ secret: roomData.speakerSecret }); expect(response.status).toBe(200); }); @@ -392,7 +544,7 @@ describe('Room API Security Tests', () => { const response = await request(app) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) - .set('Cookie', adminCookie) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) .send({ secret: roomData.moderatorSecret }); expect(response.status).toBe(200); }); diff --git a/backend/tests/integration/api/security/user-security.test.ts b/backend/tests/integration/api/security/user-security.test.ts index 00a8802..216e257 100644 --- a/backend/tests/integration/api/security/user-security.test.ts +++ b/backend/tests/integration/api/security/user-security.test.ts @@ -3,7 +3,13 @@ import { Express } from 'express'; import request from 'supertest'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import { MEET_INITIAL_ADMIN_PASSWORD } from '../../../../src/environment.js'; -import { changePassword, loginUser, startTestServer } from '../../../helpers/request-helpers.js'; +import { AuthTransportMode } from '../../../../src/typings/ce/index.js'; +import { + changeAuthTransportMode, + changePassword, + loginUser, + startTestServer +} from '../../../helpers/request-helpers.js'; const USERS_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users`; @@ -15,15 +21,31 @@ describe('User API Security Tests', () => { }); describe('Profile Tests', () => { - let adminCookie: string; + let adminAccessToken: string; beforeAll(async () => { - adminCookie = await loginUser(); + adminAccessToken = await loginUser(); }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .get(`${USERS_PATH}/profile`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as admin via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app).get(`${USERS_PATH}/profile`).set('Cookie', adminCookie); expect(response.status).toBe(200); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when user is not authenticated', async () => { @@ -38,13 +60,30 @@ describe('User API Security Tests', () => { newPassword: 'newpassword123' }; - let adminCookie: string; + let adminAccessToken: string; beforeAll(async () => { - adminCookie = await loginUser(); + adminAccessToken = await loginUser(); }); it('should succeed when user is authenticated as admin', async () => { + const response = await request(app) + .post(`${USERS_PATH}/change-password`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .send(changePasswordRequest); + expect(response.status).toBe(200); + + // Reset password + await changePassword(changePasswordRequest.newPassword, MEET_INITIAL_ADMIN_PASSWORD, adminAccessToken); + }); + + it('should succeed when user is authenticated as admin via cookie', async () => { + // Set auth transport mode to cookie + await changeAuthTransportMode(AuthTransportMode.COOKIE); + + // Login as admin to get access token cookie + const adminCookie = await loginUser(); + const response = await request(app) .post(`${USERS_PATH}/change-password`) .set('Cookie', adminCookie) @@ -53,6 +92,9 @@ describe('User API Security Tests', () => { // Reset password await changePassword(changePasswordRequest.newPassword, MEET_INITIAL_ADMIN_PASSWORD, adminCookie); + + // Revert auth transport mode to header + await changeAuthTransportMode(AuthTransportMode.HEADER); }); it('should fail when user is not authenticated', async () => { diff --git a/backend/tests/integration/api/users/change-password.test.ts b/backend/tests/integration/api/users/change-password.test.ts index 13dc8a2..14f1000 100644 --- a/backend/tests/integration/api/users/change-password.test.ts +++ b/backend/tests/integration/api/users/change-password.test.ts @@ -4,31 +4,31 @@ import { expectValidationError } from '../../../helpers/assertion-helpers.js'; import { changePassword, loginUser, startTestServer } from '../../../helpers/request-helpers.js'; describe('Users API Tests', () => { - let adminCookie: string; + let adminAccessToken: string; beforeAll(async () => { startTestServer(); - adminCookie = await loginUser(); + adminAccessToken = await loginUser(); }); describe('Change Password Tests', () => { it('should successfully change password', async () => { const newPassword = 'newpassword123'; - const response = await changePassword(MEET_INITIAL_ADMIN_PASSWORD, newPassword, adminCookie); + const response = await changePassword(MEET_INITIAL_ADMIN_PASSWORD, newPassword, adminAccessToken); expect(response.status).toBe(200); // Reset password - await changePassword(newPassword, MEET_INITIAL_ADMIN_PASSWORD, adminCookie); + await changePassword(newPassword, MEET_INITIAL_ADMIN_PASSWORD, adminAccessToken); }); it('should fail when current password is incorrect', async () => { - const response = await changePassword('wrongpassword', 'newpassword123', adminCookie); + const response = await changePassword('wrongpassword', 'newpassword123', adminAccessToken); expect(response.status).toBe(400); expect(response.body).toHaveProperty('message', 'Invalid current password'); }); it('should fail when new password is not 5 characters long', async () => { - const response = await changePassword(MEET_INITIAL_ADMIN_PASSWORD, '1234', adminCookie); + const response = await changePassword(MEET_INITIAL_ADMIN_PASSWORD, '1234', adminAccessToken); expectValidationError(response, 'newPassword', 'New password must be at least 5 characters long'); }); }); diff --git a/backend/tests/integration/api/users/get-profile.test.ts b/backend/tests/integration/api/users/get-profile.test.ts index 79a19a8..4fe5940 100644 --- a/backend/tests/integration/api/users/get-profile.test.ts +++ b/backend/tests/integration/api/users/get-profile.test.ts @@ -2,16 +2,16 @@ import { beforeAll, describe, expect, it } from '@jest/globals'; import { getProfile, loginUser, startTestServer } from '../../../helpers/request-helpers.js'; describe('Users API Tests', () => { - let adminCookie: string; + let adminAccessToken: string; beforeAll(async () => { startTestServer(); - adminCookie = await loginUser(); + adminAccessToken = await loginUser(); }); describe('Profile Tests', () => { it('should return 200 and admin profile', async () => { - const response = await getProfile(adminCookie); + const response = await getProfile(adminAccessToken); expect(response.status).toBe(200); expect(response.body).toHaveProperty('username'); expect(response.body.username).toBe('admin'); diff --git a/backend/tests/integration/webhooks/webhook.test.ts b/backend/tests/integration/webhooks/webhook.test.ts index 8950ef7..4089bb5 100644 --- a/backend/tests/integration/webhooks/webhook.test.ts +++ b/backend/tests/integration/webhooks/webhook.test.ts @@ -91,10 +91,10 @@ describe('Webhook Integration Tests', () => { it('should send meeting_ended webhook when meeting is closed', async () => { const context = await setupSingleRoom(true); const roomData = context.room; - const moderatorCookie = context.moderatorCookie; + const moderatorToken = context.moderatorToken; // Close the room - await endMeeting(roomData.roomId, moderatorCookie); + await endMeeting(roomData.roomId, moderatorToken); // Wait for the room to be closed await sleep('1s');