chore: linting

This commit is contained in:
Bill Church 2024-08-21 11:04:28 +00:00
parent dbcfc30cd0
commit da21c89f20
No known key found for this signature in database
7 changed files with 207 additions and 240 deletions

View file

@ -1,9 +1,7 @@
// server
// app/app.js
"use strict"
const createDebug = require("debug")
const debug = createDebug("webssh2")
// const createDebug = require("debug")
const http = require("http")
const express = require("express")
const socketIo = require("socket.io")
@ -14,6 +12,9 @@ const sharedsession = require("express-socket.io-session")
const config = require("./config")
const socketHandler = require("./socket")
const sshRoutes = require("./routes")
const { getCorsConfig } = require("./config")
// const debug = createDebug("webssh2")
/**
* Creates and configures the Express application
@ -54,8 +55,8 @@ function createApp() {
}
res.cookie("basicauth", JSON.stringify(cookieData), {
httpOnly: false,
path: '/ssh/host/',
sameSite: 'Strict'
path: "/ssh/host/",
sameSite: "Strict"
}) // ensure httOnly is false for the client to read the cookie
}
next()
@ -104,18 +105,6 @@ function createServer(app) {
return http.createServer(app)
}
/**
* Gets the CORS configuration
* @returns {Object} The CORS configuration object
*/
function getCorsConfig() {
return {
origin: config.origin || ["*.*"],
methods: ["GET", "POST"],
credentials: true
}
}
/**
* Handles server errors
* @param {Error} err - The error object
@ -124,6 +113,14 @@ function handleServerError(err) {
console.error("WebSSH2 server.listen ERROR:", err.code)
}
/**
* Sets up Socket.IO event listeners
* @param {import('socket.io').Server} io - The Socket.IO server instance
*/
function setupSocketIOListeners(io) {
socketHandler(io, config)
}
/**
* Initializes and starts the server
* @returns {Object} An object containing the server, io, and app instances
@ -148,13 +145,5 @@ function startServer() {
return { server, io, app }
}
/**
* Sets up Socket.IO event listeners
* @param {import('socket.io').Server} io - The Socket.IO server instance
*/
function setupSocketIOListeners(io) {
socketHandler(io, config)
}
// Don't start the server immediately, export the function instead
module.exports = { startServer, config }

View file

@ -1,12 +1,11 @@
// server
// app/config.js
"use strict"
const path = require("path")
const fs = require("fs")
const readConfig = require("read-config-ng")
const Ajv = require("ajv")
const crypto = require("crypto")
const { deepMerge, generateSecureSecret } = require("./utils")
/**
* @typedef {Object} Config
@ -204,15 +203,7 @@ const configSchema = {
required: ["secret", "name"]
}
},
required: [
"listen",
"http",
"user",
"ssh",
"header",
"options",
"algorithms"
]
required: ["listen", "http", "user", "ssh", "header", "options", "algorithms"]
}
/**
@ -230,7 +221,7 @@ function getConfigPath() {
* @returns {Object} The configuration object
*/
function readConfigFile(configPath) {
console.log("WebSSH2 service reading config from: " + configPath)
console.log(`WebSSH2 service reading config from: ${configPath}`)
return readConfig.sync(configPath)
}
@ -247,7 +238,7 @@ function validateConfig(config) {
console.log("WebSSH2 service validating config")
if (!valid) {
throw new Error(
"Config validation error: " + ajv.errorsText(validate.errors)
`Config validation error: ${ajv.errorsText(validate.errors)}`
)
}
return config
@ -261,7 +252,7 @@ function validateConfig(config) {
function logError(message, error) {
console.error(message)
if (error) {
console.error("ERROR:\n\n " + error)
console.error(`ERROR:\n\n ${error}`)
}
}
@ -289,54 +280,26 @@ function loadConfig() {
}
const validatedConfig = validateConfig(mergedConfig)
console.log('Merged and validated configuration')
console.log("Merged and validated configuration")
return validatedConfig
} else {
logError(
'\n\nERROR: Missing config.json for webssh. Using default config: ' +
JSON.stringify(defaultConfig) +
'\n\n See config.json.sample for details\n\n'
)
return defaultConfig
}
logError(
`\n\nERROR: Missing config.json for webssh. Using default config: ${JSON.stringify(
defaultConfig
)}\n\n See config.json.sample for details\n\n`
)
return defaultConfig
} catch (err) {
logError(
'\n\nERROR: Problem loading config.json for webssh. Using default config: ' +
JSON.stringify(defaultConfig) +
'\n\n See config.json.sample for details\n\n',
`\n\nERROR: Problem loading config.json for webssh. Using default config: ${JSON.stringify(
defaultConfig
)}\n\n See config.json.sample for details\n\n`,
err
)
return defaultConfig
}
}
/**
* Generates a secure random session secret
* @returns {string} A random 32-byte hex string
*/
function generateSecureSecret() {
return crypto.randomBytes(32).toString("hex")
}
/**
* Deep merges two objects
* @param {Object} target - The target object to merge into
* @param {Object} source - The source object to merge from
* @returns {Object} The merged object
*/
function deepMerge(target, source) {
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (source[key] instanceof Object && !Array.isArray(source[key])) {
target[key] = deepMerge(target[key] || {}, source[key])
} else {
target[key] = source[key]
}
}
}
return target
}
/**
* The loaded configuration
* @type {Object}
@ -344,3 +307,14 @@ function deepMerge(target, source) {
const config = loadConfig()
module.exports = config
/**
* Gets the CORS configuration
* @returns {Object} The CORS configuration object
*/
module.exports.getCorsConfig = function getCorsConfig() {
return {
origin: config.origin || ["*.*"],
methods: ["GET", "POST"],
credentials: true
}
}

