OverviewFilesCommitsIssuesDocsReleasesPeers

hyperident-shell / lib / server.js

60 lines · javascript ref:
// Shell server: hyperdht server keyed by the device keyPair, one protomux
// ShellServer per connection, identity-based firewall. Dual-runtime.
import DHT from 'hyperdht'
import Protomux from 'protomux'
import HypercoreId from 'hypercore-id-encoding'
import fs from '#node:fs'
import { ShellServer } from './shell.js'

// Read allowed pubkeys from a firewall file (one z-base32/hex key per line,
// '#' comments). Missing file => empty allow-list (deny all). bare-fs safe.
export function readAllowed (firewallPath) {
  if (!firewallPath) return []
  let txt
  try {
    txt = fs.readFileSync(firewallPath, 'utf8')
  } catch (e) {
    if (e.code === 'ENOENT') return []
    throw e
  }
  return txt
    .split('\n')
    .map((l) => l.trim())
    .filter((l) => l && !l.startsWith('#'))
    .map((k) => HypercoreId.decode(k))
}

export async function serve ({ keyPair, firewall = null, disableFirewall = false, onConnection = null } = {}) {
  const allowed = disableFirewall ? true : readAllowed(firewall)

  const node = new DHT()
  const server = node.createServer({ firewall: onFirewall })

  server.on('connection', (socket) => {
    socket.on('error', () => {}) // ignore transport resets
    const mux = new Protomux(socket)
    mux.pair({ protocol: 'hypershell' }, () => {
      const shell = new ShellServer({ mux })
      if (shell.channel) shell.open()
    })
  })

  await server.listen(keyPair)

  return {
    node,
    server,
    publicKey: keyPair.publicKey,
    async close () {
      await server.close()
      await node.destroy()
    }
  }

  function onFirewall (remotePublicKey) {
    const allow = allowed === true || allowed.some((k) => remotePublicKey.equals(k))
    if (onConnection) onConnection(remotePublicKey, allow)
    return !allow // protomux firewall: return true = DENY
  }
}