Bug fixes for video serving and updats to documentation for mobile use screenshots

This commit is contained in:
2026-04-30 14:17:50 -06:00
parent aba935c8ac
commit 2ae7d8b968
32 changed files with 247 additions and 238 deletions

View File

@@ -3,8 +3,8 @@ import { prisma } from '../../../config/database';
import { requireAdminRole } from '../middleware/auth';
import { videoAnalyticsService } from '../services/video-analytics.service';
import { logger } from '../../../utils/logger';
import { sign } from 'jsonwebtoken';
import { env } from '../../../config/env';
import { signMediaPath } from '../../../utils/signed-url';
import { copyFile } from 'fs/promises';
import { join, dirname, basename, extname, normalize, resolve } from 'path';
import { z } from 'zod';
@@ -299,7 +299,13 @@ export async function videoActionsRoutes(fastify: FastifyInstance) {
/**
* GET /videos/:id/preview-link
* Generate a temporary preview link with expiring JWT token
* Generate a shareable, time-limited preview link.
*
* Uses path-bound HMAC signatures (sig/exp/uid) — same scheme as
* POST /api/media/sign — instead of the legacy `?token=<JWT>` form,
* which leaked full session tokens via access logs/referer headers.
* The signature carries only the admin's user-id and is bound to the
* stream URL, so it can be safely shared with stakeholders.
*/
fastify.get<{ Params: { id: string } }>(
'/:id/preview-link',
@@ -318,24 +324,20 @@ export async function videoActionsRoutes(fastify: FastifyInstance) {
return reply.code(404).send({ message: 'Video not found' });
}
// Generate JWT token that expires in 24 hours
const expiryHours = parseInt(process.env.VIDEO_PREVIEW_LINK_EXPIRY_HOURS || '24');
const token = sign(
{
videoId,
purpose: 'preview',
},
env.JWT_ACCESS_SECRET,
{ expiresIn: `${expiryHours}h` }
);
const ttlSeconds = expiryHours * 60 * 60;
const userId = request.user!.id;
const previewUrl = `${env.MEDIA_API_PUBLIC_URL}/api/videos/${videoId}/preview?token=${token}`;
const streamPath = `/api/videos/${videoId}/stream`;
const signed = signMediaPath(streamPath, userId, ttlSeconds);
const query = `sig=${signed.sig}&exp=${signed.exp}&uid=${signed.uid}`;
const previewUrl = `${env.MEDIA_API_PUBLIC_URL}${streamPath}?${query}`;
logger.info(`Generated preview link for video ${videoId}`, { expiresInHours: expiryHours });
return {
previewUrl,
expiresAt: new Date(Date.now() + expiryHours * 60 * 60 * 1000).toISOString(),
expiresAt: new Date(Number(signed.exp) * 1000).toISOString(),
expiryHours,
};
} catch (error) {