View file

@ -1,59 +1,55 @@
// server
// app/connectionHandler.js
const createDebug = require('debug')
const path = require('path')
const fs = require('fs')
const extend = require('util')._extend
const debug = createDebug('webssh2:connectionHandler')
const createDebug = require("debug")
const path = require("path")
const fs = require("fs")
function handleConnection(req, res, urlParams) {
debug('Handling connection')
urlParams = urlParams || {}
const debug = createDebug("webssh2:connectionHandler")
function handleConnection(req, res) {
debug("Handling connection")
const clientPath = path.resolve(
__dirname,
'..',
'node_modules',
'webssh2_client',
'client',
'public'
"..",
"node_modules",
"webssh2_client",
"client",
"public"
)
const tempConfig = {
socket: {
url: req.protocol + '://' + req.get('host'),
path: '/ssh/socket.io'
url: `${req.protocol}://${req.get("host")}`,
path: "/ssh/socket.io"
},
autoConnect: false // Default to false
}
// Check if the current route is /host/:host
debug('handleConnection req.path:', req.path)
if (req.path.startsWith('/host/')) {
debug("handleConnection req.path:", req.path)
if (req.path.startsWith("/host/")) {
tempConfig.autoConnect = true
}
fs.readFile(
path.join(clientPath, 'client.htm'),
'utf8',
function (err, data) {
if (err) {
return res.status(500).send('Error loading client file')
}
var modifiedHtml = data.replace(
/(src|href)="(?!http|\/\/)/g,
'$1="/ssh/assets/'
)
modifiedHtml = modifiedHtml.replace(
'window.webssh2Config = null;',
'window.webssh2Config = ' + JSON.stringify(tempConfig) + ';'
)
res.send(modifiedHtml)
fs.readFile(path.join(clientPath, "client.htm"), "utf8", function(err, data) {
if (err) {
return res.status(500).send("Error loading client file")
}
)
let modifiedHtml = data.replace(
/(src|href)="(?!http|\/\/)/g,
'$1="/ssh/assets/'
)
modifiedHtml = modifiedHtml.replace(
"window.webssh2Config = null;",
`window.webssh2Config = ${JSON.stringify(tempConfig)};`
)
res.send(modifiedHtml)
// Explicitly return to satisfy the linter
})
}
module.exports = handleConnection

View file

@ -1,18 +1,20 @@
// server
// /app/routes.js
const createDebug = require("debug")
const debug = createDebug("webssh2:routes")
const express = require("express")
const router = express.Router()
const handleConnection = require("./connectionHandler")
const basicAuth = require("basic-auth")
const { validateSshTerm } = require("./utils")
const maskObject = require('jsmasker');
const maskObject = require("jsmasker")
const validator = require("validator")
const { validateSshTerm } = require("./utils")
const handleConnection = require("./connectionHandler")
function auth(req, res, next) {
debug("auth: Basic Auth")
var credentials = basicAuth(req)
const credentials = basicAuth(req)
if (!credentials) {
res.setHeader("WWW-Authenticate", 'Basic realm="WebSSH2"')
return res.status(401).send("Authentication required.")
@ -27,13 +29,13 @@ function auth(req, res, next) {
}
// Scenario 1: No auth required, uses websocket authentication instead
router.get("/", function (req, res) {
router.get("/", function(req, res) {
debug("Accessed / route")
handleConnection(req, res)
})
// Scenario 2: Auth required, uses HTTP Basic Auth
router.get("/host/:host", auth, function (req, res) {
router.get("/host/:host", auth, function(req, res) {
debug(`Accessed /ssh/host/${req.params.host} route`)
// Validate and sanitize host parameter
@ -61,7 +63,7 @@ router.get("/host/:host", auth, function (req, res) {
if (req.query.sshTerm) {
req.session.sshCredentials.term = sshTerm
}
req.session.usedBasicAuth = true
req.session.usedBasicAuth = true
// Sanitize and log the sshCredentials object
const sanitizedCredentials = maskObject(
@ -73,12 +75,12 @@ router.get("/host/:host", auth, function (req, res) {
})
// Clear credentials route
router.get("/clear-credentials", function (req, res) {
router.get("/clear-credentials", function(req, res) {
req.session.sshCredentials = null
res.status(200).send("Credentials cleared.")
})
router.get("/force-reconnect", function (req, res) {
router.get("/force-reconnect", function(req, res) {
req.session.sshCredentials = null
res.status(401).send("Authentication required.")
})

View file

@ -1,19 +1,19 @@
// server
// app/socket.js
"use strict"
const createDebug = require("debug")
const debug = createDebug("webssh2:socket")
const maskObject = require("jsmasker")
const validator = require("validator")
const SSHConnection = require("./ssh")
const { validateSshTerm } = require("./utils")
const maskObject = require('jsmasker');
const validator = require("validator")
module.exports = function (io, config) {
io.on("connection", function (socket) {
debug("io.on connection: " + socket.id)
var ssh = new SSHConnection(config)
var sessionState = {
module.exports = function(io, config) {
io.on("connection", function(socket) {
debug(`io.on connection: ${socket.id}`)
const ssh = new SSHConnection(config)
const sessionState = {
authenticated: false,
username: null,
password: null,
@ -31,7 +31,7 @@ module.exports = function (io, config) {
* @param {Object} config - The configuration object.
*/
function handleAuthenticate(creds) {
debug("handleAuthenticate: " + socket.id + ", %O", maskObject(creds))
debug(`handleAuthenticate: ${socket.id}, %O`, maskObject(creds))
if (isValidCredentials(creds)) {
sessionState.term = validateSshTerm(creds.term)
@ -39,9 +39,7 @@ module.exports = function (io, config) {
: config.ssh.term
initializeConnection(creds)
} else {
console.warn(
"handleAuthenticate: " + socket.id + ", CREDENTIALS INVALID"
)
console.warn(`handleAuthenticate: ${socket.id}, CREDENTIALS INVALID`)
socket.emit("authentication", {
success: false,
message: "Invalid credentials format"
@ -61,17 +59,13 @@ module.exports = function (io, config) {
*/
function initializeConnection(creds) {
debug(
"initializeConnection: " +
socket.id +
", INITIALIZING SSH CONNECTION: Host: " +
creds.host +
", creds: %O",
`initializeConnection: ${socket.id}, INITIALIZING SSH CONNECTION: Host: ${creds.host}, creds: %O`,
maskObject(creds)
)
ssh
.connect(creds)
.then(function () {
.then(function() {
sessionState.authenticated = true
sessionState.username = creds.username
sessionState.password = creds.password
@ -79,65 +73,51 @@ module.exports = function (io, config) {
sessionState.port = creds.port
debug(
"initializeConnection: " +
socket.id +
" conn.on ready: Host: " +
creds.host
`initializeConnection: ${socket.id} conn.on ready: Host: ${creds.host}`
)
console.log(
"initializeConnection: " +
socket.id +
" conn.on ready: " +
creds.username +
"@" +
creds.host +
":" +
creds.port +
" successfully connected"
`initializeConnection: ${socket.id} conn.on ready: ${creds.username}@${creds.host}:${creds.port} successfully connected`
)
var auth_result = { action: "auth_result", success: true }
const auth_result = { action: "auth_result", success: true }
debug(
"initializeConnection: " +
socket.id +
" conn.on ready: emitting authentication: " +
JSON.stringify(auth_result)
`initializeConnection: ${
socket.id
} conn.on ready: emitting authentication: ${JSON.stringify(
auth_result
)}`
)
socket.emit("authentication", auth_result)
// Emit consolidated permissions
var permissions = {
const permissions = {
autoLog: config.options.autoLog || false,
allowReplay: config.options.allowReplay || false,
allowReconnect: config.options.allowReconnect || false,
allowReauth: config.options.allowReauth || false
}
debug(
"initializeConnection: " +
socket.id +
" conn.on ready: emitting permissions: " +
JSON.stringify(permissions)
`initializeConnection: ${
socket.id
} conn.on ready: emitting permissions: ${JSON.stringify(
permissions
)}`
)
socket.emit("permissions", permissions)
updateElement("footer", "ssh://" + creds.host + ":" + creds.port)
updateElement("footer", `ssh://${creds.host}:${creds.port}`)
if (config.header && config.header.text !== null) {
debug("initializeConnection header: " + config.header)
debug(`initializeConnection header: ${config.header}`)
updateElement("header", config.header.text)
}
// Request terminal information from client
socket.emit("getTerminal", true)
})
.catch(function (err) {
.catch(function(err) {
console.error(
"initializeConnection: SSH CONNECTION ERROR: " +
socket.id +
", Host: " +
creds.host +
", Error: " +
err.message
`initializeConnection: SSH CONNECTION ERROR: ${socket.id}, Host: ${creds.host}, Error: ${err.message}`
)
if (err.level === "client-authentication") {
socket.emit("authentication", {
@ -161,24 +141,24 @@ module.exports = function (io, config) {
* @returns {void}
*/
function handleTerminal(data) {
debug("handleTerminal: Received terminal data: " + JSON.stringify(data))
var term = data.term
var rows = data.rows
var cols = data.cols
debug(`handleTerminal: Received terminal data: ${JSON.stringify(data)}`)
const { term } = data
const { rows } = data
const { cols } = data
if (term && validateSshTerm(term)) {
sessionState.term = term
debug("handleTerminal: Set term to " + sessionState.term)
debug(`handleTerminal: Set term to ${sessionState.term}`)
}
if (rows && validator.isInt(rows.toString())) {
sessionState.rows = parseInt(rows, 10)
debug("handleTerminal: Set rows to " + sessionState.rows)
debug(`handleTerminal: Set rows to ${sessionState.rows}`)
}
if (cols && validator.isInt(cols.toString())) {
sessionState.cols = parseInt(cols, 10)
debug("handleTerminal: Set cols to " + sessionState.cols)
debug(`handleTerminal: Set cols to ${sessionState.cols}`)
}
// Now that we have terminal information, we can create the shell
@ -199,35 +179,35 @@ module.exports = function (io, config) {
cols: sessionState.cols,
rows: sessionState.rows
})
.then(function (stream) {
stream.on("data", function (data) {
.then(function(stream) {
stream.on("data", function(data) {
socket.emit("data", data.toString("utf-8"))
})
stream.stderr.on("data", function (data) {
debug("STDERR: " + data)
stream.stderr.on("data", function(data) {
debug(`STDERR: ${data}`)
})
stream.on("close", function (code, signal) {
debug("handleStreamClose: " + socket.id)
stream.on("close", function(code, signal) {
debug(`handleStreamClose: ${socket.id}`)
handleConnectionClose()
})
socket.on("data", function (data) {
socket.on("data", function(data) {
if (stream) {
stream.write(data)
}
})
socket.on("control", function (controlData) {
socket.on("control", function(controlData) {
handleControl(controlData)
})
socket.on("resize", function (data) {
socket.on("resize", function(data) {
handleResize(data)
})
})
.catch(function (err) {
.catch(function(err) {
handleError("SHELL ERROR", err)
})
}
@ -238,8 +218,8 @@ module.exports = function (io, config) {
* @param {Object} data - The resize data containing the number of rows and columns.
*/
function handleResize(data) {
var rows = data.rows
var cols = data.cols
const { rows } = data
const { cols } = data
if (ssh.stream) {
if (rows && validator.isInt(rows.toString())) {
@ -248,9 +228,7 @@ module.exports = function (io, config) {
if (cols && validator.isInt(cols.toString())) {
sessionState.cols = parseInt(cols, 10)
}
debug(
"Resizing terminal to " + sessionState.rows + "x" + sessionState.cols
)
debug(`Resizing terminal to ${sessionState.rows}x${sessionState.cols}`)
ssh.resizeTerminal(sessionState.rows, sessionState.cols)
}
}
@ -262,7 +240,7 @@ module.exports = function (io, config) {
* @returns {void}
*/
function handleControl(controlData) {
debug("handleControl: Received control data: " + controlData)
debug(`handleControl: Received control data: ${controlData}`)
if (
validator.isIn(controlData, ["replayCredentials", "reauth"]) &&
ssh.stream
@ -274,7 +252,7 @@ module.exports = function (io, config) {
}
} else {
console.warn(
"handleControl: Invalid control command received: " + controlData
`handleControl: Invalid control command received: ${controlData}`
)
}
}
@ -285,15 +263,15 @@ module.exports = function (io, config) {
* @returns {void}
*/
function replayCredentials() {
var password = sessionState.password
var allowReplay = config.options.allowReplay || false
const { password } = sessionState
const allowReplay = config.options.allowReplay || false
if (allowReplay && ssh.stream) {
debug(`replayCredentials: ${socket.id} Replaying credentials for `)
ssh.stream.write(password + "\n")
ssh.stream.write(`${password}\n`)
} else {
console.warn(
"replayCredentials: Credential replay not allowed for " + socket.id
`replayCredentials: Credential replay not allowed for ${socket.id}`
)
}
}
@ -302,7 +280,7 @@ module.exports = function (io, config) {
* Handles reauthentication for the socket.
*/
function handleReauth() {
debug("handleReauth: Reauthentication requested for " + socket.id)
debug(`handleReauth: Reauthentication requested for ${socket.id}`)
if (config.options.allowReauth) {
clearSessionCredentials()
debug(`handleReauth: Reauthenticating ${socket.id}`)
@ -322,9 +300,9 @@ module.exports = function (io, config) {
* @param {Error} err - The error object.
*/
function handleError(context, err) {
var errorMessage = err ? ": " + err.message : ""
debug("WebSSH2 error: " + context + errorMessage)
socket.emit("ssherror", "SSH " + context + errorMessage)
const errorMessage = err ? `: ${err.message}` : ""
debug(`WebSSH2 error: ${context}${errorMessage}`)
socket.emit("ssherror", `SSH ${context}${errorMessage}`)
handleConnectionClose()
}
@ -336,14 +314,7 @@ module.exports = function (io, config) {
* @returns {void}
*/
function updateElement(element, value) {
debug(
"updateElement: " +
socket.id +
", Element: " +
element +
", Value: " +
value
)
debug(`updateElement: ${socket.id}, Element: ${element}, Value: ${value}`)
socket.emit("updateUI", { element: element, value: value })
}
@ -353,8 +324,8 @@ module.exports = function (io, config) {
* @param {string} reason - The reason for the closure.
*/
function handleConnectionClose(reason) {
debug("handleDisconnect: " + socket.id + ", Reason: " + reason)
debug("handleConnectionClose: " + socket.id)
debug(`handleDisconnect: ${socket.id}, Reason: ${reason}`)
debug(`handleConnectionClose: ${socket.id}`)
if (ssh) {
ssh.end()
}
@ -366,7 +337,7 @@ module.exports = function (io, config) {
*/
function clearSessionCredentials() {
debug(
"clearSessionCredentials: Clearing session credentials for " + socket.id
`clearSessionCredentials: Clearing session credentials for ${socket.id}`
)
if (socket.handshake.session.sshCredentials) {
socket.handshake.session.sshCredentials.username = null
@ -376,28 +347,27 @@ module.exports = function (io, config) {
sessionState.authenticated = false
sessionState.username = null
sessionState.password = null
socket.handshake.session.save(function (err) {
socket.handshake.session.save(function(err) {
if (err) {
console.error("Failed to save session for " + socket.id + ":", err)
console.error(`Failed to save session for ${socket.id}:`, err)
}
})
}
// Check for HTTP Basic Auth credentials
if (socket.handshake.session.usedBasicAuth && socket.handshake.session.sshCredentials) {
// if (socket.handshake.session.sshCredentials) {
var creds = socket.handshake.session.sshCredentials
if (
socket.handshake.session.usedBasicAuth &&
socket.handshake.session.sshCredentials
) {
// if (socket.handshake.session.sshCredentials) {
const creds = socket.handshake.session.sshCredentials
debug(
"handleConnection: " +
socket.id +
", Host: " +
creds.host +
": HTTP Basic Credentials Exist, creds: %O",
`handleConnection: ${socket.id}, Host: ${creds.host}: HTTP Basic Credentials Exist, creds: %O`,
maskObject(creds)
)
handleAuthenticate(creds)
} else if (!sessionState.authenticated) {
debug("handleConnection: " + socket.id + ", emitting request_auth")
debug(`handleConnection: ${socket.id}, emitting request_auth`)
socket.emit("authentication", { action: "request_auth" })
}

View file

@ -1,11 +1,11 @@
// server
// app/ssh.js
"use strict"
const createDebug = require("debug")
const debug = createDebug("webssh2:ssh")
const SSH = require("ssh2").Client
const maskObject = require('jsmasker');
const maskObject = require("jsmasker")
const debug = createDebug("webssh2:ssh")
function SSHConnection(config) {
this.config = config
@ -14,7 +14,7 @@ function SSHConnection(config) {
}
SSHConnection.prototype.connect = function(creds) {
var self = this
const self = this
return new Promise(function(resolve, reject) {
debug("connect: %O", maskObject(creds))
@ -23,16 +23,16 @@ SSHConnection.prototype.connect = function(creds) {
}
self.conn = new SSH()
var sshConfig = self.getSSHConfig(creds)
const sshConfig = self.getSSHConfig(creds)
self.conn.on("ready", function() {
debug("connect: ready: " + creds.host)
debug(`connect: ready: ${creds.host}`)
resolve()
})
self.conn.on("error", function(err) {
console.error("connect: error:" + err.message)
console.error(`connect: error:${err.message}`)
reject(err)
})
@ -49,14 +49,16 @@ SSHConnection.prototype.getSSHConfig = function(creds) {
tryKeyboard: true,
algorithms: creds.algorithms || this.config.ssh.algorithms,
readyTimeout: creds.readyTimeout || this.config.ssh.readyTimeout,
keepaliveInterval: creds.keepaliveInterval || this.config.ssh.keepaliveInterval,
keepaliveCountMax: creds.keepaliveCountMax || this.config.ssh.keepaliveCountMax,
keepaliveInterval:
creds.keepaliveInterval || this.config.ssh.keepaliveInterval,
keepaliveCountMax:
creds.keepaliveCountMax || this.config.ssh.keepaliveCountMax,
debug: createDebug("ssh")
}
}
SSHConnection.prototype.shell = function(options) {
var self = this
const self = this
return new Promise(function(resolve, reject) {
self.conn.shell(options, function(err, stream) {
if (err) {
@ -86,4 +88,4 @@ SSHConnection.prototype.end = function() {
}
}
module.exports = SSHConnection
module.exports = SSHConnection

View file

@ -2,6 +2,8 @@
// /app/utils.js
const validator = require("validator")
const createDebug = require("debug")
const crypto = require("crypto")
const debug = createDebug("webssh2:utils")
/**
@ -23,3 +25,35 @@ function validateSshTerm(term) {
}
exports.validateSshTerm = validateSshTerm
/**
* Deep merges two objects
* @param {Object} target - The target object to merge into
* @param {Object} source - The source object to merge from
* @returns {Object} The merged object
*/
function deepMerge(target, source) {
const output = Object.assign({}, target) // Avoid mutating target directly
Object.keys(source).forEach(key => {
if (Object.hasOwnProperty.call(source, key)) {
if (
source[key] instanceof Object &&
!Array.isArray(source[key]) &&
source[key] !== null
) {
output[key] = deepMerge(output[key] || {}, source[key])
} else {
output[key] = source[key]
}
}
})
return output
}
exports.deepMerge = deepMerge
/**
* Generates a secure random session secret
* @returns {string} A random 32-byte hex string
*/
function generateSecureSecret() {
return crypto.randomBytes(32).toString("hex")
}
exports.generateSecureSecret = generateSecureSecret