diff --git a/backend/tests/helpers/assertion-helpers.ts b/backend/tests/helpers/assertion-helpers.ts index ce9d292..0df4c5d 100644 --- a/backend/tests/helpers/assertion-helpers.ts +++ b/backend/tests/helpers/assertion-helpers.ts @@ -1,5 +1,7 @@ import { expect } from '@jest/globals'; +import { container } from '../../src/config/dependency-injector.config'; import INTERNAL_CONFIG from '../../src/config/internal-config'; +import { TokenService } from '../../src/services'; import { MeetRecordingAccess, MeetRecordingInfo, @@ -521,7 +523,8 @@ export const expectValidParticipantTokenResponse = ( response: any, roomId: string, participantName: string, - participantRole: ParticipantRole + participantRole: ParticipantRole, + otherRoles: ParticipantRole[] = [] ) => { expect(response.status).toBe(200); expect(response.body).toHaveProperty('token'); @@ -530,13 +533,24 @@ export const expectValidParticipantTokenResponse = ( const decodedToken = decodeJWTToken(token); const permissions = getPermissions(roomId, participantRole); + const rolesAndPermissions = otherRoles.map((role) => ({ + role, + permissions: getPermissions(roomId, role).openvidu + })); + + if (!rolesAndPermissions.some((r) => r.role === participantRole)) { + rolesAndPermissions.push({ + role: participantRole, + permissions: permissions.openvidu + }); + } expect(decodedToken).toHaveProperty('sub', participantName); expect(decodedToken).toHaveProperty('video', permissions.livekit); expect(decodedToken).toHaveProperty('metadata'); - const metadata = JSON.parse(decodedToken.metadata); - expect(metadata).toHaveProperty('role', participantRole); - expect(metadata).toHaveProperty('permissions', permissions.openvidu); + const metadata = JSON.parse(decodedToken.metadata || '{}'); + expect(metadata).toHaveProperty('roles'); + expect(metadata.roles).toEqual(expect.arrayContaining(rolesAndPermissions)); // Check that the token is included in a cookie expect(response.headers['set-cookie']).toBeDefined(); @@ -568,7 +582,7 @@ export const expectValidRecordingTokenResponse = ( room: roomId }); expect(decodedToken).toHaveProperty('metadata'); - const metadata = JSON.parse(decodedToken.metadata); + const metadata = JSON.parse(decodedToken.metadata || '{}'); expect(metadata).toHaveProperty('role', participantRole); expect(metadata).toHaveProperty('recordingPermissions', { canRetrieveRecordings, @@ -589,13 +603,6 @@ export const expectValidRecordingTokenResponse = ( }; const decodeJWTToken = (token: string) => { - const base64Url = token.split('.')[1]; - const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); - const jsonPayload = decodeURIComponent( - atob(base64) - .split('') - .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) - .join('') - ); - return JSON.parse(jsonPayload); + const tokenService = container.get(TokenService); + return tokenService.getClaimsIgnoringExpiration(token); }; diff --git a/backend/tests/helpers/request-helpers.ts b/backend/tests/helpers/request-helpers.ts index ee358d4..7221086 100644 --- a/backend/tests/helpers/request-helpers.ts +++ b/backend/tests/helpers/request-helpers.ts @@ -21,6 +21,7 @@ import { MeetRecordingAccess, MeetRoom, MeetRoomOptions, + ParticipantRole, WebhookPreferences } from '../../src/typings/ce/index.js'; @@ -233,12 +234,13 @@ export const getRoom = async (roomId: string, fields?: string) => { .query({ fields }); }; -export const getRoomPreferences = async (roomId: string, cookie: string) => { +export const getRoomPreferences = async (roomId: string, cookie: string, role: ParticipantRole) => { checkAppIsRunning(); return await request(app) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/rooms/${roomId}/preferences`) .set('Cookie', cookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, role) .send(); }; @@ -357,7 +359,7 @@ export const getRoomRoleBySecret = async (roomId: string, secret: string) => { return response; }; -export const generateParticipantToken = async (participantOptions: any) => { +export const generateParticipantToken = async (participantOptions: any, cookie?: string) => { checkAppIsRunning(); // Disable authentication to generate the token @@ -366,6 +368,7 @@ export const generateParticipantToken = async (participantOptions: any) => { // 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; }; @@ -376,14 +379,18 @@ export const generateParticipantToken = async (participantOptions: any) => { export const generateParticipantTokenCookie = async ( roomId: string, participantName: string, - secret: string + secret: string, + cookie?: string ): Promise => { // Generate the participant token - const response = await generateParticipantToken({ - roomId, - participantName, - secret - }); + const response = await generateParticipantToken( + { + roomId, + participantName, + secret + }, + cookie + ); expect(response.status).toBe(200); // Return the participant token cookie @@ -394,7 +401,7 @@ export const generateParticipantTokenCookie = async ( return participantTokenCookie; }; -export const refreshParticipantToken = async (participantOptions: any) => { +export const refreshParticipantToken = async (participantOptions: any, cookie: string) => { checkAppIsRunning(); // Disable authentication to generate the token @@ -402,6 +409,7 @@ export const refreshParticipantToken = async (participantOptions: any) => { const response = await request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants/token/refresh`) + .set('Cookie', cookie) .send(participantOptions); return response; }; @@ -496,6 +504,7 @@ export const deleteParticipant = async (roomId: string, participantName: string, const response = await request(app) .delete(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings/${roomId}/participants/${participantName}`) .set('Cookie', moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) .send(); return response; }; @@ -506,6 +515,7 @@ export const endMeeting = async (roomId: string, moderatorCookie: string) => { const response = await request(app) .delete(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings/${roomId}`) .set('Cookie', moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) .send(); await sleep('1s'); return response; @@ -547,6 +557,7 @@ export const startRecording = async (roomId: string, moderatorCookie = '') => { return await request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings`) .set('Cookie', moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) .send({ roomId }); @@ -558,6 +569,7 @@ export const stopRecording = async (recordingId: string, moderatorCookie = '') = const response = await request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings/${recordingId}/stop`) .set('Cookie', moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR) .send(); await sleep('2.5s'); diff --git a/backend/tests/integration/api/participants/generate-token.test.ts b/backend/tests/integration/api/participants/generate-token.test.ts index 46fd22b..2c857eb 100644 --- a/backend/tests/integration/api/participants/generate-token.test.ts +++ b/backend/tests/integration/api/participants/generate-token.test.ts @@ -5,6 +5,7 @@ import { deleteAllRooms, disconnectFakeParticipants, generateParticipantToken, + generateParticipantTokenCookie, startTestServer } from '../../../helpers/request-helpers.js'; import { RoomData, setupSingleRoom } from '../../../helpers/test-scenarios.js'; @@ -53,6 +54,30 @@ describe('Participant API Tests', () => { ); }); + it(`should generate a participant token with both publisher and moderator permissions + when using the publisher secret after having a moderator token`, async () => { + const moderatorCookie = await generateParticipantTokenCookie( + roomData.room.roomId, + `${participantName}_MODERATOR`, + roomData.moderatorSecret + ); + const publisherResponse = await generateParticipantToken( + { + roomId: roomData.room.roomId, + participantName: `${participantName}_PUBLISHER`, + secret: roomData.publisherSecret + }, + moderatorCookie + ); + expectValidParticipantTokenResponse( + publisherResponse, + roomData.room.roomId, + `${participantName}_PUBLISHER`, + ParticipantRole.PUBLISHER, + [ParticipantRole.MODERATOR] + ); + }); + it('should fail with 409 when participant already exists in the room', async () => { roomData = await setupSingleRoom(true); const response = await generateParticipantToken({ diff --git a/backend/tests/integration/api/participants/refresh-token.test.ts b/backend/tests/integration/api/participants/refresh-token.test.ts index 5aabd9d..a052fec 100644 --- a/backend/tests/integration/api/participants/refresh-token.test.ts +++ b/backend/tests/integration/api/participants/refresh-token.test.ts @@ -1,10 +1,12 @@ import { afterAll, beforeAll, describe, expect, it } from '@jest/globals'; +import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import { ParticipantRole } from '../../../../src/typings/ce/participant.js'; import { expectValidationError, expectValidParticipantTokenResponse } from '../../../helpers/assertion-helpers.js'; import { deleteAllRooms, disconnectFakeParticipants, refreshParticipantToken, + sleep, startTestServer } from '../../../helpers/request-helpers.js'; import { RoomData, setupSingleRoom } from '../../../helpers/test-scenarios.js'; @@ -16,7 +18,16 @@ describe('Participant API Tests', () => { beforeAll(async () => { startTestServer(); + + // Set short expiration for testing + const initialTokenExpiration = INTERNAL_CONFIG.PARTICIPANT_TOKEN_EXPIRATION; + INTERNAL_CONFIG.PARTICIPANT_TOKEN_EXPIRATION = '1s'; + roomData = await setupSingleRoom(true); + await sleep('2s'); // Ensure the token is expired + + // Restore original expiration after setup + INTERNAL_CONFIG.PARTICIPANT_TOKEN_EXPIRATION = initialTokenExpiration; }); afterAll(async () => { @@ -26,11 +37,14 @@ describe('Participant API Tests', () => { describe('Refresh Participant Token Tests', () => { it('should refresh participant token with moderator permissions when using the moderator secret', async () => { - const response = await refreshParticipantToken({ - roomId: roomData.room.roomId, - participantName, - secret: roomData.moderatorSecret - }); + const response = await refreshParticipantToken( + { + roomId: roomData.room.roomId, + participantName, + secret: roomData.moderatorSecret + }, + roomData.moderatorCookie + ); expectValidParticipantTokenResponse( response, roomData.room.roomId, @@ -40,11 +54,14 @@ describe('Participant API Tests', () => { }); it('should refresh participant token with publisher permissions when using the publisher secret', async () => { - const response = await refreshParticipantToken({ - roomId: roomData.room.roomId, - participantName, - secret: roomData.publisherSecret - }); + const response = await refreshParticipantToken( + { + roomId: roomData.room.roomId, + participantName, + secret: roomData.publisherSecret + }, + roomData.publisherCookie + ); expectValidParticipantTokenResponse( response, roomData.room.roomId, @@ -53,78 +70,126 @@ describe('Participant API Tests', () => { ); }); - it('should fail with 404 when participant does not exist in the room', async () => { - roomData = await setupSingleRoom(); - const response = await refreshParticipantToken({ - roomId: roomData.room.roomId, - participantName, - secret: roomData.moderatorSecret - }); - expect(response.status).toBe(404); + it('should fail with 400 when secret is invalid', async () => { + const response = await refreshParticipantToken( + { + roomId: roomData.room.roomId, + participantName, + secret: 'invalid_secret' + }, + roomData.moderatorCookie + ); + expect(response.status).toBe(400); + }); - // Recreate the room with participant - roomData = await setupSingleRoom(true); + it('should fail with 400 when previous token is not provided', async () => { + const response = await refreshParticipantToken( + { + roomId: roomData.room.roomId, + participantName, + secret: roomData.moderatorSecret + }, + '' + ); + expect(response.status).toBe(400); + expect(response.body.message).toBe('No participant token provided'); + }); + + it('should fail with 404 when participant does not exist in the room', async () => { + const newRoomData = await setupSingleRoom(); + const response = await refreshParticipantToken( + { + roomId: newRoomData.room.roomId, + participantName, + secret: newRoomData.moderatorSecret + }, + roomData.moderatorCookie + ); + expect(response.status).toBe(404); }); it('should fail with 404 when room does not exist', async () => { - const response = await refreshParticipantToken({ - roomId: 'non_existent_room', - participantName, - secret: roomData.moderatorSecret - }); + const response = await refreshParticipantToken( + { + roomId: 'non_existent_room', + participantName, + secret: roomData.moderatorSecret + }, + roomData.moderatorCookie + ); expect(response.status).toBe(404); }); - it('should fail with 400 when secret is invalid', async () => { - const response = await refreshParticipantToken({ - roomId: roomData.room.roomId, - participantName, - secret: 'invalid_secret' - }); - expect(response.status).toBe(400); + it('should fail with 409 when participant token is still valid', async () => { + const newRoomData = await setupSingleRoom(true); + const response = await refreshParticipantToken( + { + roomId: newRoomData.room.roomId, + participantName, + secret: newRoomData.moderatorSecret + }, + newRoomData.moderatorCookie + ); + expect(response.status).toBe(409); + expect(response.body.message).toBe('Participant token is still valid'); }); }); describe('Refresh Participant Token Validation Tests', () => { it('should fail when roomId is not provided', async () => { - const response = await refreshParticipantToken({ - participantName, - secret: roomData.moderatorSecret - }); + const response = await refreshParticipantToken( + { + participantName, + secret: roomData.moderatorSecret + }, + roomData.moderatorCookie + ); expectValidationError(response, 'roomId', 'Required'); }); it('should fail when participantName is not provided', async () => { - const response = await refreshParticipantToken({ - roomId: roomData.room.roomId, - secret: roomData.moderatorSecret - }); + const response = await refreshParticipantToken( + { + roomId: roomData.room.roomId, + secret: roomData.moderatorSecret + }, + roomData.moderatorCookie + ); expectValidationError(response, 'participantName', 'Required'); }); it('should fail when secret is not provided', async () => { - const response = await refreshParticipantToken({ - roomId: roomData.room.roomId, - participantName - }); + const response = await refreshParticipantToken( + { + roomId: roomData.room.roomId, + participantName + }, + roomData.moderatorCookie + ); expectValidationError(response, 'secret', 'Required'); }); it('should fail when participantName is empty', async () => { - const response = await refreshParticipantToken({ - roomId: roomData.room.roomId, - participantName: '', - secret: roomData.moderatorSecret - }); + const response = await refreshParticipantToken( + { + roomId: roomData.room.roomId, + participantName: '', + secret: roomData.moderatorSecret + }, + roomData.moderatorCookie + ); expectValidationError(response, 'participantName', 'Participant name is required'); }); it('should fail when secret is empty', async () => { - const response = await refreshParticipantToken({ - roomId: roomData.room.roomId, - participantName, - secret: '' - }); + const response = await refreshParticipantToken( + { + roomId: roomData.room.roomId, + participantName, + secret: '' + }, + roomData.moderatorCookie + ); expectValidationError(response, 'secret', 'Secret is required'); }); }); diff --git a/backend/tests/integration/api/rooms/get-room-preferences.test.ts b/backend/tests/integration/api/rooms/get-room-preferences.test.ts index 2a510d5..ace1795 100644 --- a/backend/tests/integration/api/rooms/get-room-preferences.test.ts +++ b/backend/tests/integration/api/rooms/get-room-preferences.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeAll, describe, it } from '@jest/globals'; -import { MeetRecordingAccess } from '../../../../src/typings/ce/index.js'; +import { MeetRecordingAccess, ParticipantRole } from '../../../../src/typings/ce/index.js'; import { expectSuccessRoomPreferencesResponse } from '../../../helpers/assertion-helpers.js'; import { deleteAllRooms, getRoomPreferences, startTestServer } from '../../../helpers/request-helpers.js'; import { setupSingleRoom } from '../../../helpers/test-scenarios.js'; @@ -29,7 +29,7 @@ describe('Room API Tests', () => { const roomId = roomData.room.roomId; const cookie = roomData.moderatorCookie; - const response = await getRoomPreferences(roomId, cookie); + const response = await getRoomPreferences(roomId, cookie, ParticipantRole.MODERATOR); expectSuccessRoomPreferencesResponse(response, DEFAULT_PREFERENCES); }); @@ -50,7 +50,7 @@ describe('Room API Tests', () => { const roomId = roomData.room.roomId; const cookie = roomData.moderatorCookie; - const response = await getRoomPreferences(roomId, cookie); + const response = await getRoomPreferences(roomId, cookie, ParticipantRole.MODERATOR); expectSuccessRoomPreferencesResponse(response, payload.preferences); }); }); diff --git a/backend/tests/integration/api/security/meeting-security.test.ts b/backend/tests/integration/api/security/meeting-security.test.ts index fb1b81b..bc0f745 100644 --- a/backend/tests/integration/api/security/meeting-security.test.ts +++ b/backend/tests/integration/api/security/meeting-security.test.ts @@ -3,6 +3,7 @@ import { Express } from 'express'; import request from 'supertest'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import { MEET_API_KEY } from '../../../../src/environment.js'; +import { ParticipantRole } from '../../../../src/typings/ce'; import { deleteAllRooms, disconnectFakeParticipants, @@ -50,7 +51,8 @@ describe('Meeting API Security Tests', () => { it('should succeed when participant is moderator', async () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) - .set('Cookie', roomData.moderatorCookie); + .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(200); }); @@ -59,14 +61,16 @@ describe('Meeting API Security Tests', () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) - .set('Cookie', newRoomData.moderatorCookie); + .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); it('should fail when participant is publisher', async () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) - .set('Cookie', roomData.publisherCookie); + .set('Cookie', roomData.publisherCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.PUBLISHER); expect(response.status).toBe(403); }); }); @@ -91,7 +95,8 @@ describe('Meeting API Security Tests', () => { it('should succeed when participant is moderator', async () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}`) - .set('Cookie', roomData.moderatorCookie); + .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(200); }); @@ -100,14 +105,16 @@ describe('Meeting API Security Tests', () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}`) - .set('Cookie', newRoomData.moderatorCookie); + .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); it('should fail when participant is publisher', async () => { const response = await request(app) .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}`) - .set('Cookie', roomData.publisherCookie); + .set('Cookie', roomData.publisherCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.PUBLISHER); 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 57baaa3..e4e5885 100644 --- a/backend/tests/integration/api/security/participant-security.test.ts +++ b/backend/tests/integration/api/security/participant-security.test.ts @@ -8,6 +8,7 @@ import { deleteAllRooms, disconnectFakeParticipants, loginUser, + sleep, startTestServer } from '../../../helpers/request-helpers.js'; import { RoomData, setupSingleRoom } from '../../../helpers/test-scenarios.js'; @@ -141,39 +142,56 @@ describe('Participant API Security Tests', () => { let roomData: RoomData; beforeAll(async () => { + // Set short expiration for testing + const initialTokenExpiration = INTERNAL_CONFIG.PARTICIPANT_TOKEN_EXPIRATION; + INTERNAL_CONFIG.PARTICIPANT_TOKEN_EXPIRATION = '1s'; + roomData = await setupSingleRoom(true); + await sleep('2s'); // Ensure the token is expired + + // Restore original expiration after setup + INTERNAL_CONFIG.PARTICIPANT_TOKEN_EXPIRATION = initialTokenExpiration; }); it('should succeed when no authentication is required and participant is publisher', async () => { await changeSecurityPreferences(AuthMode.NONE); - const response = await request(app).post(`${PARTICIPANTS_PATH}/token/refresh`).send({ - roomId: roomData.room.roomId, - participantName: PARTICIPANT_NAME, - secret: roomData.publisherSecret - }); + const response = await request(app) + .post(`${PARTICIPANTS_PATH}/token/refresh`) + .set('Cookie', roomData.publisherCookie) + .send({ + roomId: roomData.room.roomId, + participantName: PARTICIPANT_NAME, + secret: roomData.publisherSecret + }); expect(response.status).toBe(200); }); it('should succeed when no authentication is required and participant is moderator', async () => { await changeSecurityPreferences(AuthMode.NONE); - const response = await request(app).post(`${PARTICIPANTS_PATH}/token/refresh`).send({ - roomId: roomData.room.roomId, - participantName: PARTICIPANT_NAME, - secret: roomData.moderatorSecret - }); + const response = await request(app) + .post(`${PARTICIPANTS_PATH}/token/refresh`) + .set('Cookie', roomData.moderatorCookie) + .send({ + roomId: roomData.room.roomId, + participantName: PARTICIPANT_NAME, + secret: roomData.moderatorSecret + }); expect(response.status).toBe(200); }); it('should succeed when authentication is required for moderator and participant is publisher', async () => { await changeSecurityPreferences(AuthMode.MODERATORS_ONLY); - const response = await request(app).post(`${PARTICIPANTS_PATH}/token/refresh`).send({ - roomId: roomData.room.roomId, - participantName: PARTICIPANT_NAME, - secret: roomData.publisherSecret - }); + const response = await request(app) + .post(`${PARTICIPANTS_PATH}/token/refresh`) + .set('Cookie', roomData.publisherCookie) + .send({ + roomId: roomData.room.roomId, + participantName: PARTICIPANT_NAME, + secret: roomData.publisherSecret + }); expect(response.status).toBe(200); }); @@ -182,7 +200,7 @@ describe('Participant API Security Tests', () => { const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', adminCookie) + .set('Cookie', [adminCookie, roomData.moderatorCookie]) .send({ roomId: roomData.room.roomId, participantName: PARTICIPANT_NAME, @@ -194,11 +212,14 @@ describe('Participant API Security Tests', () => { it('should fail when authentication is required for moderator and participant is moderator but not authenticated', async () => { await changeSecurityPreferences(AuthMode.MODERATORS_ONLY); - const response = await request(app).post(`${PARTICIPANTS_PATH}/token/refresh`).send({ - roomId: roomData.room.roomId, - participantName: PARTICIPANT_NAME, - secret: roomData.moderatorSecret - }); + const response = await request(app) + .post(`${PARTICIPANTS_PATH}/token/refresh`) + .set('Cookie', roomData.moderatorCookie) + .send({ + roomId: roomData.room.roomId, + participantName: PARTICIPANT_NAME, + secret: roomData.moderatorSecret + }); expect(response.status).toBe(401); }); @@ -207,7 +228,7 @@ describe('Participant API Security Tests', () => { const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', adminCookie) + .set('Cookie', [adminCookie, roomData.publisherCookie]) .send({ roomId: roomData.room.roomId, participantName: PARTICIPANT_NAME, @@ -219,11 +240,14 @@ describe('Participant API Security Tests', () => { it('should fail when authentication is required for all users and participant is publisher but not authenticated', async () => { await changeSecurityPreferences(AuthMode.ALL_USERS); - const response = await request(app).post(`${PARTICIPANTS_PATH}/token/refresh`).send({ - roomId: roomData.room.roomId, - participantName: PARTICIPANT_NAME, - secret: roomData.publisherSecret - }); + const response = await request(app) + .post(`${PARTICIPANTS_PATH}/token/refresh`) + .set('Cookie', roomData.publisherCookie) + .send({ + roomId: roomData.room.roomId, + participantName: PARTICIPANT_NAME, + secret: roomData.publisherSecret + }); expect(response.status).toBe(401); }); @@ -232,7 +256,7 @@ describe('Participant API Security Tests', () => { const response = await request(app) .post(`${PARTICIPANTS_PATH}/token/refresh`) - .set('Cookie', adminCookie) + .set('Cookie', [adminCookie, roomData.moderatorCookie]) .send({ roomId: roomData.room.roomId, participantName: PARTICIPANT_NAME, @@ -244,11 +268,14 @@ describe('Participant API Security Tests', () => { it('should fail when authentication is required for all users and participant is moderator but not authenticated', async () => { await changeSecurityPreferences(AuthMode.ALL_USERS); - const response = await request(app).post(`${PARTICIPANTS_PATH}/token/refresh`).send({ - roomId: roomData.room.roomId, - participantName: PARTICIPANT_NAME, - secret: roomData.moderatorSecret - }); + const response = await request(app) + .post(`${PARTICIPANTS_PATH}/token/refresh`) + .set('Cookie', roomData.moderatorCookie) + .send({ + roomId: roomData.room.roomId, + participantName: PARTICIPANT_NAME, + secret: roomData.moderatorSecret + }); expect(response.status).toBe(401); }); }); diff --git a/backend/tests/integration/api/security/recording-security.test.ts b/backend/tests/integration/api/security/recording-security.test.ts index 4582159..4a78b45 100644 --- a/backend/tests/integration/api/security/recording-security.test.ts +++ b/backend/tests/integration/api/security/recording-security.test.ts @@ -3,7 +3,7 @@ import { Express } from 'express'; import request from 'supertest'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import { MEET_API_KEY } from '../../../../src/environment.js'; -import { MeetRecordingAccess } from '../../../../src/typings/ce/index.js'; +import { MeetRecordingAccess, ParticipantRole } from '../../../../src/typings/ce/index.js'; import { expectValidStopRecordingResponse } from '../../../helpers/assertion-helpers.js'; import { deleteAllRecordings, @@ -63,7 +63,8 @@ describe('Recording API Security Tests', () => { const response = await request(app) .post(INTERNAL_RECORDINGS_PATH) .send({ roomId: roomData.room.roomId }) - .set('Cookie', roomData.moderatorCookie); + .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(201); // Stop recording to clean up @@ -78,7 +79,8 @@ describe('Recording API Security Tests', () => { const response = await request(app) .post(INTERNAL_RECORDINGS_PATH) .send({ roomId: roomData.room.roomId }) - .set('Cookie', newRoomData.moderatorCookie); + .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); @@ -86,7 +88,8 @@ describe('Recording API Security Tests', () => { const response = await request(app) .post(INTERNAL_RECORDINGS_PATH) .send({ roomId: roomData.room.roomId }) - .set('Cookie', roomData.publisherCookie); + .set('Cookie', roomData.publisherCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.PUBLISHER); expect(response.status).toBe(403); }); }); @@ -119,7 +122,8 @@ describe('Recording API Security Tests', () => { 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('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(202); }); @@ -128,14 +132,16 @@ describe('Recording API Security Tests', () => { const response = await request(app) .post(`${INTERNAL_RECORDINGS_PATH}/${roomData.recordingId}/stop`) - .set('Cookie', newRoomData.moderatorCookie); + .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); it('should fail when participant is publisher', async () => { const response = await request(app) .post(`${INTERNAL_RECORDINGS_PATH}/${roomData.recordingId}/stop`) - .set('Cookie', roomData.publisherCookie); + .set('Cookie', roomData.publisherCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.PUBLISHER); expect(response.status).toBe(403); }); }); diff --git a/backend/tests/integration/api/security/room-security.test.ts b/backend/tests/integration/api/security/room-security.test.ts index 2dc3244..36396e6 100644 --- a/backend/tests/integration/api/security/room-security.test.ts +++ b/backend/tests/integration/api/security/room-security.test.ts @@ -3,7 +3,7 @@ import { Express } from 'express'; import request from 'supertest'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import { MEET_API_KEY } from '../../../../src/environment.js'; -import { AuthMode, MeetRecordingAccess } from '../../../../src/typings/ce/index.js'; +import { AuthMode, MeetRecordingAccess, ParticipantRole } from '../../../../src/typings/ce/index.js'; import { changeSecurityPreferences, createRoom, @@ -127,7 +127,8 @@ 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('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(200); }); @@ -136,14 +137,16 @@ describe('Room API Security Tests', () => { const response = await request(app) .get(`${ROOMS_PATH}/${roomData.room.roomId}`) - .set('Cookie', newRoomData.moderatorCookie); + .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); it('should fail when participant is publisher', async () => { const response = await request(app) .get(`${ROOMS_PATH}/${roomData.room.roomId}`) - .set('Cookie', roomData.publisherCookie); + .set('Cookie', roomData.publisherCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.PUBLISHER); expect(response.status).toBe(403); }); }); @@ -227,7 +230,7 @@ describe('Room API Security Tests', () => { expect(response.status).toBe(401); }); - it('should fai when user is authenticated as admin', async () => { + it('should fail when user is authenticated as admin', async () => { const response = await request(app) .get(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/preferences`) .set('Cookie', adminCookie); @@ -242,7 +245,8 @@ describe('Room API Security Tests', () => { it('should succeed when participant is moderator', async () => { const response = await request(app) .get(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/preferences`) - .set('Cookie', roomData.moderatorCookie); + .set('Cookie', roomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(200); }); @@ -251,14 +255,16 @@ describe('Room API Security Tests', () => { const response = await request(app) .get(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/preferences`) - .set('Cookie', newRoomData.moderatorCookie); + .set('Cookie', newRoomData.moderatorCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.MODERATOR); expect(response.status).toBe(403); }); it('should succeed when participant is publisher', async () => { const response = await request(app) .get(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/preferences`) - .set('Cookie', roomData.publisherCookie); + .set('Cookie', roomData.publisherCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.PUBLISHER); expect(response.status).toBe(200); }); @@ -267,7 +273,8 @@ describe('Room API Security Tests', () => { const response = await request(app) .get(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/preferences`) - .set('Cookie', newRoomData.publisherCookie); + .set('Cookie', newRoomData.publisherCookie) + .set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.PUBLISHER); expect(response.status).toBe(403); }); });