webssh2-server
Version:
A Websocket to SSH2 gateway using xterm.js, socket.io, ssh2
305 lines (230 loc) • 11.7 kB
JavaScript
// server
// tests/envConfig.test.js
import { test, describe, beforeEach, afterEach } from 'node:test'
import assert from 'node:assert/strict'
import { loadEnvironmentConfig, getEnvironmentVariableMap, getAlgorithmPresets } from '../app/envConfig.js'
import { cleanupEnvironmentVariables, storeEnvironmentVariables, restoreEnvironmentVariables } from './test-helpers.js'
describe('Environment Configuration Tests', () => {
let originalEnv = {}
beforeEach(() => {
// Store original environment variables
originalEnv = storeEnvironmentVariables()
// Clean up all environment variables
cleanupEnvironmentVariables()
})
afterEach(() => {
// Restore original environment variables
restoreEnvironmentVariables(originalEnv)
})
test('loadEnvironmentConfig returns empty object when no env vars set', () => {
const config = loadEnvironmentConfig()
assert.deepEqual(config, {})
})
test('loadEnvironmentConfig handles simple string values', () => {
process.env.WEBSSH2_SSH_HOST = 'test.example.com'
process.env.WEBSSH2_HEADER_TEXT = 'Test Header'
const config = loadEnvironmentConfig()
assert.equal(config.ssh.host, 'test.example.com')
assert.equal(config.header.text, 'Test Header')
})
test('loadEnvironmentConfig handles numeric values', () => {
process.env.WEBSSH2_LISTEN_PORT = '3000'
process.env.WEBSSH2_SSH_PORT = '2222'
process.env.WEBSSH2_SSH_READY_TIMEOUT = '30000'
const config = loadEnvironmentConfig()
assert.equal(config.listen.port, 3000)
assert.equal(config.ssh.port, 2222)
assert.equal(config.ssh.readyTimeout, 30000)
})
test('loadEnvironmentConfig handles boolean values', () => {
process.env.WEBSSH2_OPTIONS_CHALLENGE_BUTTON = 'true'
process.env.WEBSSH2_OPTIONS_AUTO_LOG = 'false'
process.env.WEBSSH2_SSH_DISABLE_INTERACTIVE_AUTH = '1'
process.env.WEBSSH2_SSH_ALWAYS_SEND_KEYBOARD_INTERACTIVE = '0'
const config = loadEnvironmentConfig()
assert.equal(config.options.challengeButton, true)
assert.equal(config.options.autoLog, false)
assert.equal(config.ssh.disableInteractiveAuth, true)
assert.equal(config.ssh.alwaysSendKeyboardInteractivePrompts, false)
})
test('loadEnvironmentConfig handles null values', () => {
process.env.WEBSSH2_SSH_HOST = 'null'
process.env.WEBSSH2_USER_NAME = ''
process.env.WEBSSH2_HEADER_TEXT = 'null'
const config = loadEnvironmentConfig()
assert.equal(config.ssh.host, null)
assert.equal(config.user.name, null)
assert.equal(config.header.text, null)
})
test('loadEnvironmentConfig handles comma-separated arrays', () => {
process.env.WEBSSH2_HTTP_ORIGINS = 'localhost:3000,*.example.com,api.test.com'
process.env.WEBSSH2_SSH_ALLOWED_SUBNETS = '192.168.1.0/24,10.0.0.0/8'
const config = loadEnvironmentConfig()
assert.deepEqual(config.http.origins, ['localhost:3000', '*.example.com', 'api.test.com'])
assert.deepEqual(config.ssh.allowedSubnets, ['192.168.1.0/24', '10.0.0.0/8'])
})
test('loadEnvironmentConfig handles JSON array format', () => {
process.env.WEBSSH2_SSH_ALGORITHMS_CIPHER = '["aes256-gcm@openssh.com","aes128-ctr","aes256-ctr"]'
process.env.WEBSSH2_HTTP_ORIGINS = '["https://example.com","https://api.example.com"]'
const config = loadEnvironmentConfig()
assert.deepEqual(config.ssh.algorithms.cipher, ['aes256-gcm@openssh.com', 'aes128-ctr', 'aes256-ctr'])
assert.deepEqual(config.http.origins, ['https://example.com', 'https://api.example.com'])
})
test('loadEnvironmentConfig handles malformed JSON arrays gracefully', () => {
process.env.WEBSSH2_HTTP_ORIGINS = '["invalid json'
const config = loadEnvironmentConfig()
// Should fall back to comma-separated parsing
assert.deepEqual(config.http.origins, ['["invalid json'])
})
test('loadEnvironmentConfig handles empty arrays', () => {
process.env.WEBSSH2_HTTP_ORIGINS = ''
process.env.WEBSSH2_SSH_ALLOWED_SUBNETS = '[]'
const config = loadEnvironmentConfig()
assert.deepEqual(config.http.origins, [])
assert.deepEqual(config.ssh.allowedSubnets, [])
})
test('loadEnvironmentConfig handles SSH algorithm presets', () => {
process.env.WEBSSH2_SSH_ALGORITHMS_PRESET = 'modern'
const config = loadEnvironmentConfig()
assert.ok(Array.isArray(config.ssh.algorithms.cipher))
assert.ok(config.ssh.algorithms.cipher.includes('aes256-gcm@openssh.com'))
assert.ok(Array.isArray(config.ssh.algorithms.kex))
assert.ok(config.ssh.algorithms.kex.includes('ecdh-sha2-nistp256'))
})
test('loadEnvironmentConfig handles legacy algorithm preset', () => {
process.env.WEBSSH2_SSH_ALGORITHMS_PRESET = 'legacy'
const config = loadEnvironmentConfig()
assert.ok(Array.isArray(config.ssh.algorithms.cipher))
assert.ok(config.ssh.algorithms.cipher.includes('aes256-cbc'))
assert.ok(Array.isArray(config.ssh.algorithms.kex))
assert.ok(config.ssh.algorithms.kex.includes('diffie-hellman-group14-sha1'))
})
test('loadEnvironmentConfig handles strict algorithm preset', () => {
process.env.WEBSSH2_SSH_ALGORITHMS_PRESET = 'strict'
const config = loadEnvironmentConfig()
assert.deepEqual(config.ssh.algorithms.cipher, ['aes256-gcm@openssh.com'])
assert.deepEqual(config.ssh.algorithms.kex, ['ecdh-sha2-nistp256'])
assert.deepEqual(config.ssh.algorithms.hmac, ['hmac-sha2-256'])
})
test('loadEnvironmentConfig handles unknown algorithm preset gracefully', () => {
process.env.WEBSSH2_SSH_ALGORITHMS_PRESET = 'unknown'
const config = loadEnvironmentConfig()
// Should not set algorithms if preset is unknown
assert.equal(config.ssh?.algorithms, undefined)
})
test('loadEnvironmentConfig supports legacy PORT variable', () => {
process.env.PORT = '8080'
const config = loadEnvironmentConfig()
assert.equal(config.listen.port, 8080)
})
test('loadEnvironmentConfig WEBSSH2_LISTEN_PORT overrides PORT', () => {
process.env.PORT = '8080'
process.env.WEBSSH2_LISTEN_PORT = '9000'
const config = loadEnvironmentConfig()
// WEBSSH2_LISTEN_PORT should be processed after PORT
// Both set the same path, so the last one wins
assert.equal(config.listen.port, 9000)
})
test('loadEnvironmentConfig handles complex nested configuration', () => {
process.env.WEBSSH2_LISTEN_IP = '127.0.0.1'
process.env.WEBSSH2_LISTEN_PORT = '3000'
process.env.WEBSSH2_SSH_HOST = 'production.example.com'
process.env.WEBSSH2_SSH_PORT = '2222'
process.env.WEBSSH2_SSH_ALGORITHMS_CIPHER = 'aes256-gcm@openssh.com,aes256-ctr'
process.env.WEBSSH2_SSH_ALGORITHMS_KEX = 'ecdh-sha2-nistp256'
process.env.WEBSSH2_HEADER_TEXT = 'Production Environment'
process.env.WEBSSH2_HEADER_BACKGROUND = 'red'
process.env.WEBSSH2_OPTIONS_AUTO_LOG = 'true'
process.env.WEBSSH2_SESSION_SECRET = 'production-secret'
const config = loadEnvironmentConfig()
assert.equal(config.listen.ip, '127.0.0.1')
assert.equal(config.listen.port, 3000)
assert.equal(config.ssh.host, 'production.example.com')
assert.equal(config.ssh.port, 2222)
assert.deepEqual(config.ssh.algorithms.cipher, ['aes256-gcm@openssh.com', 'aes256-ctr'])
assert.deepEqual(config.ssh.algorithms.kex, ['ecdh-sha2-nistp256'])
assert.equal(config.header.text, 'Production Environment')
assert.equal(config.header.background, 'red')
assert.equal(config.options.autoLog, true)
assert.equal(config.session.secret, 'production-secret')
})
test('getEnvironmentVariableMap returns all supported variables', () => {
const envMap = getEnvironmentVariableMap()
assert.ok(typeof envMap === 'object')
assert.ok('WEBSSH2_LISTEN_PORT' in envMap)
assert.ok('WEBSSH2_SSH_HOST' in envMap)
assert.ok('WEBSSH2_SSH_ALGORITHMS_PRESET' in envMap)
assert.ok('PORT' in envMap)
// Check structure of mapping
assert.equal(envMap.WEBSSH2_LISTEN_PORT.path, 'listen.port')
assert.equal(envMap.WEBSSH2_LISTEN_PORT.type, 'number')
assert.ok(typeof envMap.WEBSSH2_LISTEN_PORT.description === 'string')
})
test('getAlgorithmPresets returns all available presets', () => {
const presets = getAlgorithmPresets()
assert.ok(typeof presets === 'object')
assert.ok('modern' in presets)
assert.ok('legacy' in presets)
assert.ok('strict' in presets)
// Check structure of presets
assert.ok(Array.isArray(presets.modern.cipher))
assert.ok(Array.isArray(presets.modern.kex))
assert.ok(Array.isArray(presets.modern.hmac))
assert.ok(Array.isArray(presets.modern.compress))
assert.ok(Array.isArray(presets.modern.serverHostKey))
})
test('loadEnvironmentConfig handles whitespace in comma-separated arrays', () => {
process.env.WEBSSH2_HTTP_ORIGINS = 'localhost:3000, *.example.com , api.test.com'
const config = loadEnvironmentConfig()
assert.deepEqual(config.http.origins, ['localhost:3000', '*.example.com', 'api.test.com'])
})
test('loadEnvironmentConfig filters out empty array elements', () => {
process.env.WEBSSH2_HTTP_ORIGINS = 'localhost:3000,,api.test.com,'
const config = loadEnvironmentConfig()
assert.deepEqual(config.http.origins, ['localhost:3000', 'api.test.com'])
})
test('loadEnvironmentConfig handles all SSH algorithm types individually', () => {
process.env.WEBSSH2_SSH_ALGORITHMS_CIPHER = 'aes256-gcm@openssh.com'
process.env.WEBSSH2_SSH_ALGORITHMS_KEX = 'ecdh-sha2-nistp256'
process.env.WEBSSH2_SSH_ALGORITHMS_HMAC = 'hmac-sha2-256'
process.env.WEBSSH2_SSH_ALGORITHMS_COMPRESS = 'none'
process.env.WEBSSH2_SSH_ALGORITHMS_SERVER_HOST_KEY = 'ecdsa-sha2-nistp256'
const config = loadEnvironmentConfig()
assert.deepEqual(config.ssh.algorithms.cipher, ['aes256-gcm@openssh.com'])
assert.deepEqual(config.ssh.algorithms.kex, ['ecdh-sha2-nistp256'])
assert.deepEqual(config.ssh.algorithms.hmac, ['hmac-sha2-256'])
assert.deepEqual(config.ssh.algorithms.compress, ['none'])
assert.deepEqual(config.ssh.algorithms.serverHostKey, ['ecdsa-sha2-nistp256'])
})
test('loadEnvironmentConfig handles all user credential fields', () => {
process.env.WEBSSH2_USER_NAME = 'testuser'
process.env.WEBSSH2_USER_PASSWORD = 'testpassword'
process.env.WEBSSH2_USER_PRIVATE_KEY = 'base64encodedkey'
process.env.WEBSSH2_USER_PASSPHRASE = 'keypassphrase'
const config = loadEnvironmentConfig()
assert.equal(config.user.name, 'testuser')
assert.equal(config.user.password, 'testpassword')
assert.equal(config.user.privateKey, 'base64encodedkey')
assert.equal(config.user.passphrase, 'keypassphrase')
})
test('loadEnvironmentConfig handles all session configuration', () => {
process.env.WEBSSH2_SESSION_SECRET = 'my-session-secret'
process.env.WEBSSH2_SESSION_NAME = 'custom.sid'
const config = loadEnvironmentConfig()
assert.equal(config.session.secret, 'my-session-secret')
assert.equal(config.session.name, 'custom.sid')
})
test('loadEnvironmentConfig handles all options configuration', () => {
process.env.WEBSSH2_OPTIONS_CHALLENGE_BUTTON = 'false'
process.env.WEBSSH2_OPTIONS_AUTO_LOG = 'true'
process.env.WEBSSH2_OPTIONS_ALLOW_REAUTH = 'false'
process.env.WEBSSH2_OPTIONS_ALLOW_RECONNECT = 'false'
process.env.WEBSSH2_OPTIONS_ALLOW_REPLAY = 'false'
const config = loadEnvironmentConfig()
assert.equal(config.options.challengeButton, false)
assert.equal(config.options.autoLog, true)
assert.equal(config.options.allowReauth, false)
assert.equal(config.options.allowReconnect, false)
assert.equal(config.options.allowReplay, false)
})
})