From 6a350b07a5cf0485ef465015149bbab3a8e2edfb Mon Sep 17 00:00:00 2001 From: juancarmore Date: Wed, 28 Jan 2026 18:04:11 +0100 Subject: [PATCH] test: enhance analytics, API key, global config, meeting, and user API security tests with user role and permissions validations --- .../backend/tests/helpers/request-helpers.ts | 34 +- .../api/security/analytics-security.test.ts | 30 +- .../api/security/api-key-security.test.ts | 68 ++- .../security/global-config-security.test.ts | 90 ++- .../api/security/meeting-security.test.ts | 163 ++++-- .../api/security/user-security.test.ts | 513 +++++++++++++++++- 6 files changed, 769 insertions(+), 129 deletions(-) diff --git a/meet-ce/backend/tests/helpers/request-helpers.ts b/meet-ce/backend/tests/helpers/request-helpers.ts index 3ee9389c..90fe81a2 100644 --- a/meet-ce/backend/tests/helpers/request-helpers.ts +++ b/meet-ce/backend/tests/helpers/request-helpers.ts @@ -55,7 +55,7 @@ export const startTestServer = async (): Promise => { export const generateApiKey = async (): Promise => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); const response = await request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/api-keys`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -68,7 +68,7 @@ export const generateApiKey = async (): Promise => { export const getApiKeys = async () => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); const response = await request(app) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/api-keys`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -79,7 +79,7 @@ export const getApiKeys = async () => { export const deleteApiKeys = async () => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); const response = await request(app) .delete(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/api-keys`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -114,7 +114,7 @@ export const getRoomsAppearanceConfig = async () => { export const updateRoomsAppearanceConfig = async (config: { appearance: MeetAppearanceConfig }) => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); const response = await request(app) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/rooms/appearance`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -125,7 +125,7 @@ export const updateRoomsAppearanceConfig = async (config: { appearance: MeetAppe export const getWebbhookConfig = async () => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); const response = await request(app) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/webhooks`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -136,7 +136,7 @@ export const getWebbhookConfig = async () => { export const updateWebbhookConfig = async (config: WebhookConfig) => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); const response = await request(app) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/webhooks`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -164,7 +164,7 @@ export const getSecurityConfig = async () => { export const updateSecurityConfig = async (config: SecurityConfig) => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); const response = await request(app) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/security`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -199,9 +199,9 @@ export const loginUser = async (userId: string, password: string): Promise" + * Logs in the root admin user and returns the access token in the format "Bearer " */ -export const loginAdminUser = async (): Promise => { +export const loginRootAdmin = async (): Promise => { return loginUser(MEET_ENV.INITIAL_ADMIN_USER, MEET_ENV.INITIAL_ADMIN_PASSWORD); }; @@ -210,7 +210,7 @@ export const loginAdminUser = async (): Promise => { export const createUser = async (options: MeetUserOptions) => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); return await request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -220,7 +220,7 @@ export const createUser = async (options: MeetUserOptions) => { export const getUsers = async (query: Record = {}) => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); return await request(app) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -230,7 +230,7 @@ export const getUsers = async (query: Record = {}) => { export const getUser = async (userId: string) => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); return await request(app) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users/${userId}`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -274,7 +274,7 @@ export const changePasswordAfterFirstLogin = async ( export const resetUserPassword = async (userId: string, newPassword: string) => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); return await request(app) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users/${userId}/password`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -284,7 +284,7 @@ export const resetUserPassword = async (userId: string, newPassword: string) => export const updateUserRole = async (userId: string, role: string) => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); return await request(app) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users/${userId}/role`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -294,7 +294,7 @@ export const updateUserRole = async (userId: string, role: string) => { export const deleteUser = async (userId: string) => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); return await request(app) .delete(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users/${userId}`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -304,7 +304,7 @@ export const deleteUser = async (userId: string) => { export const bulkDeleteUsers = async (userIds: string[]) => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); return await request(app) .delete(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) @@ -905,7 +905,7 @@ export const runReleaseActiveRecordingLock = async (roomId: string) => { export const getAnalytics = async () => { checkAppIsRunning(); - const accessToken = await loginAdminUser(); + const accessToken = await loginRootAdmin(); const response = await request(app) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/analytics`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken) diff --git a/meet-ce/backend/tests/integration/api/security/analytics-security.test.ts b/meet-ce/backend/tests/integration/api/security/analytics-security.test.ts index 47f35ade..043322db 100644 --- a/meet-ce/backend/tests/integration/api/security/analytics-security.test.ts +++ b/meet-ce/backend/tests/integration/api/security/analytics-security.test.ts @@ -3,17 +3,23 @@ import { Express } from 'express'; import request from 'supertest'; import { INTERNAL_CONFIG } from '../../../../src/config/internal-config.js'; import { MEET_ENV } from '../../../../src/environment.js'; -import { loginAdminUser, startTestServer } from '../../../helpers/request-helpers.js'; +import { deleteAllUsers, startTestServer } from '../../../helpers/request-helpers.js'; +import { setupTestUsers } from '../../../helpers/test-scenarios.js'; +import { TestUsers } from '../../../interfaces/scenarios.js'; const ANALYTICS_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/analytics`; describe('Analytics API Security Tests', () => { let app: Express; - let adminAccessToken: string; + let testUsers: TestUsers; beforeAll(async () => { app = await startTestServer(); - adminAccessToken = await loginAdminUser(); + testUsers = await setupTestUsers(); + }); + + afterAll(async () => { + await deleteAllUsers(); }); describe('Get Analytics Tests', () => { @@ -24,13 +30,27 @@ describe('Analytics API Security Tests', () => { expect(response.status).toBe(401); }); - it('should succeed when user is authenticated as admin', async () => { + it('should succeed when user is authenticated as ADMIN', async () => { const response = await request(app) .get(ANALYTICS_PATH) - .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken); expect(response.status).toBe(200); }); + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .get(ANALYTICS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .get(ANALYTICS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken); + expect(response.status).toBe(403); + }); + it('should fail when user is not authenticated', async () => { const response = await request(app).get(ANALYTICS_PATH); expect(response.status).toBe(401); diff --git a/meet-ce/backend/tests/integration/api/security/api-key-security.test.ts b/meet-ce/backend/tests/integration/api/security/api-key-security.test.ts index 2a40f67f..2672c9dc 100644 --- a/meet-ce/backend/tests/integration/api/security/api-key-security.test.ts +++ b/meet-ce/backend/tests/integration/api/security/api-key-security.test.ts @@ -2,36 +2,48 @@ 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 { - generateApiKey, - loginAdminUser, - restoreDefaultApiKeys, - startTestServer -} from '../../../helpers/request-helpers.js'; +import { deleteAllUsers, generateApiKey, restoreDefaultApiKeys, startTestServer } from '../../../helpers/request-helpers.js'; +import { setupTestUsers } from '../../../helpers/test-scenarios.js'; +import { TestUsers } from '../../../interfaces/scenarios.js'; const API_KEYS_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/api-keys`; describe('API Keys API Security Tests', () => { let app: Express; - let adminAccessToken: string; + let testUsers: TestUsers; beforeAll(async () => { app = await startTestServer(); - adminAccessToken = await loginAdminUser(); + testUsers = await setupTestUsers(); }); afterAll(async () => { await restoreDefaultApiKeys(); + await deleteAllUsers(); }); describe('Create API Key', () => { - it('should succeed when user is authenticated as admin', async () => { + it('should succeed when user is authenticated as ADMIN', async () => { const response = await request(app) .post(`${API_KEYS_PATH}`) - .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken); expect(response.status).toBe(201); }); + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .post(`${API_KEYS_PATH}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .post(`${API_KEYS_PATH}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken); + expect(response.status).toBe(403); + }); + it('should fail when user is not authenticated', async () => { const response = await request(app).post(`${API_KEYS_PATH}`); expect(response.status).toBe(401); @@ -39,13 +51,27 @@ describe('API Keys API Security Tests', () => { }); describe('Get API Keys', () => { - it('should succeed when user is authenticated as admin', async () => { + it('should succeed when user is authenticated as ADMIN', async () => { const response = await request(app) .get(`${API_KEYS_PATH}`) - .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken); expect(response.status).toBe(200); }); + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .get(`${API_KEYS_PATH}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .get(`${API_KEYS_PATH}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken); + expect(response.status).toBe(403); + }); + it('should fail when user is not authenticated', async () => { const response = await request(app).get(`${API_KEYS_PATH}`); expect(response.status).toBe(401); @@ -58,13 +84,27 @@ describe('API Keys API Security Tests', () => { await generateApiKey(); }); - it('should succeed when user is authenticated as admin', async () => { + it('should succeed when user is authenticated as ADMIN', async () => { const response = await request(app) .delete(`${API_KEYS_PATH}`) - .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken); expect(response.status).toBe(200); }); + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .delete(`${API_KEYS_PATH}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .delete(`${API_KEYS_PATH}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken); + expect(response.status).toBe(403); + }); + it('should fail when user is not authenticated', async () => { const response = await request(app).delete(`${API_KEYS_PATH}`); expect(response.status).toBe(401); diff --git a/meet-ce/backend/tests/integration/api/security/global-config-security.test.ts b/meet-ce/backend/tests/integration/api/security/global-config-security.test.ts index c414544f..5a7df6d9 100644 --- a/meet-ce/backend/tests/integration/api/security/global-config-security.test.ts +++ b/meet-ce/backend/tests/integration/api/security/global-config-security.test.ts @@ -4,17 +4,23 @@ import { Express } from 'express'; import request from 'supertest'; import { INTERNAL_CONFIG } from '../../../../src/config/internal-config.js'; import { MEET_ENV } from '../../../../src/environment.js'; -import { loginAdminUser, restoreDefaultGlobalConfig, startTestServer } from '../../../helpers/request-helpers.js'; +import { deleteAllUsers, restoreDefaultGlobalConfig, startTestServer } from '../../../helpers/request-helpers.js'; +import { setupTestUsers } from '../../../helpers/test-scenarios.js'; +import { TestUsers } from '../../../interfaces/scenarios.js'; const CONFIG_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config`; describe('Global Config API Security Tests', () => { let app: Express; - let adminAccessToken: string; + let testUsers: TestUsers; beforeAll(async () => { app = await startTestServer(); - adminAccessToken = await loginAdminUser(); + testUsers = await setupTestUsers(); + }); + + afterAll(async () => { + await deleteAllUsers(); }); describe('Update Webhook Config Tests', () => { @@ -31,16 +37,32 @@ describe('Global Config API Security Tests', () => { expect(response.status).toBe(401); }); - it('should succeed when user is authenticated as admin', async () => { + 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) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken) .send(webhookConfig); expect(response.status).toBe(200); await restoreDefaultGlobalConfig(); }); + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .put(`${CONFIG_PATH}/webhooks`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken) + .send(webhookConfig); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .put(`${CONFIG_PATH}/webhooks`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken) + .send(webhookConfig); + expect(response.status).toBe(403); + }); + it('should fail when user is not authenticated', async () => { const response = await request(app).put(`${CONFIG_PATH}/webhooks`).send(webhookConfig); expect(response.status).toBe(401); @@ -55,13 +77,27 @@ describe('Global Config API Security Tests', () => { expect(response.status).toBe(401); }); - it('should succeed when user is authenticated as admin', async () => { + 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); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken); expect(response.status).toBe(200); }); + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .get(`${CONFIG_PATH}/webhooks`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .get(`${CONFIG_PATH}/webhooks`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken); + expect(response.status).toBe(403); + }); + it('should fail when user is not authenticated', async () => { const response = await request(app).get(`${CONFIG_PATH}/webhooks`); expect(response.status).toBe(401); @@ -84,16 +120,32 @@ describe('Global Config API Security Tests', () => { expect(response.status).toBe(401); }); - it('should succeed when user is authenticated as admin', async () => { + 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) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken) .send(securityConfig); expect(response.status).toBe(200); await restoreDefaultGlobalConfig(); }); + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .put(`${CONFIG_PATH}/security`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken) + .send(securityConfig); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .put(`${CONFIG_PATH}/security`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken) + .send(securityConfig); + expect(response.status).toBe(403); + }); + it('should fail when user is not authenticated', async () => { const response = await request(app).put(`${CONFIG_PATH}/security`).send(securityConfig); expect(response.status).toBe(401); @@ -128,16 +180,32 @@ describe('Global Config API Security Tests', () => { expect(response.status).toBe(401); }); - it('should succeed when user is authenticated as admin', async () => { + 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) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken) .send(appearanceConfig); expect(response.status).toBe(200); await restoreDefaultGlobalConfig(); }); + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .put(`${CONFIG_PATH}/rooms/appearance`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken) + .send(appearanceConfig); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .put(`${CONFIG_PATH}/rooms/appearance`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken) + .send(appearanceConfig); + expect(response.status).toBe(403); + }); + it('should fail when user is not authenticated', async () => { const response = await request(app).put(`${CONFIG_PATH}/rooms/appearance`).send(appearanceConfig); expect(response.status).toBe(401); diff --git a/meet-ce/backend/tests/integration/api/security/meeting-security.test.ts b/meet-ce/backend/tests/integration/api/security/meeting-security.test.ts index 73c352bb..480dff71 100644 --- a/meet-ce/backend/tests/integration/api/security/meeting-security.test.ts +++ b/meet-ce/backend/tests/integration/api/security/meeting-security.test.ts @@ -1,4 +1,4 @@ -import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; +import { afterAll, beforeAll, describe, expect, it } from '@jest/globals'; import { MeetRoomMemberRole, MeetRoomMemberTokenMetadata } from '@openvidu-meet/typings'; import { Express } from 'express'; import request from 'supertest'; @@ -8,27 +8,36 @@ import { getPermissions } from '../../../helpers/assertion-helpers.js'; import { deleteAllRooms, disconnectFakeParticipants, - loginAdminUser, + joinFakeParticipant, + loginRootAdmin, startTestServer, updateParticipantMetadata } from '../../../helpers/request-helpers.js'; -import { setupSingleRoom } from '../../../helpers/test-scenarios.js'; -import { RoomData } from '../../../interfaces/scenarios.js'; +import { setupRoomMember, setupSingleRoom, updateRoomMemberPermissions } from '../../../helpers/test-scenarios.js'; +import { RoomData, RoomMemberData } from '../../../interfaces/scenarios.js'; const MEETINGS_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings`; describe('Meeting API Security Tests', () => { + const participantIdentity = 'TEST_PARTICIPANT'; + let app: Express; - let adminAccessToken: string; + let rootAdminAccessToken: string; + let roomData: RoomData; + let roomId: string; + let roomMember: RoomMemberData; beforeAll(async () => { app = await startTestServer(); - adminAccessToken = await loginAdminUser(); - }); + rootAdminAccessToken = await loginRootAdmin(); - beforeEach(async () => { - roomData = await setupSingleRoom(true); + roomData = await setupSingleRoom(); + roomId = roomData.room.roomId; + roomMember = await setupRoomMember(roomId, { + name: 'External Member', + baseRole: MeetRoomMemberRole.MODERATOR + }); }); afterAll(async () => { @@ -39,136 +48,176 @@ describe('Meeting API Security Tests', () => { describe('End Meeting Tests', () => { it('should fail when request includes API key', async () => { const response = await request(app) - .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) + .delete(`${MEETINGS_PATH}/${roomId}`) .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY); expect(response.status).toBe(401); }); - it('should fail when user is authenticated as admin', async () => { + it('should fail when using access token', async () => { const response = await request(app) - .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) - .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + .delete(`${MEETINGS_PATH}/${roomId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken); expect(response.status).toBe(401); }); - it('should succeed when participant is moderator', async () => { + it('should succeed when using room member token with canEndMeeting permission', async () => { + // Update room member to have canEndMeeting permission + roomMember = await updateRoomMemberPermissions(roomId, roomMember.member.memberId, { canEndMeeting: true }); + const response = await request(app) - .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) - .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomData.moderatorToken); + .delete(`${MEETINGS_PATH}/${roomId}`) + .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomMember.memberToken); expect(response.status).toBe(200); + + // Re-join participant for further tests + await joinFakeParticipant(roomId, participantIdentity); }); - it('should fail when participant is moderator of a different room', async () => { - const newRoomData = await setupSingleRoom(); + it('should fail when using room member token without canEndMeeting permission', async () => { + // Update room member to not have canEndMeeting permission + roomMember = await updateRoomMemberPermissions(roomId, roomMember.member.memberId, { + canEndMeeting: false + }); const response = await request(app) - .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) - .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, newRoomData.moderatorToken); + .delete(`${MEETINGS_PATH}/${roomId}`) + .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomMember.memberToken); expect(response.status).toBe(403); }); - it('should fail when participant is speaker', async () => { + it('should fail when using room member token from a different room', async () => { + const newRoomData = await setupSingleRoom(); + const response = await request(app) - .delete(`${MEETINGS_PATH}/${roomData.room.roomId}`) - .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomData.speakerToken); + .delete(`${MEETINGS_PATH}/${roomId}`) + .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, newRoomData.moderatorToken); expect(response.status).toBe(403); }); }); describe('Update Participant in Meeting Tests', () => { - const PARTICIPANT_NAME = 'TEST_PARTICIPANT'; const role = MeetRoomMemberRole.MODERATOR; - beforeEach(async () => { + const setParticipantMetadata = async () => { const metadata: MeetRoomMemberTokenMetadata = { livekitUrl: MEET_ENV.LIVEKIT_URL, - roomId: roomData.room.roomId, + roomId, baseRole: MeetRoomMemberRole.SPEAKER, effectivePermissions: getPermissions(MeetRoomMemberRole.SPEAKER) }; - await updateParticipantMetadata(roomData.room.roomId, PARTICIPANT_NAME, metadata); + await updateParticipantMetadata(roomId, participantIdentity, metadata); + }; + + beforeAll(async () => { + // Ensure participant has the correct metadata before tests + await setParticipantMetadata(); }); it('should fail when request includes API key', async () => { const response = await request(app) - .put(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}/role`) + .put(`${MEETINGS_PATH}/${roomId}/participants/${participantIdentity}/role`) .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY) .send({ role }); expect(response.status).toBe(401); }); - it('should fail when user is authenticated as admin', async () => { + it('should fail when using access token', async () => { const response = await request(app) - .put(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}/role`) - .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) + .put(`${MEETINGS_PATH}/${roomId}/participants/${participantIdentity}/role`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken) .send({ role }); expect(response.status).toBe(401); }); - it('should succeed when participant is moderator', async () => { + it('should succeed when using room member token with canMakeModerator permission', async () => { + // Update room member to have canMakeModerator permission + roomMember = await updateRoomMemberPermissions(roomId, roomMember.member.memberId, { + canMakeModerator: true + }); + const response = await request(app) - .put(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}/role`) - .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomData.moderatorToken) + .put(`${MEETINGS_PATH}/${roomId}/participants/${participantIdentity}/role`) + .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomMember.memberToken) .send({ role }); expect(response.status).toBe(200); + + // Re-join participant for further tests + await joinFakeParticipant(roomId, participantIdentity); + await setParticipantMetadata(); }); - it('should fail when participant is moderator of a different room', async () => { - const newRoomData = await setupSingleRoom(); + it('should fail when using room member token without canMakeModerator permission', async () => { + // Update room member to not have canMakeModerator permission + roomMember = await updateRoomMemberPermissions(roomId, roomMember.member.memberId, { + canMakeModerator: false + }); const response = await request(app) - .put(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}/role`) - .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, newRoomData.moderatorToken) + .put(`${MEETINGS_PATH}/${roomId}/participants/${participantIdentity}/role`) + .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomMember.memberToken) .send({ role }); expect(response.status).toBe(403); }); - it('should fail when participant is speaker', async () => { + it('should fail when using room member token from a different room', async () => { + const newRoomData = await setupSingleRoom(); + const response = await request(app) - .put(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_NAME}/role`) - .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomData.speakerToken) + .put(`${MEETINGS_PATH}/${roomId}/participants/${participantIdentity}/role`) + .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, newRoomData.moderatorToken) .send({ role }); expect(response.status).toBe(403); }); }); describe('Kick Participant from Meeting Tests', () => { - const PARTICIPANT_IDENTITY = 'TEST_PARTICIPANT'; - it('should fail when request includes API key', async () => { const response = await request(app) - .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_IDENTITY}`) + .delete(`${MEETINGS_PATH}/${roomId}/participants/${participantIdentity}`) .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY); expect(response.status).toBe(401); }); - it('should fail when user is authenticated as admin', async () => { + it('should fail when using access token', async () => { const response = await request(app) - .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_IDENTITY}`) - .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + .delete(`${MEETINGS_PATH}/${roomId}/participants/${participantIdentity}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken); expect(response.status).toBe(401); }); - it('should succeed when participant is moderator', async () => { + it('should succeed when using room member token with canKickParticipants permission', async () => { + // Update room member to have canKickParticipants permission + roomMember = await updateRoomMemberPermissions(roomId, roomMember.member.memberId, { + canKickParticipants: true + }); + const response = await request(app) - .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_IDENTITY}`) - .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomData.moderatorToken); + .delete(`${MEETINGS_PATH}/${roomId}/participants/${participantIdentity}`) + .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomMember.memberToken); expect(response.status).toBe(200); + + // Re-join participant for further tests + await joinFakeParticipant(roomId, participantIdentity); }); - it('should fail when participant is moderator of a different room', async () => { - const newRoomData = await setupSingleRoom(); + it('should fail when using room member token without canKickParticipants permission', async () => { + // Update room member to not have canKickParticipants permission + roomMember = await updateRoomMemberPermissions(roomId, roomMember.member.memberId, { + canKickParticipants: false + }); const response = await request(app) - .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_IDENTITY}`) - .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, newRoomData.moderatorToken); + .delete(`${MEETINGS_PATH}/${roomId}/participants/${participantIdentity}`) + .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomMember.memberToken); expect(response.status).toBe(403); }); - it('should fail when participant is speaker', async () => { + it('should fail when using room member token from a different room', async () => { + const newRoomData = await setupSingleRoom(); + const response = await request(app) - .delete(`${MEETINGS_PATH}/${roomData.room.roomId}/participants/${PARTICIPANT_IDENTITY}`) - .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomData.speakerToken); + .delete(`${MEETINGS_PATH}/${roomId}/participants/${participantIdentity}`) + .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, newRoomData.moderatorToken); expect(response.status).toBe(403); }); }); diff --git a/meet-ce/backend/tests/integration/api/security/user-security.test.ts b/meet-ce/backend/tests/integration/api/security/user-security.test.ts index 300bf632..546fa9c0 100644 --- a/meet-ce/backend/tests/integration/api/security/user-security.test.ts +++ b/meet-ce/backend/tests/integration/api/security/user-security.test.ts @@ -1,30 +1,454 @@ import { beforeAll, describe, expect, it } from '@jest/globals'; +import { MeetUserRole } from '@openvidu-meet/typings'; import { Express } from 'express'; import request from 'supertest'; import { INTERNAL_CONFIG } from '../../../../src/config/internal-config.js'; import { MEET_ENV } from '../../../../src/environment.js'; -import { changePassword, loginAdminUser, startTestServer } from '../../../helpers/request-helpers.js'; +import { + changePassword, + createUser, + deleteAllUsers, + loginRootAdmin, + startTestServer +} from '../../../helpers/request-helpers.js'; +import { setupTestUsers } from '../../../helpers/test-scenarios.js'; +import { TestUsers } from '../../../interfaces/scenarios.js'; const USERS_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users`; describe('User API Security Tests', () => { let app: Express; + let rootAdminAccessToken: string; + let testUsers: TestUsers; beforeAll(async () => { app = await startTestServer(); + rootAdminAccessToken = await loginRootAdmin(); + testUsers = await setupTestUsers(); + }); + + afterAll(async () => { + await deleteAllUsers(); + }); + + describe('Create User Tests', () => { + const getNewUserData = () => { + const timestamp = Date.now(); + return { + userId: `usr_${timestamp}`, + name: 'Test User', + password: 'testpass123', + role: MeetUserRole.USER + }; + }; + + it('should fail when using API key', async () => { + const response = await request(app) + .post(USERS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY) + .send(getNewUserData()); + expect(response.status).toBe(401); + }); + + it('should succeed when user is authenticated as root admin', async () => { + const response = await request(app) + .post(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken) + .send(getNewUserData()); + expect(response.status).toBe(201); + }); + + it('should succeed when user is authenticated as ADMIN', async () => { + const response = await request(app) + .post(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken) + .send(getNewUserData()); + expect(response.status).toBe(201); + }); + + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .post(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken) + .send(getNewUserData()); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .post(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken) + .send(getNewUserData()); + expect(response.status).toBe(403); + }); + + it('should fail when user is not authenticated', async () => { + const response = await request(app).post(USERS_PATH).send(getNewUserData()); + expect(response.status).toBe(401); + }); + }); + + describe('Get Users Tests', () => { + it('should fail when using API key', async () => { + const response = await request(app) + .get(USERS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY); + expect(response.status).toBe(401); + }); + + it('should succeed when user is authenticated as root admin', async () => { + const response = await request(app) + .get(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as ADMIN', async () => { + const response = await request(app) + .get(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as USER', async () => { + const response = await request(app) + .get(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken); + expect(response.status).toBe(200); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .get(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken); + expect(response.status).toBe(403); + }); + + it('should fail when user is not authenticated', async () => { + const response = await request(app).get(USERS_PATH); + expect(response.status).toBe(401); + }); + }); + + describe('Get User Tests', () => { + let userId: string; + + beforeAll(async () => { + const response = await createUser({ + userId: `usr_${Date.now()}`, + name: 'Test User', + password: 'testpass123', + role: MeetUserRole.USER + }); + expect(response.status).toBe(201); + userId = response.body.userId; + }); + + it('should fail when using API key', async () => { + const response = await request(app) + .get(`${USERS_PATH}/${userId}`) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY); + expect(response.status).toBe(401); + }); + + it('should succeed when user is authenticated as root admin', async () => { + const response = await request(app) + .get(`${USERS_PATH}/${userId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as ADMIN', async () => { + const response = await request(app) + .get(`${USERS_PATH}/${userId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as USER', async () => { + const response = await request(app) + .get(`${USERS_PATH}/${userId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken); + expect(response.status).toBe(200); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .get(`${USERS_PATH}/${userId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken); + expect(response.status).toBe(403); + }); + + it('should fail when user is not authenticated', async () => { + const response = await request(app).get(`${USERS_PATH}/${userId}`); + expect(response.status).toBe(401); + }); + }); + + describe('Reset User Password Tests', () => { + let userId: string; + const newPassword = 'resetpassword123'; + + beforeAll(async () => { + const response = await createUser({ + userId: `usr_${Date.now()}`, + name: 'Test User', + password: 'testpass123', + role: MeetUserRole.USER + }); + expect(response.status).toBe(201); + userId = response.body.userId; + }); + + it('should fail when using API key', async () => { + const response = await request(app) + .put(`${USERS_PATH}/${userId}/password`) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY) + .send({ newPassword }); + expect(response.status).toBe(401); + }); + + it('should succeed when user is authenticated as root admin', async () => { + const response = await request(app) + .put(`${USERS_PATH}/${userId}/password`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken) + .send({ newPassword }); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as ADMIN', async () => { + const response = await request(app) + .put(`${USERS_PATH}/${userId}/password`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken) + .send({ newPassword }); + expect(response.status).toBe(200); + }); + + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .put(`${USERS_PATH}/${userId}/password`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken) + .send({ newPassword }); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .put(`${USERS_PATH}/${userId}/password`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken) + .send({ newPassword }); + expect(response.status).toBe(403); + }); + + it('should fail when user is not authenticated', async () => { + const response = await request(app).put(`${USERS_PATH}/${userId}/password`).send({ newPassword }); + expect(response.status).toBe(401); + }); + }); + + describe('Update User Role Tests', () => { + let userId: string; + + beforeAll(async () => { + const response = await createUser({ + userId: `usr_${Date.now()}`, + name: 'Test User', + password: 'testpass123', + role: MeetUserRole.USER + }); + expect(response.status).toBe(201); + userId = response.body.userId; + }); + + it('should fail when using API key', async () => { + const response = await request(app) + .put(`${USERS_PATH}/${userId}/role`) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY) + .send({ role: MeetUserRole.ADMIN }); + expect(response.status).toBe(401); + }); + + it('should succeed when user is authenticated as root admin', async () => { + const response = await request(app) + .put(`${USERS_PATH}/${userId}/role`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken) + .send({ role: MeetUserRole.ADMIN }); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as ADMIN', async () => { + const response = await request(app) + .put(`${USERS_PATH}/${userId}/role`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken) + .send({ role: MeetUserRole.ADMIN }); + expect(response.status).toBe(200); + }); + + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .put(`${USERS_PATH}/${userId}/role`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken) + .send({ role: MeetUserRole.ADMIN }); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .put(`${USERS_PATH}/${userId}/role`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken) + .send({ role: MeetUserRole.ADMIN }); + expect(response.status).toBe(403); + }); + + it('should fail when user is not authenticated', async () => { + const response = await request(app).put(`${USERS_PATH}/${userId}/role`).send({ role: MeetUserRole.ADMIN }); + expect(response.status).toBe(401); + }); + }); + + describe('Delete User Tests', () => { + let userId: string; + + beforeEach(async () => { + // Create a user to delete in each test + const response = await createUser({ + userId: `usr_${Date.now()}`, + name: 'Test User', + password: 'testpass123', + role: MeetUserRole.USER + }); + expect(response.status).toBe(201); + userId = response.body.userId; + }); + + it('should fail when using API key', async () => { + const response = await request(app) + .delete(`${USERS_PATH}/${userId}`) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY); + expect(response.status).toBe(401); + }); + + it('should succeed when user is authenticated as root admin', async () => { + const response = await request(app) + .delete(`${USERS_PATH}/${userId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as ADMIN', async () => { + const response = await request(app) + .delete(`${USERS_PATH}/${userId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken); + expect(response.status).toBe(200); + }); + + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .delete(`${USERS_PATH}/${userId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .delete(`${USERS_PATH}/${userId}`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken); + expect(response.status).toBe(403); + }); + + it('should fail when user is not authenticated', async () => { + const response = await request(app).delete(`${USERS_PATH}/${userId}`); + expect(response.status).toBe(401); + }); + }); + + describe('Bulk Delete Users Tests', () => { + let userId: string; + + beforeEach(async () => { + // Create user to delete in each test + const response = await createUser({ + userId: `usr_${Date.now()}`, + name: 'Test User', + password: 'testpass123', + role: MeetUserRole.USER + }); + expect(response.status).toBe(201); + userId = response.body.userId; + }); + + it('should fail when using API key', async () => { + const response = await request(app) + .delete(USERS_PATH) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY) + .query({ userIds: userId }); + expect(response.status).toBe(401); + }); + + it('should succeed when user is authenticated as root admin', async () => { + const response = await request(app) + .delete(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken) + .query({ userIds: userId }); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as ADMIN', async () => { + const response = await request(app) + .delete(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken) + .query({ userIds: userId }); + expect(response.status).toBe(200); + }); + + it('should fail when user is authenticated as USER', async () => { + const response = await request(app) + .delete(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken) + .query({ userIds: userId }); + expect(response.status).toBe(403); + }); + + it('should fail when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .delete(USERS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken) + .query({ userIds: userId }); + expect(response.status).toBe(403); + }); + + it('should fail when user is not authenticated', async () => { + const response = await request(app).delete(USERS_PATH).query({ userIds: userId }); + expect(response.status).toBe(401); + }); }); describe('Profile Tests', () => { - let adminAccessToken: string; - - beforeAll(async () => { - adminAccessToken = await loginAdminUser(); - }); - - it('should succeed when user is authenticated as admin', async () => { + it('should succeed when user is authenticated as root admin', async () => { const response = await request(app) .get(`${USERS_PATH}/me`) - .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as ADMIN', async () => { + const response = await request(app) + .get(`${USERS_PATH}/me`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as USER', async () => { + const response = await request(app) + .get(`${USERS_PATH}/me`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken); + expect(response.status).toBe(200); + }); + + it('should succeed when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .get(`${USERS_PATH}/me`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken); expect(response.status).toBe(200); }); @@ -35,30 +459,69 @@ describe('User API Security Tests', () => { }); describe('Change Password Tests', () => { - const changePasswordRequest = { - currentPassword: MEET_ENV.INITIAL_ADMIN_PASSWORD, - newPassword: 'newpassword123' - }; + const newPassword = 'newpassword123'; - let adminAccessToken: string; - - beforeAll(async () => { - adminAccessToken = await loginAdminUser(); - }); - - it('should succeed when user is authenticated as admin', async () => { + it('should succeed when user is authenticated as root admin', async () => { const response = await request(app) .post(`${USERS_PATH}/change-password`) - .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, adminAccessToken) - .send(changePasswordRequest); + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, rootAdminAccessToken) + .send({ + currentPassword: MEET_ENV.INITIAL_ADMIN_PASSWORD, + newPassword + }); expect(response.status).toBe(200); - // Reset password - await changePassword(changePasswordRequest.newPassword, MEET_ENV.INITIAL_ADMIN_PASSWORD, adminAccessToken); + // Reset old password + await changePassword(newPassword, MEET_ENV.INITIAL_ADMIN_PASSWORD, rootAdminAccessToken); + }); + + 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, testUsers.admin.accessToken) + .send({ + currentPassword: testUsers.admin.password, + newPassword + }); + expect(response.status).toBe(200); + + // Reset old password + await changePassword(newPassword, testUsers.admin.password, testUsers.admin.accessToken); + }); + + it('should succeed when user is authenticated as USER', async () => { + const response = await request(app) + .post(`${USERS_PATH}/change-password`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken) + .send({ + currentPassword: testUsers.user.password, + newPassword + }); + expect(response.status).toBe(200); + + // Reset old password + await changePassword(newPassword, testUsers.user.password, testUsers.user.accessToken); + }); + + it('should succeed when user is authenticated as ROOM_MEMBER', async () => { + const response = await request(app) + .post(`${USERS_PATH}/change-password`) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken) + .send({ + currentPassword: testUsers.roomMember.password, + newPassword + }); + expect(response.status).toBe(200); + + // Reset old password + await changePassword(newPassword, testUsers.roomMember.password, testUsers.roomMember.accessToken); }); it('should fail when user is not authenticated', async () => { - const response = await request(app).post(`${USERS_PATH}/change-password`).send(changePasswordRequest); + const response = await request(app).post(`${USERS_PATH}/change-password`).send({ + currentPassword: MEET_ENV.INITIAL_ADMIN_PASSWORD, + newPassword + }); expect(response.status).toBe(401); }); });