test: add comprehensive token validation tests for access and room member tokens

This commit is contained in:
juancarmore 2026-01-30 13:04:24 +01:00
parent 993681395c
commit cdbb30fc2a
7 changed files with 363 additions and 28 deletions

View File

@ -18,8 +18,7 @@ describe('API Keys API Tests', () => {
beforeAll(async () => {
app = await startTestServer();
const { accessToken } = await loginRootAdmin();
rootAdminAccessToken = accessToken;
({ accessToken: rootAdminAccessToken } = await loginRootAdmin());
});
afterAll(async () => {

View File

@ -12,8 +12,7 @@ describe('Authentication API Tests', () => {
beforeAll(async () => {
app = await startTestServer();
const { accessToken } = await loginRootAdmin();
rootAdminAccessToken = accessToken;
({ accessToken: rootAdminAccessToken } = await loginRootAdmin());
});
describe('Logout Tests', () => {

View File

@ -72,7 +72,7 @@ describe('Authentication API Tests', () => {
expect(response.body.message).toContain('Invalid refresh token');
});
it('should fail when using roomMemberToken token instead of refresh token', async () => {
it('should fail when using room member token instead of refresh token', async () => {
const { moderatorToken } = await setupSingleRoom();
const response = await request(app)
.post(`${AUTH_PATH}/refresh`)

View File

@ -0,0 +1,357 @@
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 {
createUser,
deleteAllRooms,
deleteAllUsers,
deleteUser,
loginUser,
resetUserPassword,
sleep,
startTestServer
} from '../../../helpers/request-helpers.js';
import { setupSingleRoom, setupTestUsers, setupUser } from '../../../helpers/test-scenarios.js';
import { TestUsers, UserData } from '../../../interfaces/scenarios.js';
const USERS_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users`;
const ROOMS_PATH = `${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms`;
describe('Token Validation Tests', () => {
let app: Express;
let testUsers: TestUsers;
let roomId: string;
let roomMemberToken: string;
beforeAll(async () => {
app = await startTestServer();
testUsers = await setupTestUsers();
// Setup a room with a member token
const roomData = await setupSingleRoom();
roomId = roomData.room.roomId;
roomMemberToken = roomData.moderatorToken;
});
afterAll(async () => {
await deleteAllRooms();
await deleteAllUsers();
});
describe('Access Token Tests', () => {
it('should succeed when providing valid access token', 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 providing valid access token through query param without bearer prefix', async () => {
const accessTokenQuery = testUsers.user.accessToken.replace('Bearer ', '');
const response = await request(app).get(`${USERS_PATH}/me`).query({ accessToken: accessTokenQuery });
expect(response.status).toBe(200);
});
it('should succeed when admin accesses admin-only endpoint', async () => {
const response = await request(app)
.get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/api-keys`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken);
expect(response.status).toBe(200);
});
it('should fail when access token is missing', async () => {
const response = await request(app).get(`${USERS_PATH}/me`);
expect(response.status).toBe(401);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Unauthorized');
});
it('should fail when access token is invalid', async () => {
const response = await request(app)
.get(`${USERS_PATH}/me`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, 'Bearer invalidtoken');
expect(response.status).toBe(401);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Invalid token');
});
it('should fail when access token is expired', async () => {
// Set short access token expiration
const initialTokenExpiration = INTERNAL_CONFIG.ACCESS_TOKEN_EXPIRATION;
INTERNAL_CONFIG.ACCESS_TOKEN_EXPIRATION = '1s';
// Create a user and get their access token
const userData = await setupUser({
userId: `user_${Date.now()}`,
name: 'User',
password: 'password123',
role: MeetUserRole.USER
});
await sleep('2s'); // Ensure the token is expired
// Restore original expiration after setup
INTERNAL_CONFIG.ACCESS_TOKEN_EXPIRATION = initialTokenExpiration;
const response = await request(app)
.get(`${USERS_PATH}/me`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, userData.accessToken);
expect(response.status).toBe(401);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Invalid token');
});
it('should fail when using room member token instead of access token', async () => {
const response = await request(app)
.get(`${USERS_PATH}/me`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, roomMemberToken);
expect(response.status).toBe(401);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Invalid token');
});
it('should fail when using refresh token instead of access token', async () => {
const response = await request(app)
.get(`${USERS_PATH}/me`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.refreshToken);
expect(response.status).toBe(401);
expect(response.body).toHaveProperty('message');
});
it('should fail when token subject (user) does not exist', async () => {
// Create a user and get their access token
const userData = await setupUser({
userId: `user_${Date.now()}`,
name: 'User',
password: 'password123',
role: MeetUserRole.USER
});
// Delete the user to invalidate the token subject
await deleteUser(userData.user.userId);
// Attempt to use the token
const response = await request(app)
.get(`${USERS_PATH}/me`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, userData.accessToken);
expect(response.status).toBe(403);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Invalid token subject');
});
it('should fail when USER tries to access ADMIN-only endpoint', async () => {
const response = await request(app)
.get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/api-keys`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken);
expect(response.status).toBe(403);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Insufficient permissions');
});
it('should fail when ROOM_MEMBER tries to access ADMIN and USER endpoint', async () => {
const response = await request(app)
.get(USERS_PATH)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken);
expect(response.status).toBe(403);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Insufficient permissions');
});
describe('Password Change Required Tests', () => {
const resetPassword = 'NewPassword123';
let userData: UserData;
beforeAll(async () => {
// Create a user and get their access token
userData = await setupUser({
userId: `user_${Date.now()}`,
name: 'User',
password: 'password123',
role: MeetUserRole.USER
});
// Reset user password to force password change
await resetUserPassword(userData.user.userId, resetPassword);
});
it('should succeed when accessing /me endpoint with mustChangePassword', async () => {
const response = await request(app)
.get(`${USERS_PATH}/me`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, userData.accessToken);
expect(response.status).toBe(200);
});
it('should succeed when accessing /change-password endpoint with mustChangePassword', async () => {
const response = await request(app)
.post(`${USERS_PATH}/change-password`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, userData.accessToken)
.send({
currentPassword: resetPassword,
newPassword: userData.password
});
expect(response.status).toBe(200);
// Reset user password again for futher tests
await resetUserPassword(userData.user.userId, resetPassword);
});
it('should fail when accessing other endpoints with mustChangePassword', async () => {
// Try to create a room (requires USER role which this user has, but password change blocks it)
const response = await request(app)
.post(ROOMS_PATH)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, userData.accessToken)
.send({});
expect(response.status).toBe(403);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Password change required');
});
});
describe('Temporary Access Token Tests', () => {
let userId: string;
let accessTokenTmp: string;
beforeAll(async () => {
// Create a user (when created, this user is set to require password change)
const response = await createUser({
userId: 'temp_user',
name: 'Temp User',
password: 'InitialPassword1!',
role: MeetUserRole.USER
});
userId = response.body.userId;
// Login to get temporary access token
({ accessToken: accessTokenTmp } = await loginUser(userId, 'InitialPassword1!'));
});
it('should succeed when accessing /me endpoint with temporary token', async () => {
const response = await request(app)
.get(`${USERS_PATH}/me`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessTokenTmp);
expect(response.status).toBe(200);
});
it('should succeed when accessing /change-password endpoint with temporary token', async () => {
const response = await request(app)
.post(`${USERS_PATH}/change-password`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessTokenTmp)
.send({
currentPassword: 'InitialPassword1!',
newPassword: 'FinalPassword123!'
});
expect(response.status).toBe(200);
});
it('should fail when accessing other endpoints with temporary token', async () => {
const response = await request(app)
.post(ROOMS_PATH)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessTokenTmp)
.send({});
expect(response.status).toBe(403);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Password change required');
});
});
});
describe('Room Member Token Tests', () => {
it('should succeed when providing valid room member token', async () => {
const response = await request(app)
.get(`${ROOMS_PATH}/${roomId}`)
.set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomMemberToken);
expect(response.status).toBe(200);
});
it('should succeed when providing valid room member token through query param without bearer prefix', async () => {
const roomMemberTokenQuery = roomMemberToken.replace('Bearer ', '');
const response = await request(app)
.get(`${ROOMS_PATH}/${roomId}`)
.query({ roomMemberToken: roomMemberTokenQuery });
expect(response.status).toBe(200);
});
it('should fail when room member token is missing', async () => {
const response = await request(app).get(`${ROOMS_PATH}/${roomId}`);
expect(response.status).toBe(401);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Unauthorized');
});
it('should fail when room member token is invalid', async () => {
const response = await request(app)
.get(`${ROOMS_PATH}/${roomId}`)
.set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, 'Bearer invalidtoken');
expect(response.status).toBe(401);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Invalid token');
});
it('should fail when room member token is expired', async () => {
// Set short room member token expiration
const initialTokenExpiration = INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_EXPIRATION;
INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_EXPIRATION = '1s';
const roomData = await setupSingleRoom();
await sleep('2s'); // Ensure the token is expired
// Restore original expiration after setup
INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_EXPIRATION = initialTokenExpiration;
const response = await request(app)
.get(`${ROOMS_PATH}/${roomData.room.roomId}`)
.set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomData.moderatorToken);
expect(response.status).toBe(401);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Invalid token');
});
it('should fail when using access token instead of room member token', async () => {
const response = await request(app)
.get(`${ROOMS_PATH}/${roomId}`)
.set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, testUsers.user.accessToken);
expect(response.status).toBe(401);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Invalid token');
});
it('should succeed when both room member token and user access token are valid', async () => {
const response = await request(app)
.get(`${ROOMS_PATH}/${roomId}`)
.set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomMemberToken)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken);
expect(response.status).toBe(200);
});
it('should succeed with room member token even when access token is invalid', async () => {
const response = await request(app)
.get(`${ROOMS_PATH}/${roomId}`)
.set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomMemberToken)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, 'Bearer invalidtoken');
expect(response.status).toBe(200);
});
it('should fail when room member token is expired, even if user access token is valid', async () => {
// Set short room member token expiration
const initialTokenExpiration = INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_EXPIRATION;
INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_EXPIRATION = '1s';
const roomData = await setupSingleRoom();
await sleep('2s'); // Ensure the token is expired
// Restore original expiration after setup
INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_EXPIRATION = initialTokenExpiration;
const response = await request(app)
.get(`${ROOMS_PATH}/${roomData.room.roomId}`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken)
.set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomData.moderatorToken);
expect(response.status).toBe(401);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Invalid token');
});
});
});

View File

@ -30,8 +30,7 @@ describe('Meeting API Security Tests', () => {
beforeAll(async () => {
app = await startTestServer();
const { accessToken } = await loginRootAdmin();
rootAdminAccessToken = accessToken;
({ accessToken: rootAdminAccessToken } = await loginRootAdmin());
roomData = await setupSingleRoom();
roomId = roomData.room.roomId;

View File

@ -3,7 +3,7 @@ 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 { deleteAllRooms, deleteAllUsers, sleep, startTestServer } from '../../../helpers/request-helpers.js';
import { deleteAllRooms, deleteAllUsers, startTestServer } from '../../../helpers/request-helpers.js';
import { setupSingleRoom, setupTestUsers, setupTestUsersForRoom } from '../../../helpers/test-scenarios.js';
import { RoomData, RoomTestUsers, TestUsers } from '../../../interfaces/scenarios.js';
@ -325,24 +325,6 @@ describe('Room API Security Tests', () => {
.set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, newRoomData.moderatorToken);
expect(response.status).toBe(403);
});
it('should fail when room member token is expired, even if user access token is valid', async () => {
// Set short room member token expiration
const initialTokenExpiration = INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_EXPIRATION;
INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_EXPIRATION = '1s';
const newRoomData = await setupSingleRoom();
await sleep('2s'); // Ensure the token is expired
// Restore original expiration after setup
INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_EXPIRATION = initialTokenExpiration;
const response = await request(app)
.get(`${ROOMS_PATH}/${newRoomData.room.roomId}`)
.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken)
.set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, newRoomData.moderatorToken);
expect(response.status).toBe(401);
});
});
describe('Delete Room Tests', () => {

View File

@ -23,8 +23,7 @@ describe('User API Security Tests', () => {
beforeAll(async () => {
app = await startTestServer();
const { accessToken } = await loginRootAdmin();
rootAdminAccessToken = accessToken;
({ accessToken: rootAdminAccessToken } = await loginRootAdmin());
testUsers = await setupTestUsers();
});