Authenticate browser media with durable HttpOnly login cookies

Adapted cookie, origin, URL validation and secret handling from MikaelKW/nodecast-tv-plus. Protect API roles, revoke logins across restart, preserve existing accounts, and isolate transcode sessions by account.

Co-authored-by: Mikael <mikaelkwessel@gmail.com>
This commit is contained in:
Technomancer702
2026-10-12 16:53:15 -07:00
parent 2999d6542f
commit 1f665dcb11
24 changed files with 553 additions and 111 deletions

View File

@ -0,0 +1,81 @@
'use strict';
const ALLOWED_MEDIA_PROTOCOLS = new Set(['http:', 'https:']);
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
const FFMPEG_PROTOCOL_WHITELIST = 'http,https,tcp,tls,crypto,hls';
function validationError(message) {
const error = new Error(message);
error.statusCode = 400;
return error;
}
function isSensitiveHost(hostname) {
const host = hostname.toLowerCase().replace(/^\[|\]$/g, '').replace(/\.$/, '');
const allowLoopback = process.env.ALLOW_LOCAL_MEDIA_URLS === 'true';
if (host === 'metadata.google.internal') return true;
if (host === '169.254.169.254' || host.startsWith('169.254.')) return true;
const mapped = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
if (mapped) {
const high = parseInt(mapped[1], 16), low = parseInt(mapped[2], 16);
return isSensitiveHost(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
}
if ((!allowLoopback && host === '::1') || /^fe[89ab][0-9a-f]:/.test(host)) return true;
if (!allowLoopback && (host === 'localhost' || host.endsWith('.localhost'))) return true;
if (!allowLoopback && (/^127\./.test(host) || host === '0.0.0.0')) return true;
return false;
}
function validateHttpUrl(value, fieldName = 'URL') {
if (typeof value !== 'string' || !value.trim()) {
throw validationError(`${fieldName} is required`);
}
const candidate = value.trim();
if (CONTROL_CHARACTERS.test(value)) {
throw validationError(`${fieldName} contains invalid characters`);
}
let parsed;
try {
parsed = new URL(candidate);
} catch {
throw validationError(`${fieldName} must be a valid HTTP or HTTPS URL`);
}
if (!ALLOWED_MEDIA_PROTOCOLS.has(parsed.protocol)) {
throw validationError(`${fieldName} must use HTTP or HTTPS`);
}
if (!parsed.hostname) {
throw validationError(`${fieldName} must include a host`);
}
if (isSensitiveHost(parsed.hostname)) {
throw validationError(`${fieldName} points to a protected local or metadata address`);
}
return candidate;
}
function redactUrl(value) {
try {
const parsed = new URL(value);
if (!ALLOWED_MEDIA_PROTOCOLS.has(parsed.protocol)) return '[unsupported URL]';
return `${parsed.protocol}//${parsed.host}/…`;
} catch {
return '[invalid URL]';
}
}
function redactText(value) {
return String(value ?? '').replace(/https?:\/\/[^\s"'<>]+/gi, '[redacted URL]');
}
module.exports = {
FFMPEG_PROTOCOL_WHITELIST,
redactText,
redactUrl,
validateHttpUrl
};