backend: Add security and webhook preferences controllers and validation middleware
This commit is contained in:
parent
166389b606
commit
2d82d6a96d
@ -0,0 +1,76 @@
|
|||||||
|
import { container } from '../../config/dependency-injector.config.js';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { LoggerService } from '../../services/logger.service.js';
|
||||||
|
import { GlobalPreferencesService } from '../../services/preferences/index.js';
|
||||||
|
import { OpenViduMeetError } from '../../models/error.model.js';
|
||||||
|
import { SecurityPreferencesDTO, UpdateSecurityPreferencesDTO } from '@typings-ce';
|
||||||
|
|
||||||
|
export const updateSecurityPreferences = async (req: Request, res: Response) => {
|
||||||
|
const logger = container.get(LoggerService);
|
||||||
|
const globalPrefService = container.get(GlobalPreferencesService);
|
||||||
|
|
||||||
|
logger.verbose(`Updating security preferences: ${JSON.stringify(req.body)}`);
|
||||||
|
const securityPreferences = req.body as UpdateSecurityPreferencesDTO;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const globalPreferences = await globalPrefService.getGlobalPreferences();
|
||||||
|
|
||||||
|
if (securityPreferences.roomCreationPolicy) {
|
||||||
|
globalPreferences.securityPreferences.roomCreationPolicy = securityPreferences.roomCreationPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (securityPreferences.authentication) {
|
||||||
|
const currentAuth = globalPreferences.securityPreferences.authentication;
|
||||||
|
const newAuth = securityPreferences.authentication;
|
||||||
|
|
||||||
|
currentAuth.authMode = newAuth.authMode;
|
||||||
|
currentAuth.method.type = newAuth.method.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
await globalPrefService.saveGlobalPreferences(globalPreferences);
|
||||||
|
|
||||||
|
return res.status(200).json({ message: 'Security preferences updated successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof OpenViduMeetError) {
|
||||||
|
logger.error(`Error updating security preferences: ${error.message}`);
|
||||||
|
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error('Error updating security preferences:' + error);
|
||||||
|
return res.status(500).json({ message: 'Error updating security preferences' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSecurityPreferences = async (_req: Request, res: Response) => {
|
||||||
|
const logger = container.get(LoggerService);
|
||||||
|
const preferenceService = container.get(GlobalPreferencesService);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const preferences = await preferenceService.getGlobalPreferences();
|
||||||
|
|
||||||
|
if (!preferences) {
|
||||||
|
return res.status(404).json({ message: 'Security preferences not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the preferences to the DTO format by removing credentials
|
||||||
|
const securityPreferences = preferences.securityPreferences;
|
||||||
|
const securityPreferencesDTO: SecurityPreferencesDTO = {
|
||||||
|
roomCreationPolicy: securityPreferences.roomCreationPolicy,
|
||||||
|
authentication: {
|
||||||
|
authMode: securityPreferences.authentication.authMode,
|
||||||
|
method: {
|
||||||
|
type: securityPreferences.authentication.method.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return res.status(200).json(securityPreferencesDTO);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof OpenViduMeetError) {
|
||||||
|
logger.error(`Error getting security preferences: ${error.message}`);
|
||||||
|
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error('Error getting security preferences:' + error);
|
||||||
|
return res.status(500).json({ message: 'Error fetching security preferences from database' });
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import { container } from '../../config/dependency-injector.config.js';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { LoggerService } from '../../services/logger.service.js';
|
||||||
|
import { GlobalPreferencesService } from '../../services/preferences/index.js';
|
||||||
|
import { OpenViduMeetError } from '../../models/error.model.js';
|
||||||
|
import { WebhookPreferences } from '@typings-ce';
|
||||||
|
|
||||||
|
export const updateWebhookPreferences = async (req: Request, res: Response) => {
|
||||||
|
const logger = container.get(LoggerService);
|
||||||
|
const globalPrefService = container.get(GlobalPreferencesService);
|
||||||
|
|
||||||
|
logger.verbose(`Updating webhooks preferences: ${JSON.stringify(req.body)}`);
|
||||||
|
const webhookPreferences = req.body as WebhookPreferences;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const globalPreferences = await globalPrefService.getGlobalPreferences();
|
||||||
|
globalPreferences.webhooksPreferences = webhookPreferences;
|
||||||
|
await globalPrefService.saveGlobalPreferences(globalPreferences);
|
||||||
|
|
||||||
|
return res.status(200).json({ message: 'Webhooks preferences updated successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof OpenViduMeetError) {
|
||||||
|
logger.error(`Error updating webhooks preferences: ${error.message}`);
|
||||||
|
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error('Error updating webhooks preferences:' + error);
|
||||||
|
return res.status(500).json({ message: 'Error updating webhooks preferences' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getWebhookPreferences = async (_req: Request, res: Response) => {
|
||||||
|
const logger = container.get(LoggerService);
|
||||||
|
const preferenceService = container.get(GlobalPreferencesService);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const preferences = await preferenceService.getGlobalPreferences();
|
||||||
|
|
||||||
|
if (!preferences) {
|
||||||
|
return res.status(404).json({ message: 'Webhooks preferences not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(preferences.webhooksPreferences);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof OpenViduMeetError) {
|
||||||
|
logger.error(`Error getting webhooks preferences: ${error.message}`);
|
||||||
|
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error('Error getting webhooks preferences:' + error);
|
||||||
|
return res.status(500).json({ message: 'Error fetching webhooks preferences from database' });
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -2,4 +2,8 @@ export * from './auth.controller.js';
|
|||||||
export * from './recording.controller.js';
|
export * from './recording.controller.js';
|
||||||
export * from './room.controller.js';
|
export * from './room.controller.js';
|
||||||
export * from './participant.controller.js';
|
export * from './participant.controller.js';
|
||||||
export * from './livekit-webhook.controller.js';
|
export * from './livekit-webhook.controller.js';
|
||||||
|
|
||||||
|
export * from './global-preferences/appearance-preferences.controller.js';
|
||||||
|
export * from './global-preferences/webhook-preferences.controller.js';
|
||||||
|
export * from './global-preferences/security-preferences.controller.js';
|
||||||
|
|||||||
@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
AuthenticationPreferencesDTO,
|
||||||
|
AuthMode,
|
||||||
|
AuthType,
|
||||||
|
RoomCreationPolicy,
|
||||||
|
SingleUserAuthDTO,
|
||||||
|
UpdateSecurityPreferencesDTO,
|
||||||
|
ValidAuthMethodDTO,
|
||||||
|
WebhookPreferences
|
||||||
|
} from '@typings-ce';
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const WebhookPreferencesSchema: z.ZodType<WebhookPreferences> = z.object({
|
||||||
|
enabled: z.boolean(),
|
||||||
|
url: z.string().url()
|
||||||
|
});
|
||||||
|
|
||||||
|
const AuthModeSchema: z.ZodType<AuthMode> = z.enum([AuthMode.NONE, AuthMode.MODERATORS_ONLY, AuthMode.ALL_USERS]);
|
||||||
|
|
||||||
|
const AuthTypeSchema: z.ZodType<AuthType> = z.enum([AuthType.SINGLE_USER]);
|
||||||
|
|
||||||
|
const SingleUserAuthDTOSchema: z.ZodType<SingleUserAuthDTO> = z.object({
|
||||||
|
type: AuthTypeSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
const ValidAuthMethodDTOSchema: z.ZodType<ValidAuthMethodDTO> = SingleUserAuthDTOSchema;
|
||||||
|
|
||||||
|
const AuthenticationPreferencesDTOSchema: z.ZodType<AuthenticationPreferencesDTO> = z.object({
|
||||||
|
authMode: AuthModeSchema,
|
||||||
|
method: ValidAuthMethodDTOSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
const RoomCreationPolicySchema: z.ZodType<RoomCreationPolicy> = z.object({
|
||||||
|
allowRoomCreation: z.boolean(),
|
||||||
|
requireAuthentication: z.boolean()
|
||||||
|
});
|
||||||
|
|
||||||
|
const UpdateSecurityPreferencesDTOSchema: z.ZodType<UpdateSecurityPreferencesDTO> = z
|
||||||
|
.object({
|
||||||
|
authentication: AuthenticationPreferencesDTOSchema.optional(),
|
||||||
|
roomCreationPolicy: RoomCreationPolicySchema.optional()
|
||||||
|
})
|
||||||
|
.refine((data) => Object.keys(data).length > 0, {
|
||||||
|
message: 'At least one field must be provided for the update'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const validateWebhookPreferences = (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const { success, error, data } = WebhookPreferencesSchema.safeParse(req.body);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return rejectRequest(res, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.body = data;
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateSecurityPreferences = (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const { success, error, data } = UpdateSecurityPreferencesDTOSchema.safeParse(req.body);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return rejectRequest(res, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.body = data;
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
const rejectRequest = (res: Response, error: z.ZodError) => {
|
||||||
|
const errors = error.errors.map((error) => ({
|
||||||
|
field: error.path.join('.'),
|
||||||
|
message: error.message
|
||||||
|
}));
|
||||||
|
|
||||||
|
return res.status(422).json({
|
||||||
|
error: 'Unprocessable Entity',
|
||||||
|
message: 'Invalid request body',
|
||||||
|
details: errors
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,24 +1,49 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
import {
|
import * as appearancePrefCtrl from '../controllers/global-preferences/appearance-preferences.controller.js';
|
||||||
getAppearancePreferences,
|
import * as webhookPrefCtrl from '../controllers/global-preferences/webhook-preferences.controller.js';
|
||||||
updateAppearancePreferences
|
import * as securityPrefCtrl from '../controllers/global-preferences/security-preferences.controller.js';
|
||||||
} from '../controllers/global-preferences/appearance-preferences.controller.js';
|
|
||||||
import { withAuth, tokenAndRoleValidator, apiKeyValidator } from '../middlewares/auth.middleware.js';
|
import { withAuth, tokenAndRoleValidator, apiKeyValidator } from '../middlewares/auth.middleware.js';
|
||||||
import { UserRole } from '@typings-ce';
|
import { UserRole } from '@typings-ce';
|
||||||
|
import {
|
||||||
|
validateSecurityPreferences,
|
||||||
|
validateWebhookPreferences
|
||||||
|
} from '../middlewares/request-validators/preferences-validator.middleware.js';
|
||||||
|
|
||||||
export const preferencesRouter = Router();
|
export const preferencesRouter = Router();
|
||||||
|
|
||||||
preferencesRouter.use(bodyParser.urlencoded({ extended: true }));
|
preferencesRouter.use(bodyParser.urlencoded({ extended: true }));
|
||||||
preferencesRouter.use(bodyParser.json());
|
preferencesRouter.use(bodyParser.json());
|
||||||
|
|
||||||
|
// Webhook preferences
|
||||||
|
preferencesRouter.put(
|
||||||
|
'/webhooks',
|
||||||
|
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
||||||
|
validateWebhookPreferences,
|
||||||
|
webhookPrefCtrl.updateWebhookPreferences
|
||||||
|
);
|
||||||
|
preferencesRouter.get(
|
||||||
|
'/webhooks',
|
||||||
|
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
||||||
|
webhookPrefCtrl.getWebhookPreferences
|
||||||
|
);
|
||||||
|
|
||||||
|
// Security preferences
|
||||||
|
preferencesRouter.put(
|
||||||
|
'/security',
|
||||||
|
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
||||||
|
validateSecurityPreferences,
|
||||||
|
securityPrefCtrl.updateSecurityPreferences
|
||||||
|
);
|
||||||
|
preferencesRouter.get('/security', securityPrefCtrl.getSecurityPreferences);
|
||||||
|
|
||||||
|
// Appearance preferences
|
||||||
preferencesRouter.put(
|
preferencesRouter.put(
|
||||||
'/appearance',
|
'/appearance',
|
||||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
||||||
updateAppearancePreferences
|
appearancePrefCtrl.updateAppearancePreferences
|
||||||
);
|
);
|
||||||
preferencesRouter.get(
|
preferencesRouter.get(
|
||||||
'/appearance',
|
'/appearance',
|
||||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
||||||
getAppearancePreferences
|
appearancePrefCtrl.getAppearancePreferences
|
||||||
);
|
);
|
||||||
|
|||||||
@ -53,6 +53,16 @@ export class GlobalPreferencesService<
|
|||||||
return await this.ensurePreferencesInitialized();
|
return await this.ensurePreferencesInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the global preferences.
|
||||||
|
* @param {GlobalPreferences} preferences
|
||||||
|
* @returns {Promise<GlobalPreferences>}
|
||||||
|
*/
|
||||||
|
async saveGlobalPreferences(preferences: G): Promise<G> {
|
||||||
|
this.logger.info('Saving global preferences');
|
||||||
|
return this.storage.saveGlobalPreferences(preferences) as Promise<G>;
|
||||||
|
}
|
||||||
|
|
||||||
async saveOpenViduRoom(ovRoom: R): Promise<R> {
|
async saveOpenViduRoom(ovRoom: R): Promise<R> {
|
||||||
this.logger.info(`Saving OpenVidu room ${ovRoom.roomName}`);
|
this.logger.info(`Saving OpenVidu room ${ovRoom.roomName}`);
|
||||||
return this.storage.saveOpenViduRoom(ovRoom) as Promise<R>;
|
return this.storage.saveOpenViduRoom(ovRoom) as Promise<R>;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user