From 83049786eb1bdb1de229aec47032388fb1a8e574 Mon Sep 17 00:00:00 2001 From: Gronod Date: Sat, 16 May 2026 15:07:50 +0100 Subject: [PATCH] security: fix issues #1-4 from security audit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1 Session cookie: add secure (production-only) and sameSite=strict to prevent transmission over HTTP and cross-site request abuse. #2 Remove Emby AccessToken from cookie payload — it was stored in the browser cookie but is never needed client-side; reduces blast radius if cookie is ever exposed. #3 Add requireAuth middleware to all proxy routes (/api/emby, /api/sabnzbd, /api/sonarr, /api/radarr) — previously unauthenticated, now require a valid emby_user session cookie. #4 Remove open CORS wildcard (cors() with no options). The frontend is served from the same origin so no CORS headers are required. Also update clearCookie() to include matching cookie options. --- .dockerignore | 1 + server/index.js | 2 -- server/middleware/requireAuth.js | 14 ++++++++++++++ server/routes/auth.js | 12 +++++++++--- server/routes/emby.js | 3 +++ server/routes/radarr.js | 3 +++ server/routes/sabnzbd.js | 3 +++ server/routes/sonarr.js | 3 +++ 8 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 server/middleware/requireAuth.js diff --git a/.dockerignore b/.dockerignore index c68e5f2..e2c8501 100644 --- a/.dockerignore +++ b/.dockerignore @@ -13,3 +13,4 @@ README.md .dockerignore Dockerfile .gitea/ +docs/ \ No newline at end of file diff --git a/server/index.js b/server/index.js index 7231319..cd8a215 100644 --- a/server/index.js +++ b/server/index.js @@ -1,5 +1,4 @@ const express = require('express'); -const cors = require('cors'); const path = require('path'); const cookieParser = require('cookie-parser'); const fs = require('fs'); @@ -59,7 +58,6 @@ const { startPoller, POLL_INTERVAL, POLLING_ENABLED } = require('./utils/poller' const app = express(); const PORT = process.env.PORT || 3001; -app.use(cors()); app.use(cookieParser()); app.use(express.json()); app.use(express.static(path.join(__dirname, '../public'))); diff --git a/server/middleware/requireAuth.js b/server/middleware/requireAuth.js new file mode 100644 index 0000000..002af74 --- /dev/null +++ b/server/middleware/requireAuth.js @@ -0,0 +1,14 @@ +function requireAuth(req, res, next) { + const userCookie = req.cookies.emby_user; + if (!userCookie) { + return res.status(401).json({ error: 'Not authenticated' }); + } + try { + req.user = JSON.parse(userCookie); + } catch { + return res.status(401).json({ error: 'Invalid session' }); + } + next(); +} + +module.exports = requireAuth; diff --git a/server/routes/auth.js b/server/routes/auth.js index 7d488bc..0a2c8ba 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -37,14 +37,16 @@ router.post('/login', async (req, res) => { console.log(`[Auth] Login successful for user: ${user.Name}`); // Set authentication cookie + // Note: token is intentionally excluded from the cookie — it is not needed client-side const isAdmin = !!(user.Policy && user.Policy.IsAdministrator); res.cookie('emby_user', JSON.stringify({ id: user.Id, name: user.Name, - isAdmin: isAdmin, - token: authData.AccessToken + isAdmin: isAdmin }), { httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', maxAge: 24 * 60 * 60 * 1000 // 24 hours }); @@ -91,7 +93,11 @@ router.get('/me', (req, res) => { // Logout router.post('/logout', (req, res) => { - res.clearCookie('emby_user'); + res.clearCookie('emby_user', { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict' + }); res.json({ success: true }); }); diff --git a/server/routes/emby.js b/server/routes/emby.js index 6dab4da..4854eef 100644 --- a/server/routes/emby.js +++ b/server/routes/emby.js @@ -1,10 +1,13 @@ const express = require('express'); const axios = require('axios'); const router = express.Router(); +const requireAuth = require('../middleware/requireAuth'); const EMBY_URL = process.env.EMBY_URL; const EMBY_API_KEY = process.env.EMBY_API_KEY; +router.use(requireAuth); + // Get active sessions router.get('/sessions', async (req, res) => { try { diff --git a/server/routes/radarr.js b/server/routes/radarr.js index 083670f..99115de 100644 --- a/server/routes/radarr.js +++ b/server/routes/radarr.js @@ -1,10 +1,13 @@ const express = require('express'); const axios = require('axios'); const router = express.Router(); +const requireAuth = require('../middleware/requireAuth'); const RADARR_URL = process.env.RADARR_URL; const RADARR_API_KEY = process.env.RADARR_API_KEY; +router.use(requireAuth); + // Get queue router.get('/queue', async (req, res) => { try { diff --git a/server/routes/sabnzbd.js b/server/routes/sabnzbd.js index 0120ee3..ffa479b 100644 --- a/server/routes/sabnzbd.js +++ b/server/routes/sabnzbd.js @@ -1,10 +1,13 @@ const express = require('express'); const axios = require('axios'); const router = express.Router(); +const requireAuth = require('../middleware/requireAuth'); const SABNZBD_URL = process.env.SABNZBD_URL; const SABNZBD_API_KEY = process.env.SABNZBD_API_KEY; +router.use(requireAuth); + // Get current queue router.get('/queue', async (req, res) => { try { diff --git a/server/routes/sonarr.js b/server/routes/sonarr.js index 68ea828..e0989c0 100644 --- a/server/routes/sonarr.js +++ b/server/routes/sonarr.js @@ -1,10 +1,13 @@ const express = require('express'); const axios = require('axios'); const router = express.Router(); +const requireAuth = require('../middleware/requireAuth'); const SONARR_URL = process.env.SONARR_URL; const SONARR_API_KEY = process.env.SONARR_API_KEY; +router.use(requireAuth); + // Get queue router.get('/queue', async (req, res) => { try {