test: refactor authentication in integration tests to use header-based access tokens; add coverage for cookie-based mode

This commit is contained in:
juancarmore 2025-10-10 18:23:47 +02:00
parent c93306b705
commit 479e94add8
35 changed files with 1380 additions and 605 deletions

View File

@ -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) => {

View File

@ -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<string> => {
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<string> => {
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<AuthTransportMode> => {
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 <token>" or the cookie string if in cookie mode
*/
export const loginUser = async (): Promise<string> => {
checkAppIsRunning();
@ -176,28 +186,56 @@ export const loginUser = async (): Promise<string> => {
.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<string, any> = {}) => {
* @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 <token>"
*/
export const generateParticipantTokenCookie = async (
export const generateParticipantToken = async (
roomId: string,
secret: string,
participantName: string,
cookie?: string
participantName: string
): Promise<string> => {
// 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 <token>"
*/
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<Response> => {
export const bulkDeleteRecordings = async (recordingIds: any[], recordingToken?: string): Promise<Response> => {
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<Response> => {
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<string, any> = {}) => {
.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 () => {

View File

@ -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<RoomData> => {
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;
}

View File

@ -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');

View File

@ -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);
});
});

View File

@ -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');
});

View File

@ -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');

View File

@ -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

View File

@ -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');
});

View File

@ -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>(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);

View File

@ -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);

View File

@ -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');

View File

@ -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 () => {

View File

@ -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 () => {

View File

@ -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);
});

View File

@ -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,

View File

@ -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 = ' .<!?' + room.roomId + ' ';
const response = await startRecording(malformedRoomId, moderatorCookie);
const response = await startRecording(malformedRoomId, moderatorToken);
console.log('Response:', response.body);
expect(response.status).toBe(409);
@ -159,25 +159,25 @@ describe('Recording API Tests', () => {
});
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({

View File

@ -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');

View File

@ -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(

View File

@ -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');
});

View File

@ -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(

View File

@ -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();

View File

@ -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);
});
});

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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);
});

View File

@ -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,

View File

@ -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);
});
});

View File

@ -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);
});

View File

@ -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 () => {

View File

@ -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');
});
});

View File

@ -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');

View File

@ -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');