fix: proxy cover art through server to satisfy CSP img-src 'self'
The new CSP blocks direct browser requests to external image origins (themoviedb.org, thetvdb.com, etc.) used for poster art. - dashboard.js: add GET /api/dashboard/cover-art?url=... proxy endpoint (auth-required, http/https only, image content-type validated, 5MB cap, 24h Cache-Control, streams response directly to client) - app.js: route coverArt src through /api/dashboard/cover-art proxy - server/utils/logger.js: fix hardcoded /app/server.log path (use DATA_DIR)
This commit is contained in:
@@ -721,4 +721,41 @@ router.get('/status', requireAuth, (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Cover art proxy — fetches external poster images server-side so the
|
||||
// browser loads them from 'self' and the CSP img-src stays tight.
|
||||
// Requires authentication. Only proxies http/https URLs.
|
||||
router.get('/cover-art', requireAuth, async (req, res) => {
|
||||
const { url } = req.query;
|
||||
if (!url || typeof url !== 'string') {
|
||||
return res.status(400).json({ error: 'Missing url parameter' });
|
||||
}
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(url);
|
||||
} catch {
|
||||
return res.status(400).json({ error: 'Invalid url' });
|
||||
}
|
||||
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||
return res.status(400).json({ error: 'Only http/https URLs are supported' });
|
||||
}
|
||||
try {
|
||||
const response = await axios.get(url, {
|
||||
responseType: 'stream',
|
||||
timeout: 8000,
|
||||
maxContentLength: 5 * 1024 * 1024 // 5 MB max
|
||||
});
|
||||
const contentType = response.headers['content-type'] || 'image/jpeg';
|
||||
// Only proxy image content types
|
||||
if (!contentType.startsWith('image/')) {
|
||||
return res.status(400).json({ error: 'Remote URL is not an image' });
|
||||
}
|
||||
res.setHeader('Content-Type', contentType);
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400'); // 24h browser cache
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
response.data.pipe(res);
|
||||
} catch (err) {
|
||||
res.status(502).json({ error: 'Failed to fetch cover art' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user