security: fix issues #1-4 from security audit
Build and Push Docker Image / build (push) Successful in 39s
Build and Push Docker Image / build (push) Successful in 39s
#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.
This commit is contained in:
@@ -13,3 +13,4 @@ README.md
|
|||||||
.dockerignore
|
.dockerignore
|
||||||
Dockerfile
|
Dockerfile
|
||||||
.gitea/
|
.gitea/
|
||||||
|
docs/
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const cors = require('cors');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -59,7 +58,6 @@ const { startPoller, POLL_INTERVAL, POLLING_ENABLED } = require('./utils/poller'
|
|||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3001;
|
const PORT = process.env.PORT || 3001;
|
||||||
|
|
||||||
app.use(cors());
|
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.static(path.join(__dirname, '../public')));
|
app.use(express.static(path.join(__dirname, '../public')));
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -37,14 +37,16 @@ router.post('/login', async (req, res) => {
|
|||||||
console.log(`[Auth] Login successful for user: ${user.Name}`);
|
console.log(`[Auth] Login successful for user: ${user.Name}`);
|
||||||
|
|
||||||
// Set authentication cookie
|
// Set authentication cookie
|
||||||
|
// Note: token is intentionally excluded from the cookie — it is not needed client-side
|
||||||
const isAdmin = !!(user.Policy && user.Policy.IsAdministrator);
|
const isAdmin = !!(user.Policy && user.Policy.IsAdministrator);
|
||||||
res.cookie('emby_user', JSON.stringify({
|
res.cookie('emby_user', JSON.stringify({
|
||||||
id: user.Id,
|
id: user.Id,
|
||||||
name: user.Name,
|
name: user.Name,
|
||||||
isAdmin: isAdmin,
|
isAdmin: isAdmin
|
||||||
token: authData.AccessToken
|
|
||||||
}), {
|
}), {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
sameSite: 'strict',
|
||||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,7 +93,11 @@ router.get('/me', (req, res) => {
|
|||||||
|
|
||||||
// Logout
|
// Logout
|
||||||
router.post('/logout', (req, res) => {
|
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 });
|
res.json({ success: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const requireAuth = require('../middleware/requireAuth');
|
||||||
|
|
||||||
const EMBY_URL = process.env.EMBY_URL;
|
const EMBY_URL = process.env.EMBY_URL;
|
||||||
const EMBY_API_KEY = process.env.EMBY_API_KEY;
|
const EMBY_API_KEY = process.env.EMBY_API_KEY;
|
||||||
|
|
||||||
|
router.use(requireAuth);
|
||||||
|
|
||||||
// Get active sessions
|
// Get active sessions
|
||||||
router.get('/sessions', async (req, res) => {
|
router.get('/sessions', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const requireAuth = require('../middleware/requireAuth');
|
||||||
|
|
||||||
const RADARR_URL = process.env.RADARR_URL;
|
const RADARR_URL = process.env.RADARR_URL;
|
||||||
const RADARR_API_KEY = process.env.RADARR_API_KEY;
|
const RADARR_API_KEY = process.env.RADARR_API_KEY;
|
||||||
|
|
||||||
|
router.use(requireAuth);
|
||||||
|
|
||||||
// Get queue
|
// Get queue
|
||||||
router.get('/queue', async (req, res) => {
|
router.get('/queue', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const requireAuth = require('../middleware/requireAuth');
|
||||||
|
|
||||||
const SABNZBD_URL = process.env.SABNZBD_URL;
|
const SABNZBD_URL = process.env.SABNZBD_URL;
|
||||||
const SABNZBD_API_KEY = process.env.SABNZBD_API_KEY;
|
const SABNZBD_API_KEY = process.env.SABNZBD_API_KEY;
|
||||||
|
|
||||||
|
router.use(requireAuth);
|
||||||
|
|
||||||
// Get current queue
|
// Get current queue
|
||||||
router.get('/queue', async (req, res) => {
|
router.get('/queue', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const requireAuth = require('../middleware/requireAuth');
|
||||||
|
|
||||||
const SONARR_URL = process.env.SONARR_URL;
|
const SONARR_URL = process.env.SONARR_URL;
|
||||||
const SONARR_API_KEY = process.env.SONARR_API_KEY;
|
const SONARR_API_KEY = process.env.SONARR_API_KEY;
|
||||||
|
|
||||||
|
router.use(requireAuth);
|
||||||
|
|
||||||
// Get queue
|
// Get queue
|
||||||
router.get('/queue', async (req, res) => {
|
router.get('/queue', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user