refactor: extract authService and use hashed admin_password as jwt_secret

This commit is contained in:
antebrl
2025-05-03 18:40:24 +02:00
parent ee16181219
commit 170de2da33
6 changed files with 115 additions and 57 deletions

View File

@@ -1,68 +1,57 @@
require('dotenv').config();
const jwt = require('jsonwebtoken');
const ADMIN_ENABLED = process.env.ADMIN_ENABLED === 'true';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD;
const JWT_SECRET = process.env.JWT_SECRET || 'streamhub-jwt-secret';
const JWT_EXPIRY = '24h';
require("dotenv").config();
const authService = require("../services/auth/JwtService");
module.exports = {
adminLogin(req, res) {
if (!ADMIN_ENABLED || ADMIN_PASSWORD === undefined) {
return res.status(403).json({
if (!authService.isAdminEnabled()) {
return res.status(403).json({
success: false,
message: 'Admin mode is disabled on this server'
message: "Admin mode is disabled on this server",
});
}
const { password } = req.body;
if (password === ADMIN_PASSWORD) {
// Generate JWT token
const token = jwt.sign(
{ isAdmin: true },
JWT_SECRET,
{ expiresIn: JWT_EXPIRY }
);
if (authService.verifyAdminPassword(password)) {
const token = authService.generateAdminToken();
return res.json({
return res.json({
success: true,
token
token,
});
} else {
return res.status(401).json({
return res.status(401).json({
success: false,
message: 'Invalid password'
message: "Invalid password",
});
}
},
checkAdminStatus(req, res) {
res.json({
enabled: ADMIN_ENABLED
res.json({
enabled: authService.isAdminEnabled(),
});
},
// Verify JWT token middleware
verifyToken(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
const token = req.headers.authorization?.split(" ")[1];
if (!token) {
return res.status(401).json({
success: false,
message: 'Access denied. No token provided.'
return res.status(401).json({
success: false,
message: "Access denied. No token provided.",
});
}
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({
success: false,
message: 'Invalid token.'
const decoded = authService.verifyToken(token);
if (!decoded) {
return res.status(401).json({
success: false,
message: "Invalid token.",
});
}
}
};
req.user = decoded;
next();
},
};

View File

@@ -11,6 +11,7 @@
"dependencies": {
"child_process": "^1.0.2",
"cookie-parser": "^1.4.6",
"crypto": "^1.0.1",
"dotenv": "^16.4.5",
"express": "^4.21.1",
"iptv-playlist-parser": "^0.13.0",
@@ -275,6 +276,12 @@
"node": ">= 0.10"
}
},
"node_modules/crypto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in."
},
"node_modules/dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",

View File

@@ -21,6 +21,7 @@
"dependencies": {
"child_process": "^1.0.2",
"cookie-parser": "^1.4.6",
"crypto": "^1.0.1",
"dotenv": "^16.4.5",
"express": "^4.21.1",
"iptv-playlist-parser": "^0.13.0",

View File

@@ -0,0 +1,73 @@
const jwt = require("jsonwebtoken");
const crypto = require("crypto");
require("dotenv").config();
/**
* Service for handling JWT authentication
*/
class AuthService {
constructor() {
this.ADMIN_ENABLED = process.env.ADMIN_ENABLED === "true";
this.ADMIN_PASSWORD = process.env.ADMIN_PASSWORD;
this.JWT_EXPIRY = process.env.JWT_EXPIRY || "24h";
// Validate admin password if admin mode is enabled
if (
this.ADMIN_ENABLED &&
(!this.ADMIN_PASSWORD || this.ADMIN_PASSWORD.length < 12)
) {
throw new Error(
"ADMIN_PASSWORD must be set and at least 12 characters long for security."
);
}
// Generate a secure JWT secret from the admin password
// or use a random value if admin mode is disabled
this.JWT_SECRET = crypto
.createHash("sha256")
.update(this.ADMIN_PASSWORD || "")
.digest("hex");
}
/**
* Generate a JWT token for an admin user
* @returns {string} JWT token
*/
generateAdminToken() {
return jwt.sign({ isAdmin: true }, this.JWT_SECRET, {
expiresIn: this.JWT_EXPIRY,
});
}
/**
* Verify a JWT token
* @param {string} token - The JWT token to verify
* @returns {Object|null} Decoded token payload or null if invalid
*/
verifyToken(token) {
try {
return jwt.verify(token, this.JWT_SECRET);
} catch (error) {
return null;
}
}
/**
* Check if admin mode is enabled
* @returns {boolean} True if admin mode is enabled
*/
isAdminEnabled() {
return this.ADMIN_ENABLED;
}
/**
* Verify admin password
* @param {string} password - Password to verify
* @returns {boolean} True if password matches
*/
verifyAdminPassword(password) {
return this.ADMIN_PASSWORD === password;
}
}
module.exports = new AuthService();

View File

@@ -1,7 +1,4 @@
const jwt = require('jsonwebtoken');
require('dotenv').config();
const JWT_SECRET = process.env.JWT_SECRET || 'streamhub-jwt-secret';
const authService = require("../../services/auth/JwtService");
/**
* Socket.io middleware to authenticate users via JWT token
@@ -16,18 +13,11 @@ function socketAuthMiddleware(socket, next) {
return next();
}
try {
const decoded = jwt.verify(token, JWT_SECRET);
// Attach the decoded user info to the socket for use in handlers
socket.user = decoded;
return next();
} catch (error) {
// If token is invalid, connect without admin privileges
socket.user = { isAdmin: false };
return next();
}
const decoded = authService.verifyToken(token);
// Attach the decoded user info (or default non-admin) to the socket
socket.user = decoded || { isAdmin: false };
return next();
}
module.exports = socketAuthMiddleware;
module.exports = socketAuthMiddleware;

View File

@@ -33,8 +33,6 @@ services:
# Admin mode configuration
ADMIN_ENABLED: "true"
ADMIN_PASSWORD: "your_secure_password"
# JWT Secret for admin authentication
JWT_SECRET: "change_this_to_a_secure_random_string"
networks:
- app-network