Improve video access security by adding signed URL support from Bunny.net

Implements signed URLs for secure video and thumbnail delivery using the BUNNY_SECURITY_KEY environment variable in bunny.ts.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 50814a1e-92e4-4968-856f-7bc7eedf5e8f
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/50814a1e-92e4-4968-856f-7bc7eedf5e8f/3tWpY1N
This commit is contained in:
sebastjanartic 2025-08-04 19:13:59 +00:00
parent 16cf7dd5a8
commit f286638b19

View File

@ -1,4 +1,5 @@
import { type Video, type InsertVideo } from "@shared/schema";
import crypto from 'crypto';
interface BunnyVideo {
guid: string;
@ -22,11 +23,13 @@ export class BunnyService {
private apiKey: string;
private libraryId: string;
private hostname: string;
private securityKey: string;
constructor() {
this.apiKey = process.env.BUNNY_API_KEY!;
this.libraryId = process.env.BUNNY_LIBRARY_ID!;
this.hostname = process.env.BUNNY_HOSTNAME!;
this.securityKey = process.env.BUNNY_SECURITY_KEY || ''; // CDN security key for signing URLs
if (!this.apiKey || !this.libraryId || !this.hostname) {
throw new Error("Missing Bunny.net configuration");
@ -51,18 +54,32 @@ export class BunnyService {
return response.json();
}
private bunnyVideoToVideo(bunnyVideo: BunnyVideo): Video {
// Use proxy endpoint for thumbnails to handle private access
const thumbnailUrl = `/thumbnail/${bunnyVideo.guid}`;
private generateSignedUrl(path: string, expiryHours: number = 1): string {
if (!this.securityKey) {
// If no security key, return iframe embed as fallback
const videoId = path.split('/')[1];
return `https://iframe.mediadelivery.net/embed/${this.libraryId}/${videoId}?controls=true&autoplay=false`;
}
// Try direct CDN URL first - some videos might be accessible
const directUrl = `https://${this.hostname}/${bunnyVideo.guid}/playlist.m3u8`;
const expireTimestamp = Math.floor(Date.now() / 1000) + (expiryHours * 3600);
const tokenContent = `${this.securityKey}${path}${expireTimestamp}`;
// Fallback iframe embed for private videos
const iframeUrl = `https://iframe.mediadelivery.net/embed/${this.libraryId}/${bunnyVideo.guid}?controls=true&autoplay=false`;
const hash = crypto.createHash('md5').update(tokenContent).digest();
const token = Buffer.from(hash).toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
return `https://${this.hostname}${path}?token=${token}&expires=${expireTimestamp}`;
}
private bunnyVideoToVideo(bunnyVideo: BunnyVideo): Video {
// Generate signed URLs for private video access
const videoPath = `/${bunnyVideo.guid}/playlist.m3u8`;
const thumbnailPath = `/${bunnyVideo.guid}/${bunnyVideo.thumbnailFileName || 'thumbnail.jpg'}`;
// Use direct URL for HLS streaming
const videoUrl = directUrl;
const videoUrl = this.generateSignedUrl(videoPath);
const thumbnailUrl = this.generateSignedUrl(thumbnailPath);
return {
id: bunnyVideo.guid,