backend: enhance zod schemas for room members and users, adding new validation and permissions structures
This commit is contained in:
parent
e3de4256a8
commit
e0736677ca
@ -1,12 +1,4 @@
|
||||
import {
|
||||
AuthenticationConfig,
|
||||
AuthMode,
|
||||
AuthType,
|
||||
SecurityConfig,
|
||||
SingleUserAuth,
|
||||
ValidAuthMethod,
|
||||
WebhookConfig
|
||||
} from '@openvidu-meet/typings';
|
||||
import { AuthenticationConfig, SecurityConfig, WebhookConfig } from '@openvidu-meet/typings';
|
||||
import { z } from 'zod';
|
||||
import { AppearanceConfigSchema } from './room.schema.js';
|
||||
|
||||
@ -37,19 +29,8 @@ export const TestWebhookReqSchema = z.object({
|
||||
.regex(/^https?:\/\//, { message: 'URL must start with http:// or https://' })
|
||||
});
|
||||
|
||||
const AuthModeSchema: z.ZodType<AuthMode> = z.nativeEnum(AuthMode);
|
||||
|
||||
const AuthTypeSchema: z.ZodType<AuthType> = z.nativeEnum(AuthType);
|
||||
|
||||
const SingleUserAuthSchema: z.ZodType<SingleUserAuth> = z.object({
|
||||
type: AuthTypeSchema
|
||||
});
|
||||
|
||||
const ValidAuthMethodSchema: z.ZodType<ValidAuthMethod> = SingleUserAuthSchema;
|
||||
|
||||
const AuthenticationConfigSchema: z.ZodType<AuthenticationConfig> = z.object({
|
||||
authMethod: ValidAuthMethodSchema,
|
||||
authModeToAccessRoom: AuthModeSchema
|
||||
allowUserCreation: z.boolean()
|
||||
});
|
||||
|
||||
export const SecurityConfigSchema: z.ZodType<SecurityConfig> = z.object({
|
||||
|
||||
132
meet-ce/backend/src/models/zod-schemas/room-member.schema.ts
Normal file
132
meet-ce/backend/src/models/zod-schemas/room-member.schema.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import {
|
||||
MeetRoomMemberFilters,
|
||||
MeetRoomMemberOptions,
|
||||
MeetRoomMemberPermissions,
|
||||
MeetRoomMemberRole,
|
||||
MeetRoomMemberTokenMetadata,
|
||||
MeetRoomMemberTokenOptions
|
||||
} from '@openvidu-meet/typings';
|
||||
import { z } from 'zod';
|
||||
|
||||
const RoomMemberRoleSchema: z.ZodType<MeetRoomMemberRole> = z.nativeEnum(MeetRoomMemberRole);
|
||||
|
||||
export const MeetPermissionsSchema: z.ZodType<MeetRoomMemberPermissions> = z.object({
|
||||
canRecord: z.boolean(),
|
||||
canRetrieveRecordings: z.boolean(),
|
||||
canDeleteRecordings: z.boolean(),
|
||||
canJoinMeeting: z.boolean(),
|
||||
canShareAccessLinks: z.boolean(),
|
||||
canMakeModerator: z.boolean(),
|
||||
canKickParticipants: z.boolean(),
|
||||
canEndMeeting: z.boolean(),
|
||||
canPublishVideo: z.boolean(),
|
||||
canPublishAudio: z.boolean(),
|
||||
canShareScreen: z.boolean(),
|
||||
canReadChat: z.boolean(),
|
||||
canWriteChat: z.boolean(),
|
||||
canChangeVirtualBackground: z.boolean()
|
||||
});
|
||||
|
||||
export const PartialMeetPermissionsSchema: z.ZodType<Partial<MeetRoomMemberPermissions>> = z.object({
|
||||
canRecord: z.boolean().optional(),
|
||||
canRetrieveRecordings: z.boolean().optional(),
|
||||
canDeleteRecordings: z.boolean().optional(),
|
||||
canJoinMeeting: z.boolean().optional(),
|
||||
canShareAccessLinks: z.boolean().optional(),
|
||||
canMakeModerator: z.boolean().optional(),
|
||||
canKickParticipants: z.boolean().optional(),
|
||||
canEndMeeting: z.boolean().optional(),
|
||||
canPublishVideo: z.boolean().optional(),
|
||||
canPublishAudio: z.boolean().optional(),
|
||||
canShareScreen: z.boolean().optional(),
|
||||
canReadChat: z.boolean().optional(),
|
||||
canWriteChat: z.boolean().optional(),
|
||||
canChangeVirtualBackground: z.boolean().optional()
|
||||
});
|
||||
|
||||
export const RoomMemberOptionsSchema: z.ZodType<MeetRoomMemberOptions> = z
|
||||
.object({
|
||||
userId: z
|
||||
.string()
|
||||
.regex(/^[a-z0-9_]+$/, 'userId must contain only lowercase letters, numbers, and underscores')
|
||||
.optional(),
|
||||
name: z.string().min(1, 'name cannot be empty').max(50, 'name cannot exceed 50 characters').optional(),
|
||||
baseRole: RoomMemberRoleSchema,
|
||||
customPermissions: PartialMeetPermissionsSchema.optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
// Either userId or name must be provided, but not both
|
||||
return (data.userId && !data.name) || (!data.userId && data.name);
|
||||
},
|
||||
{
|
||||
message: 'Either userId or name must be provided, but not both',
|
||||
path: ['userId']
|
||||
}
|
||||
);
|
||||
|
||||
export const RoomMemberFiltersSchema: z.ZodType<MeetRoomMemberFilters> = z.object({
|
||||
name: z.string().optional(),
|
||||
fields: z.string().optional(),
|
||||
maxItems: z.coerce
|
||||
.number()
|
||||
.positive('maxItems must be a positive number')
|
||||
.transform((val) => {
|
||||
// Convert the value to a number
|
||||
const intVal = Math.floor(val);
|
||||
// Ensure it's not greater than 100
|
||||
return intVal > 100 ? 100 : intVal;
|
||||
})
|
||||
.default(10),
|
||||
nextPageToken: z.string().optional()
|
||||
});
|
||||
|
||||
export const BulkDeleteRoomMembersReqSchema = z.object({
|
||||
memberIds: z.preprocess(
|
||||
(arg) => {
|
||||
if (typeof arg === 'string') {
|
||||
// If the argument is a string, it is expected to be a comma-separated list of member IDs.
|
||||
return arg
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s !== '');
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
z.array(z.string()).min(1, {
|
||||
message: 'At least one memberId is required'
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
export const UpdateRoomMemberReqSchema = z.object({
|
||||
baseRole: RoomMemberRoleSchema.optional(),
|
||||
customPermissions: PartialMeetPermissionsSchema.optional()
|
||||
});
|
||||
|
||||
export const RoomMemberTokenOptionsSchema: z.ZodType<MeetRoomMemberTokenOptions> = z
|
||||
.object({
|
||||
secret: z.string().optional(),
|
||||
grantJoinMeetingPermission: z.boolean().optional().default(false),
|
||||
participantName: z.string().optional(),
|
||||
participantIdentity: z.string().optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
// If grantJoinMeetingPermission is true, participantName must be provided
|
||||
return !data.grantJoinMeetingPermission || data.participantName;
|
||||
},
|
||||
{
|
||||
message: 'participantName is required when grantJoinMeetingPermission is true',
|
||||
path: ['participantName']
|
||||
}
|
||||
);
|
||||
|
||||
export const RoomMemberTokenMetadataSchema: z.ZodType<MeetRoomMemberTokenMetadata> = z.object({
|
||||
livekitUrl: z.string().url('LiveKit URL must be a valid URL'),
|
||||
roomId: z.string(),
|
||||
baseRole: RoomMemberRoleSchema,
|
||||
customPermissions: PartialMeetPermissionsSchema.optional(),
|
||||
effectivePermissions: MeetPermissionsSchema
|
||||
});
|
||||
@ -2,18 +2,15 @@ import {
|
||||
MeetAppearanceConfig,
|
||||
MeetChatConfig,
|
||||
MeetE2EEConfig,
|
||||
MeetPermissions,
|
||||
MeetRecordingAccess,
|
||||
MeetRecordingConfig,
|
||||
MeetRoomAnonymousConfig,
|
||||
MeetRoomAutoDeletionPolicy,
|
||||
MeetRoomConfig,
|
||||
MeetRoomDeletionPolicyWithMeeting,
|
||||
MeetRoomDeletionPolicyWithRecordings,
|
||||
MeetRoomFilters,
|
||||
MeetRoomMemberRole,
|
||||
MeetRoomMemberTokenMetadata,
|
||||
MeetRoomMemberTokenOptions,
|
||||
MeetRoomOptions,
|
||||
MeetRoomRolesConfig,
|
||||
MeetRoomStatus,
|
||||
MeetRoomTheme,
|
||||
MeetRoomThemeMode,
|
||||
@ -23,6 +20,7 @@ import ms from 'ms';
|
||||
import { z } from 'zod';
|
||||
import { INTERNAL_CONFIG } from '../../config/internal-config.js';
|
||||
import { MeetRoomHelper } from '../../helpers/room.helper.js';
|
||||
import { PartialMeetPermissionsSchema } from './room-member.schema.js';
|
||||
|
||||
export const nonEmptySanitizedRoomId = (fieldName: string) =>
|
||||
z
|
||||
@ -34,23 +32,9 @@ export const nonEmptySanitizedRoomId = (fieldName: string) =>
|
||||
message: `${fieldName} cannot be empty after sanitization`
|
||||
});
|
||||
|
||||
const RecordingAccessSchema: z.ZodType<MeetRecordingAccess> = z.nativeEnum(MeetRecordingAccess);
|
||||
|
||||
const RecordingConfigSchema: z.ZodType<MeetRecordingConfig> = z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
allowAccessTo: RecordingAccessSchema.optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
// If recording is enabled, allowAccessTo must be provided
|
||||
return !data.enabled || data.allowAccessTo !== undefined;
|
||||
},
|
||||
{
|
||||
message: 'allowAccessTo is required when recording is enabled',
|
||||
path: ['allowAccessTo']
|
||||
}
|
||||
);
|
||||
const RecordingConfigSchema: z.ZodType<MeetRecordingConfig> = z.object({
|
||||
enabled: z.boolean()
|
||||
});
|
||||
|
||||
const ChatConfigSchema: z.ZodType<MeetChatConfig> = z.object({
|
||||
enabled: z.boolean()
|
||||
@ -103,10 +87,7 @@ const RoomConfigSchema: z.ZodType<Partial<MeetRoomConfig>> = z
|
||||
.transform((data) => {
|
||||
// Automatically disable recording when E2EE is enabled
|
||||
if (data.e2ee?.enabled && data.recording?.enabled) {
|
||||
data.recording = {
|
||||
...data.recording,
|
||||
enabled: false
|
||||
};
|
||||
data.recording.enabled = false;
|
||||
}
|
||||
|
||||
return data;
|
||||
@ -125,6 +106,32 @@ const RoomAutoDeletionPolicySchema: z.ZodType<MeetRoomAutoDeletionPolicy> = z.ob
|
||||
withRecordings: RoomDeletionPolicyWithRecordingsSchema
|
||||
});
|
||||
|
||||
const RoomRolesConfigSchema: z.ZodType<MeetRoomRolesConfig> = z.object({
|
||||
moderator: z
|
||||
.object({
|
||||
permissions: PartialMeetPermissionsSchema
|
||||
})
|
||||
.optional(),
|
||||
speaker: z
|
||||
.object({
|
||||
permissions: PartialMeetPermissionsSchema
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
|
||||
const RoomAnonymousConfigSchema: z.ZodType<MeetRoomAnonymousConfig> = z.object({
|
||||
moderator: z
|
||||
.object({
|
||||
enabled: z.boolean()
|
||||
})
|
||||
.optional(),
|
||||
speaker: z
|
||||
.object({
|
||||
enabled: z.boolean()
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
|
||||
export const RoomOptionsSchema: z.ZodType<MeetRoomOptions> = z.object({
|
||||
roomName: z
|
||||
.string()
|
||||
@ -164,10 +171,15 @@ export const RoomOptionsSchema: z.ZodType<MeetRoomOptions> = z.object({
|
||||
}
|
||||
),
|
||||
config: RoomConfigSchema.optional().default({
|
||||
recording: { enabled: true, allowAccessTo: MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER },
|
||||
recording: { enabled: true },
|
||||
chat: { enabled: true },
|
||||
virtualBackground: { enabled: true },
|
||||
e2ee: { enabled: false }
|
||||
}),
|
||||
roles: RoomRolesConfigSchema.optional(),
|
||||
anonymous: RoomAnonymousConfigSchema.optional().default({
|
||||
moderator: { enabled: true },
|
||||
speaker: { enabled: true }
|
||||
})
|
||||
// maxParticipants: z
|
||||
// .number()
|
||||
@ -244,38 +256,14 @@ export const UpdateRoomConfigReqSchema = z.object({
|
||||
config: RoomConfigSchema
|
||||
});
|
||||
|
||||
export const UpdateRoomRolesReqSchema = z.object({
|
||||
roles: RoomRolesConfigSchema
|
||||
});
|
||||
|
||||
export const UpdateRoomAnonymousReqSchema = z.object({
|
||||
anonymous: RoomAnonymousConfigSchema
|
||||
});
|
||||
|
||||
export const UpdateRoomStatusReqSchema = z.object({
|
||||
status: z.enum([MeetRoomStatus.OPEN, MeetRoomStatus.CLOSED])
|
||||
});
|
||||
|
||||
export const RoomMemberTokenOptionsSchema: z.ZodType<MeetRoomMemberTokenOptions> = z
|
||||
.object({
|
||||
secret: z.string().nonempty('Secret is required'),
|
||||
grantJoinMeetingPermission: z.boolean().optional().default(false),
|
||||
participantName: z.string().optional(),
|
||||
participantIdentity: z.string().optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
// If grantJoinMeetingPermission is true, participantName must be provided
|
||||
return !data.grantJoinMeetingPermission || data.participantName;
|
||||
},
|
||||
{
|
||||
message: 'participantName is required when grantJoinMeetingPermission is true',
|
||||
path: ['participantName']
|
||||
}
|
||||
);
|
||||
|
||||
const MeetPermissionsSchema: z.ZodType<MeetPermissions> = z.object({
|
||||
canRecord: z.boolean(),
|
||||
canRetrieveRecordings: z.boolean(),
|
||||
canDeleteRecordings: z.boolean(),
|
||||
canChat: z.boolean(),
|
||||
canChangeVirtualBackground: z.boolean()
|
||||
});
|
||||
|
||||
export const RoomMemberTokenMetadataSchema: z.ZodType<MeetRoomMemberTokenMetadata> = z.object({
|
||||
livekitUrl: z.string().url('LiveKit URL must be a valid URL'),
|
||||
role: z.nativeEnum(MeetRoomMemberRole),
|
||||
permissions: MeetPermissionsSchema
|
||||
});
|
||||
|
||||
@ -1,5 +1,53 @@
|
||||
import { MeetUserFilters, MeetUserOptions, MeetUserRole } from '@openvidu-meet/typings';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const CreateUserReqSchema: z.ZodType<MeetUserOptions> = z.object({
|
||||
userId: z
|
||||
.string()
|
||||
.min(5, 'userId must be at least 5 characters long')
|
||||
.max(20, 'userId cannot exceed 20 characters')
|
||||
.regex(/^[a-z0-9_]+$/, 'userId must contain only lowercase letters, numbers, and underscores'),
|
||||
name: z.string().min(1, 'name is required and cannot be empty').max(50, 'name cannot exceed 50 characters'),
|
||||
role: z.nativeEnum(MeetUserRole),
|
||||
password: z.string().min(5, 'password must be at least 5 characters long')
|
||||
});
|
||||
|
||||
export const UserFiltersSchema: z.ZodType<MeetUserFilters> = z.object({
|
||||
userId: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
fields: z.string().optional(),
|
||||
maxItems: z.coerce
|
||||
.number()
|
||||
.positive('maxItems must be a positive number')
|
||||
.transform((val) => {
|
||||
// Convert the value to a number
|
||||
const intVal = Math.floor(val);
|
||||
// Ensure it's not greater than 100
|
||||
return intVal > 100 ? 100 : intVal;
|
||||
})
|
||||
.default(10),
|
||||
nextPageToken: z.string().optional()
|
||||
});
|
||||
|
||||
export const BulkDeleteUsersReqSchema = z.object({
|
||||
userIds: z.preprocess(
|
||||
(arg) => {
|
||||
if (typeof arg === 'string') {
|
||||
// If the argument is a string, it is expected to be a comma-separated list of user IDs.
|
||||
return arg
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s !== '');
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
z.array(z.string()).min(1, {
|
||||
message: 'At least one userId is required'
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
export const ChangePasswordReqSchema = z.object({
|
||||
currentPassword: z.string(),
|
||||
newPassword: z.string().min(5, 'New password must be at least 5 characters long')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user