backend: enhance API key handling and validation, including error handling for missing keys
This commit is contained in:
parent
bd512f782c
commit
5dcde7a153
@ -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');
|
||||
|
||||
@ -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',
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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<User | null> {
|
||||
async authenticateUser(username: string, password: string): Promise<User | null> {
|
||||
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<boolean> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<string> {
|
||||
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<string> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user