UNPKG

webssh2-server

Version:

A Websocket to SSH2 gateway using xterm.js, socket.io, ssh2

126 lines (125 loc) 6.13 kB
// app/config/env-mapper.ts // Pure functions for mapping environment variables to configuration import { parseEnvValue } from './env-parser.js'; import { getAlgorithmPreset } from './algorithm-presets.js'; import { createSafeKey, safeGet, safePathToKeys, safeSetNested } from '../utils/index.js'; /** * Static mapping of environment variables to configuration paths * @pure */ export const ENV_VAR_MAPPING = { PORT: { path: 'listen.port', type: 'number' }, WEBSSH2_LISTEN_IP: { path: 'listen.ip', type: 'string' }, WEBSSH2_LISTEN_PORT: { path: 'listen.port', type: 'number' }, WEBSSH2_HTTP_ORIGINS: { path: 'http.origins', type: 'array' }, WEBSSH2_USER_NAME: { path: 'user.name', type: 'string' }, WEBSSH2_USER_PASSWORD: { path: 'user.password', type: 'string' }, WEBSSH2_USER_PRIVATE_KEY: { path: 'user.privateKey', type: 'string' }, WEBSSH2_USER_PASSPHRASE: { path: 'user.passphrase', type: 'string' }, WEBSSH2_SSH_HOST: { path: 'ssh.host', type: 'string' }, WEBSSH2_SSH_PORT: { path: 'ssh.port', type: 'number' }, WEBSSH2_SSH_LOCAL_ADDRESS: { path: 'ssh.localAddress', type: 'string' }, WEBSSH2_SSH_LOCAL_PORT: { path: 'ssh.localPort', type: 'number' }, WEBSSH2_SSH_TERM: { path: 'ssh.term', type: 'string' }, WEBSSH2_SSH_ENV_ALLOWLIST: { path: 'ssh.envAllowlist', type: 'array' }, WEBSSH2_SSH_READY_TIMEOUT: { path: 'ssh.readyTimeout', type: 'number' }, WEBSSH2_SSH_KEEPALIVE_INTERVAL: { path: 'ssh.keepaliveInterval', type: 'number' }, WEBSSH2_SSH_KEEPALIVE_COUNT_MAX: { path: 'ssh.keepaliveCountMax', type: 'number' }, WEBSSH2_SSH_ALLOWED_SUBNETS: { path: 'ssh.allowedSubnets', type: 'array' }, WEBSSH2_SSH_ALWAYS_SEND_KEYBOARD_INTERACTIVE: { path: 'ssh.alwaysSendKeyboardInteractivePrompts', type: 'boolean', }, WEBSSH2_SSH_DISABLE_INTERACTIVE_AUTH: { path: 'ssh.disableInteractiveAuth', type: 'boolean' }, WEBSSH2_SSH_ALGORITHMS_CIPHER: { path: 'ssh.algorithms.cipher', type: 'array' }, WEBSSH2_SSH_ALGORITHMS_KEX: { path: 'ssh.algorithms.kex', type: 'array' }, WEBSSH2_SSH_ALGORITHMS_HMAC: { path: 'ssh.algorithms.hmac', type: 'array' }, WEBSSH2_SSH_ALGORITHMS_COMPRESS: { path: 'ssh.algorithms.compress', type: 'array' }, WEBSSH2_SSH_ALGORITHMS_SERVER_HOST_KEY: { path: 'ssh.algorithms.serverHostKey', type: 'array' }, WEBSSH2_SSH_ALGORITHMS_PRESET: { path: 'ssh.algorithms', type: 'preset' }, WEBSSH2_HEADER_TEXT: { path: 'header.text', type: 'string' }, WEBSSH2_HEADER_BACKGROUND: { path: 'header.background', type: 'string' }, WEBSSH2_OPTIONS_CHALLENGE_BUTTON: { path: 'options.challengeButton', type: 'boolean' }, WEBSSH2_OPTIONS_AUTO_LOG: { path: 'options.autoLog', type: 'boolean' }, WEBSSH2_OPTIONS_ALLOW_REAUTH: { path: 'options.allowReauth', type: 'boolean' }, WEBSSH2_OPTIONS_ALLOW_RECONNECT: { path: 'options.allowReconnect', type: 'boolean' }, WEBSSH2_OPTIONS_ALLOW_REPLAY: { path: 'options.allowReplay', type: 'boolean' }, WEBSSH2_OPTIONS_REPLAY_CRLF: { path: 'options.replayCRLF', type: 'boolean' }, WEBSSH2_SESSION_SECRET: { path: 'session.secret', type: 'string' }, WEBSSH2_SESSION_NAME: { path: 'session.name', type: 'string' }, WEBSSH2_SSO_ENABLED: { path: 'sso.enabled', type: 'boolean' }, WEBSSH2_SSO_CSRF_PROTECTION: { path: 'sso.csrfProtection', type: 'boolean' }, WEBSSH2_SSO_TRUSTED_PROXIES: { path: 'sso.trustedProxies', type: 'array' }, WEBSSH2_SSO_HEADER_USERNAME: { path: 'sso.headerMapping.username', type: 'string' }, WEBSSH2_SSO_HEADER_PASSWORD: { path: 'sso.headerMapping.password', type: 'string' }, WEBSSH2_SSO_HEADER_SESSION: { path: 'sso.headerMapping.session', type: 'string' }, }; /** * Map environment variables to configuration object * @param env - Environment variables object * @returns Configuration object with mapped values * @pure */ export function mapEnvironmentVariables(env) { const config = {}; for (const [envVar, mapping] of Object.entries(ENV_VAR_MAPPING)) { // Access restricted to known keys from ENV_VAR_MAPPING const envValue = safeGet(env, createSafeKey(envVar)); if (envValue !== undefined && typeof envValue === 'string') { if (mapping.type === 'preset') { const preset = getAlgorithmPreset(envValue); if (preset != null) { setNestedProperty(config, mapping.path, preset); } } else { const parsedValue = parseEnvValue(envValue, mapping.type); setNestedProperty(config, mapping.path, parsedValue); } } } return config; } /** * Set a nested property in an object using dot notation path * @param obj - Object to modify * @param path - Dot-separated path to property * @param value - Value to set * @pure - Note: This function mutates obj for efficiency, but could be made pure by returning a new object */ export function setNestedProperty(obj, path, value) { // Convert path to SafeKeys since paths come from static ENV_VAR_MAPPING const safeKeys = safePathToKeys(path); safeSetNested(obj, safeKeys, value); } /** * Create immutable nested property setter * @param obj - Original object * @param path - Dot-separated path to property * @param value - Value to set * @returns New object with property set * @pure */ export function setNestedPropertyImmutable(obj, path, value) { const keys = path.split('.'); if (keys.length === 0) { return obj; } const [head, ...tail] = keys; if (head === undefined || head === '') { return obj; } if (tail.length === 0) { return { ...obj, [head]: value }; } // Head is validated above to be non-empty string, safe to use const safeHead = createSafeKey(head); const current = safeGet(obj, safeHead); const nested = current != null && typeof current === 'object' && !Array.isArray(current) ? current : {}; return { ...obj, [head]: setNestedPropertyImmutable(nested, tail.join('.'), value), }; }