refactor: extract authService and use hashed admin_password as jwt_secret
This commit is contained in:
@@ -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();
|
||||
},
|
||||
};
|
||||
|
||||
7
backend/package-lock.json
generated
7
backend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
73
backend/services/auth/JwtService.js
Normal file
73
backend/services/auth/JwtService.js
Normal 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();
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user