From 5dcde7a153346c6ecd7e67ea0d288dee894171b1 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Thu, 19 Jun 2025 12:19:54 +0200 Subject: [PATCH] backend: enhance API key handling and validation, including error handling for missing keys --- backend/src/controllers/auth.controller.ts | 2 +- backend/src/environment.ts | 4 +-- backend/src/middlewares/auth.middleware.ts | 14 ++++++--- backend/src/models/error.model.ts | 14 ++++++++- backend/src/services/auth.service.ts | 15 +++++++++- .../src/services/openvidu-webhook.service.ts | 30 +++++++++++++++---- 6 files changed, 64 insertions(+), 15 deletions(-) diff --git a/backend/src/controllers/auth.controller.ts b/backend/src/controllers/auth.controller.ts index ddc58d8..c9b7f08 100644 --- a/backend/src/controllers/auth.controller.ts +++ b/backend/src/controllers/auth.controller.ts @@ -20,7 +20,7 @@ export const login = async (req: Request, res: Response) => { const { username, password } = req.body as { username: string; password: string }; const authService = container.get(AuthService); - const user = await authService.authenticate(username, password); + const user = await authService.authenticateUser(username, password); if (!user) { logger.warn('Login failed'); diff --git a/backend/src/environment.ts b/backend/src/environment.ts index 8e2676a..c5ec7a0 100644 --- a/backend/src/environment.ts +++ b/backend/src/environment.ts @@ -7,7 +7,7 @@ if (process.env.MEET_CONFIG_DIR) { envPath = process.env.MEET_CONFIG_DIR; } else if (process.env.NODE_ENV === 'development') { envPath = '.env.development'; -} else if (process.env.NODE_ENV === 'test') { +} else if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'ci') { envPath = '.env.test'; } else { envPath = undefined; @@ -22,7 +22,7 @@ export const { MEET_NAME_ID = 'openviduMeet', // Authentication configuration - MEET_API_KEY = 'meet-api-key', + MEET_API_KEY = '', MEET_ADMIN_USER = 'admin', MEET_ADMIN_SECRET = 'admin', diff --git a/backend/src/middlewares/auth.middleware.ts b/backend/src/middlewares/auth.middleware.ts index c394e1e..12d4f17 100644 --- a/backend/src/middlewares/auth.middleware.ts +++ b/backend/src/middlewares/auth.middleware.ts @@ -5,7 +5,6 @@ import { ClaimGrants } from 'livekit-server-sdk'; import ms from 'ms'; import { container } from '../config/index.js'; import INTERNAL_CONFIG from '../config/internal-config.js'; -import { MEET_API_KEY } from '../environment.js'; import { errorInsufficientPermissions, errorInvalidApiKey, @@ -16,7 +15,7 @@ import { OpenViduMeetError, rejectRequestFromMeetError } from '../models/index.js'; -import { LoggerService, TokenService, UserService } from '../services/index.js'; +import { AuthService, LoggerService, TokenService, UserService } from '../services/index.js'; /** * This middleware allows to chain multiple validators to check if the request is authorized. @@ -130,8 +129,15 @@ export const apiKeyValidator = async (req: Request) => { throw errorWithControl(errorUnauthorized(), false); } - if (apiKey !== MEET_API_KEY) { - throw errorWithControl(errorInvalidApiKey(), true); + try { + const authService = container.get(AuthService); + const isValidApiKey = await authService.validateApiKey(apiKey as string); + + if (!isValidApiKey) { + throw errorInvalidApiKey(); + } + } catch (error) { + throw errorWithControl(error as OpenViduMeetError, true); } const userService = container.get(UserService); diff --git a/backend/src/models/error.model.ts b/backend/src/models/error.model.ts index 54cd84b..fb228e8 100644 --- a/backend/src/models/error.model.ts +++ b/backend/src/models/error.model.ts @@ -92,6 +92,14 @@ export const errorInvalidApiKey = (): OpenViduMeetError => { return new OpenViduMeetError('Authentication Error', 'Invalid API key', 401); }; +export const errorApiKeyNotConfigured = (): OpenViduMeetError => { + return new OpenViduMeetError( + 'Authentication Error', + 'There are no API keys configured yet. Please, create one to access the API', + 401 + ); +}; + // Recording errors export const errorRecordingDisabled = (roomId: string): OpenViduMeetError => { @@ -135,7 +143,11 @@ export const errorRoomHasNoParticipants = (roomId: string): OpenViduMeetError => }; export const errorInvalidRecordingSecret = (recordingId: string, secret: string): OpenViduMeetError => { - return new OpenViduMeetError('Recording Error', `Secret '${secret}' is not recognized for recording '${recordingId}'`, 400); + return new OpenViduMeetError( + 'Recording Error', + `Secret '${secret}' is not recognized for recording '${recordingId}'`, + 400 + ); }; const isMatchingError = (error: OpenViduMeetError, originalError: OpenViduMeetError): boolean => { diff --git a/backend/src/services/auth.service.ts b/backend/src/services/auth.service.ts index 4c8cae4..63b8ccb 100644 --- a/backend/src/services/auth.service.ts +++ b/backend/src/services/auth.service.ts @@ -1,6 +1,8 @@ import { User } from '@typings-ce'; import { inject, injectable } from 'inversify'; +import { MEET_API_KEY } from '../environment.js'; import { PasswordHelper } from '../helpers/index.js'; +import { errorApiKeyNotConfigured } from '../models/error.model.js'; import { MeetStorageService, UserService } from './index.js'; @injectable() @@ -10,7 +12,7 @@ export class AuthService { @inject(MeetStorageService) protected storageService: MeetStorageService ) {} - async authenticate(username: string, password: string): Promise { + async authenticateUser(username: string, password: string): Promise { const user = await this.userService.getUser(username); if (!user || !(await PasswordHelper.verifyPassword(password, user.passwordHash))) { @@ -35,4 +37,15 @@ export class AuthService { await this.storageService.deleteApiKeys(); return { message: 'API keys deleted successfully' }; } + + async validateApiKey(apiKey: string): Promise { + const storedApiKeys = await this.getApiKeys(); + + if (storedApiKeys.length === 0 && !MEET_API_KEY) { + throw errorApiKeyNotConfigured(); + } + + // Check if the provided API key matches any stored API key or the MEET_API_KEY + return storedApiKeys.some((key) => key.key === apiKey) || apiKey === MEET_API_KEY; + } } diff --git a/backend/src/services/openvidu-webhook.service.ts b/backend/src/services/openvidu-webhook.service.ts index 7351ab8..cbd9980 100644 --- a/backend/src/services/openvidu-webhook.service.ts +++ b/backend/src/services/openvidu-webhook.service.ts @@ -9,13 +9,14 @@ import { import crypto from 'crypto'; import { inject, injectable } from 'inversify'; import { MEET_API_KEY } from '../environment.js'; -import { LoggerService, MeetStorageService } from './index.js'; +import { AuthService, LoggerService, MeetStorageService } from './index.js'; @injectable() export class OpenViduWebhookService { constructor( @inject(LoggerService) protected logger: LoggerService, - @inject(MeetStorageService) protected globalPrefService: MeetStorageService + @inject(MeetStorageService) protected globalPrefService: MeetStorageService, + @inject(AuthService) protected authService: AuthService ) {} /** @@ -122,11 +123,11 @@ export class OpenViduWebhookService { data: payload }; - const signature = this.generateWebhookSignature(creationDate, data); - this.logger.info(`Sending webhook event ${data.event}`); try { + const signature = await this.generateWebhookSignature(creationDate, data); + await this.fetchWithRetry(webhookPreferences.url!, { method: 'POST', headers: { @@ -142,9 +143,10 @@ export class OpenViduWebhookService { } } - protected generateWebhookSignature(timestamp: number, payload: object): string { + protected async generateWebhookSignature(timestamp: number, payload: object): Promise { + const apiKey = await this.getApiKey(); return crypto - .createHmac('sha256', MEET_API_KEY) + .createHmac('sha256', apiKey) .update(`${timestamp}.${JSON.stringify(payload)}`) .digest('hex'); } @@ -177,4 +179,20 @@ export class OpenViduWebhookService { throw error; } } + + protected async getApiKey(): Promise { + const apiKeys = await this.authService.getApiKeys(); + + if (apiKeys.length === 0) { + // If no API keys are configured, check if the MEET_API_KEY environment variable is set + if (MEET_API_KEY) { + return MEET_API_KEY; + } + + throw new Error('There are no API keys configured yet. Please, create one to use webhooks.'); + } + + // Return the first API key + return apiKeys[0].key; + } }