backend: Update meet-room-options schema and validation to enforce roomIdPrefix length limit

This commit is contained in:
Carlos Santos 2025-04-15 13:22:50 +02:00
parent 817135433a
commit 20ef57b14c
3 changed files with 23 additions and 8 deletions

View File

@ -8,7 +8,7 @@ properties:
After this date, the room will be automatically deleted and no new participants can join. After this date, the room will be automatically deleted and no new participants can join.
If this value is set and the date has already passed, the room is marked for deletion but will only be removed after the last If this value is set and the date has already passed, the room is marked for deletion but will only be removed after the last
participant leaves ("graceful deletion"). participant leaves ("graceful deletion").
If this value is not defined, the room will exist indefinitely until manually deleted. If this value is not defined, the room will exist indefinitely until manually deleted.
@ -17,6 +17,7 @@ properties:
example: 'room' example: 'room'
description: > description: >
A prefix to be used for the room ID. The room ID will be generated by concatenating this prefix with an unique identifier. A prefix to be used for the room ID. The room ID will be generated by concatenating this prefix with an unique identifier.
The maximum length of the room ID is 50 characters.
# maxParticipants: # maxParticipants:
# type: integer # type: integer
# example: 10 # example: 10

View File

@ -18,7 +18,7 @@ import INTERNAL_CONFIG from '../../config/internal-config.js';
* @param val The string to sanitize * @param val The string to sanitize
* @returns A sanitized string safe for use as an identifier * @returns A sanitized string safe for use as an identifier
*/ */
const sanitizeId = (val: string): string => { const sanitizeRoomId = (val: string): string => {
let transformed = val let transformed = val
.trim() // Remove leading/trailing spaces .trim() // Remove leading/trailing spaces
.replace(/\s+/g, '') // Remove all spaces .replace(/\s+/g, '') // Remove all spaces
@ -34,11 +34,12 @@ const sanitizeId = (val: string): string => {
return transformed; return transformed;
}; };
const nonEmptySanitizedString = (fieldName: string) => export const nonEmptySanitizedRoomId = (fieldName: string) =>
z z
.string() .string()
.min(1, { message: `${fieldName} is required and cannot be empty` }) .min(1, { message: `${fieldName} is required and cannot be empty` })
.transform(sanitizeId) .max(100, { message: `${fieldName} cannot exceed 100 characters` })
.transform(sanitizeRoomId)
.refine((data) => data !== '', { .refine((data) => data !== '', {
message: `${fieldName} cannot be empty after sanitization` message: `${fieldName} cannot be empty after sanitization`
}); });
@ -83,7 +84,8 @@ const RoomRequestOptionsSchema: z.ZodType<MeetRoomOptions> = z.object({
.optional(), .optional(),
roomIdPrefix: z roomIdPrefix: z
.string() .string()
.transform(sanitizeId) .max(50, 'roomIdPrefix cannot exceed 50 characters')
.transform(sanitizeRoomId)
.optional() .optional()
.default(''), .default(''),
preferences: RoomPreferencesSchema.optional().default({ preferences: RoomPreferencesSchema.optional().default({
@ -135,7 +137,7 @@ const BulkDeleteRoomsSchema = z.object({
// Pre-sanitize to check for duplicates that would become identical // Pre-sanitize to check for duplicates that would become identical
for (const id of roomIds) { for (const id of roomIds) {
const transformed = sanitizeId(id); const transformed = sanitizeRoomId(id);
// Only add non-empty IDs // Only add non-empty IDs
if (transformed !== '') { if (transformed !== '') {
@ -190,7 +192,7 @@ export const withValidRoomPreferences = (req: Request, res: Response, next: Next
}; };
export const withValidRoomId = (req: Request, res: Response, next: NextFunction) => { export const withValidRoomId = (req: Request, res: Response, next: NextFunction) => {
const { success, error, data } = nonEmptySanitizedString('roomId').safeParse(req.params.roomId); const { success, error, data } = nonEmptySanitizedRoomId('roomId').safeParse(req.params.roomId);
if (!success) { if (!success) {
return rejectRequest(res, error); return rejectRequest(res, error);
@ -213,7 +215,7 @@ export const withValidRoomBulkDeleteRequest = (req: Request, res: Response, next
}; };
export const withValidRoomDeleteRequest = (req: Request, res: Response, next: NextFunction) => { export const withValidRoomDeleteRequest = (req: Request, res: Response, next: NextFunction) => {
const roomIdResult = nonEmptySanitizedString('roomId').safeParse(req.params.roomId); const roomIdResult = nonEmptySanitizedRoomId('roomId').safeParse(req.params.roomId);
if (!roomIdResult.success) { if (!roomIdResult.success) {
return rejectRequest(res, roomIdResult.error); return rejectRequest(res, roomIdResult.error);

View File

@ -215,5 +215,17 @@ describe('OpenVidu Meet Room API Tests', () => {
expect(response.body.error).toContain('Bad Request'); expect(response.body.error).toContain('Bad Request');
expect(response.body.message).toContain('Malformed Body'); expect(response.body.message).toContain('Malformed Body');
}); });
it('should fail when roomIdPrefix is too long', async () => {
const longRoomId = 'a'.repeat(51);
const payload = {
roomIdPrefix: longRoomId,
autoDeletionDate: validAutoDeletionDate
};
const response = await request(app).post(ROOMS_PATH).set('Cookie', userCookie).send(payload).expect(422);
expect(JSON.stringify(response.body.details)).toContain('roomIdPrefix cannot exceed 50 characters');
});
}); });
}); });