Files
sofarr/server/routes/auth.js
Gronod 83049786eb
All checks were successful
Build and Push Docker Image / build (push) Successful in 39s
security: fix issues #1-4 from security audit
#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.
2026-05-16 15:07:50 +01:00

105 lines
2.8 KiB
JavaScript

const express = require('express');
const axios = require('axios');
const router = express.Router();
const EMBY_URL = process.env.EMBY_URL;
const EMBY_API_KEY = process.env.EMBY_API_KEY;
// Authenticate user with Emby
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
console.log(`[Auth] Attempting login for user: ${username}`);
// Authenticate with Emby
const authResponse = await axios.post(`${EMBY_URL}/Users/authenticatebyname`, {
Username: username,
Pw: password
}, {
headers: {
'X-Emby-Authorization': `MediaBrowser Client="MediaDashboard", Device="Browser", DeviceId="dashboard-${Date.now()}", Version="1.0.0"`
}
});
const authData = authResponse.data;
console.log(`[Auth] Emby auth response:`, JSON.stringify(authData));
// Get user info using the access token
const userResponse = await axios.get(`${EMBY_URL}/Users/${authData.User.Id || authData.User.id}`, {
headers: {
'X-MediaBrowser-Token': authData.AccessToken
}
});
const user = userResponse.data;
console.log(`[Auth] User info:`, JSON.stringify(user));
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
}), {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
});
res.json({
success: true,
user: {
id: user.Id,
name: user.Name,
isAdmin: isAdmin
}
});
} catch (error) {
console.error(`[Auth] Login failed:`, error.message);
res.status(401).json({
success: false,
error: 'Invalid username or password'
});
}
});
// Get current authenticated user
router.get('/me', (req, res) => {
try {
const userCookie = req.cookies.emby_user;
if (!userCookie) {
return res.json({ authenticated: false });
}
const user = JSON.parse(userCookie);
res.json({
authenticated: true,
user: {
id: user.id,
name: user.name,
isAdmin: !!user.isAdmin
}
});
} catch (error) {
console.error(`[Auth] Error getting current user:`, error.message);
res.json({ authenticated: false });
}
});
// Logout
router.post('/logout', (req, res) => {
res.clearCookie('emby_user', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict'
});
res.json({ success: true });
});
module.exports = router;