Package

@epheme/core

Common middleware and utilities used across the Epheme stack: logging, device auth, plugin hosting, and license middleware.

Overview

  • Design focuses on privacy (redaction of sensitive fields) and clear, testable primitives.
  • Exports small, well-documented helper modules that are composed by hosts.

deviceRegistry.js — Device JWT + middleware

Issues short-lived HS256 device JWTs, verifies tokens, and provides Express middleware factories `requireDevice()` and `requireAdmin()`.

'use strict';
/**
 * @epheme/core/deviceRegistry
 *
 * Portable device-authentication primitives for any BafGo-compatible server.
 * Wraps the JWT issuance/verification logic from hub/backend/index.js and
 * exposes Express-compatible middleware factories.
 */

const crypto = require('crypto');
const jwt    = require('jsonwebtoken');

// ── Rate-limit state for failed admin secret checks ──────────────────────────
const _adminFailMap = new Map(); // ip → { count, resetAt }
const ADMIN_WINDOW_MS    = 60_000; // 1 minute sliding window
const ADMIN_MAX_FAILURES = 10;

function _adminCheckRate(ip) {
  const now = Date.now();
  const w   = _adminFailMap.get(ip);
  if (!w || now > w.resetAt) return true;
  return w.count < ADMIN_MAX_FAILURES;
}

function _adminRecordFail(ip) {
  const now = Date.now();
  const w   = _adminFailMap.get(ip);
  if (!w || now > w.resetAt) {
    _adminFailMap.set(ip, { count: 1, resetAt: now + ADMIN_WINDOW_MS });
  } else {
    w.count++;
  }
}

// ─────────────────────────────────────────────────────────────────────────────

function createDeviceRegistry({ deviceJwtSecret, deviceJwtTtl = 3600 } = {}) {

  function issueDeviceJWT(device) {
    if (!deviceJwtSecret) throw new Error('DEVICE_JWT_SECRET not configured');
    const payload = {
      device_id: device.id,
      tenant:    device.tenant,
      role:      device.role,
      type:      'device_access',
    };
    if (device.cert_fingerprint) payload.certFingerprint = device.cert_fingerprint;
    return jwt.sign(payload, deviceJwtSecret, { expiresIn: deviceJwtTtl });
  }

  function verifyDeviceJWT(token) {
    if (!deviceJwtSecret || !token) return null;
    try {
      const payload = jwt.verify(token, deviceJwtSecret);
      if (payload.type !== 'device_access') return null;
      return payload;
    } catch {
      return null;
    }
  }

  function requireDevice({ requiredRole } = {}) {
    return function deviceAuthMiddleware(req, res, next) {
      const authHeader = req.get('Authorization') || '';
      const bearer     = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null;
      const payload    = verifyDeviceJWT(bearer);
      if (!payload) {
        return res.status(401).json({ error: 'Device authentication required' });
      }
      if (requiredRole && payload.role !== requiredRole) {
        return res.status(403).json({ error: `Role '${requiredRole}' required` });
      }
      req.device = payload;
      next();
    };
  }

  function requireAdmin({ envKey = 'DEVICE_ADMIN_SECRET' } = {}) {
    return function adminAuthMiddleware(req, res, next) {
      const secret = process.env[envKey];
      if (!secret) {
        return res.status(503).json({ error: `Device admin not configured (${envKey} missing)` });
      }
      const ip = req.ip || req.socket?.remoteAddress || 'unknown';
      if (!_adminCheckRate(ip)) {
        return res.status(429).json({ error: 'Too many failed attempts. Try again later.' });
      }
      const provided = req.get('x-device-admin-secret') || '';
      const hmacKey  = crypto.createHash('sha256').update('admin-secret-comparison').digest();
      const a = crypto.createHmac('sha256', hmacKey).update(secret).digest();
      const b = crypto.createHmac('sha256', hmacKey).update(provided).digest();
      if (!crypto.timingSafeEqual(a, b)) {
        _adminRecordFail(ip);
        return res.status(403).json({ error: 'Invalid or missing X-Device-Admin-Secret' });
      }
      next();
    };
  }

  return { issueDeviceJWT, verifyDeviceJWT, requireDevice, requireAdmin };
}

module.exports = { createDeviceRegistry };

pluginHost.js — plugin loading and context

Discovers and mounts backend plugins, builds a PluginContext with KV, DB, license, metrics and hub event filtering.

/**
 * createPluginHost — BafGo backend plugin host.
 *
 * Discovers, validates, and mounts backend plugins onto an Express app.
 */

'use strict';

const express        = require('express');
const semver         = require('semver');
const { makeFeatureLicenseMiddleware } = require('./licenseMiddleware');
const { recordHit }  = require('./metrics');
const { createPluginDb } = require('./db/pluginDb');

const CORE_VERSION = require('./package.json').version;
const PLUGIN_ID_RE = /^[a-z0-9][a-z0-9-]{1,31}$/;

async function createPluginHost(app, options = {}) {
  const {
    redis         = null,
    licensePublicKeyPem = null,
    tenantLicenseClaims = null,
    eventBus      = null,
    db: dbOptions = null,
    plugins       = [],
  } = options;

  const loadedPlugins = [];

  for (const pluginExport of plugins) {
    const plugin = pluginExport?.default ?? pluginExport;
    if (!plugin || typeof plugin.register !== 'function') {
      throw new Error(
        `BafGo plugin host: a plugin entry does not implement EphemeBackendPlugin ` +
        `(missing register() function). Got: ${JSON.stringify(Object.keys(plugin || {}))}`
      );
    }

    const pluginId = String(plugin.id || '');
    if (!PLUGIN_ID_RE.test(pluginId)) {
      throw new Error(`BafGo plugin host: invalid pluginId "${pluginId}".`);
    }

    const manifest = plugin.manifest ?? null;
    if (manifest?.requiredCoreVersion) {
      if (!semver.satisfies(CORE_VERSION, manifest.requiredCoreVersion)) {
        throw new Error(
          `BafGo plugin host: plugin "${pluginId}" requires @epheme/core@` +
          `${manifest.requiredCoreVersion} but installed version is ${CORE_VERSION}`
        );
      }
    }

    const ctx = buildPluginContext({ pluginId, redis, licensePublicKeyPem, tenantLicenseClaims, eventBus, dbOptions, declaredHubEvents: manifest?.hubEvents ?? [] });

    await plugin.register(ctx);

    app.use(`/plugins/${pluginId}`, ctx.router);

    loadedPlugins.push({ pluginId, plugin, ctx });
    ctx.logger.info(`loaded — routes mounted at /plugins/${pluginId}`);
  }

  return {
    async shutdown() {
      for (const { pluginId, plugin, ctx } of loadedPlugins) {
        try {
          await plugin.onShutdown?.();
          await ctx._db?.close?.();
        } catch (err) {
          ctx.logger.error({ err }, 'error during shutdown');
        }
      }
    },
  };
}

module.exports = { createPluginHost };

logger.js — Privacy-safe structured logging

Creates Pino logger instances with a redaction list and request middleware that never logs identities or request bodies.

'use strict';

const pino     = require('pino');
const pinoHttp = require('pino-http');
const { randomUUID } = require('node:crypto');

const REDACT_PATHS = [
  'deviceId', 'deviceJwt', 'userId', 'jwt', 'token', 'pat', 'patId', 'sessionId',
  'roomId', 'shareId', 'inviteToken', 'ip', 'remoteAddress', 'req.headers', 'req.remoteAddress', 'req.url', 'req.id', 'req.params', 'req.query', 'res.headers', 'authorization', 'cookie', 'password', 'secret', 'apiKey', 'body',
];

function serializeErr(err) {
  if (!err || typeof err !== 'object') return err;
  const out = {};
  if (err.name    || err.constructor?.name) out.type       = err.name ?? err.constructor.name;
  if (err.message !== undefined)            out.message    = String(err.message);
  if (err.stack   !== undefined)            out.stack      = String(err.stack);
  if (err.code    !== undefined)            out.code       = err.code;
  if (err.statusCode !== undefined)         out.statusCode = err.statusCode;
  return out;
}

function safeRoute(req) {
  if (req.route && req.route.path) return req.route.path;

  return (req.url || '/')
    .split('?')[0]
    .replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '/:id')
    .replace(/\/[0-9a-f]{16,}/gi, '/:id')
    .replace(/\/\d{4,}/g, '/:id');
}

function createLogger({ service, component, tenant, destination } = {}) {
  const env   = process.env.NODE_ENV  || 'development';
  const level = process.env.LOG_LEVEL || (env === 'production' ? 'info' : 'debug');

  const usePretty = !destination
    && process.env.LOG_PRETTY !== 'false'
    && (process.env.LOG_PRETTY === 'true' || (process.stdout.isTTY && env !== 'test'));

  const base = { environment: env };
  const svc = service || process.env.LOG_SERVICE;
  if (svc)       base.service   = svc;
  if (component) base.component = component;
  const ten = tenant || process.env.TENANT_SLUG;
  if (ten)       base.tenant    = ten;
  const ns  = process.env.K8S_NAMESPACE;
  if (ns)        base.namespace = ns;

  const opts = {
    level,
    redact: { paths: REDACT_PATHS, remove: true },
    serializers: { err: serializeErr },
    base,
  };

  if (usePretty) {
    return pino(opts, pino.transport({ target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard', ignore: 'pid,hostname' } }));
  }

  return destination ? pino(opts, destination) : pino(opts);
}

module.exports = { createLogger, requestLogger: pinoHttp, REDACT_PATHS, _safeRoute: safeRoute };

licenseMiddleware.js — License verification middleware

Builds Express middleware that verifies RS256 license JWTs and checks required features.

const jwt = require('jsonwebtoken');

function extractBearerToken(authorizationHeader) {
  const header = String(authorizationHeader || '').trim();
  if (!header.startsWith('Bearer ')) return null;
  const token = header.slice(7).trim();
  return token || null;
}

function formatMissingFeatureError(requiredFeatures) {
  if (requiredFeatures.length === 1) return `${requiredFeatures[0]} feature required`;
  return `Missing required license features: ${requiredFeatures.join(', ')}`;
}

function makeLicensePublicKeyHandler(options = {}) {
  const {
    getPublicKeyPem,
    missingMessage = 'License public key not configured',
  } = options;

  if (typeof getPublicKeyPem !== 'function') {
    throw new Error('makeLicensePublicKeyHandler requires getPublicKeyPem function');
  }

  return function licensePublicKeyHandler(_req, res) {
    const pem = getPublicKeyPem();
    if (!pem) {
      return res.status(503).json({ error: missingMessage });
    }
    return res.type('text/plain').send(pem);
  };
}

/**
 * Build an Express middleware that verifies an RS256 license JWT and checks
 * required license fields/features.
 */
function makeFeatureLicenseMiddleware(options = {}) {
  const {
    getPublicKeyPem,
    requiredLicense = 'premium',
    requiredFeatures = [],
    attachProperty = 'licensePayload',
    precheck,
    validatePayload,
    logPrefix = 'license',
  } = options;

  if (typeof getPublicKeyPem !== 'function') {
    throw new Error('makeFeatureLicenseMiddleware requires getPublicKeyPem function');
  }

  return async function featureLicenseMiddleware(req, res, next) {
    const precheckError = typeof precheck === 'function' ? precheck(req) : null;
    if (precheckError) {
      return res.status(503).json({ error: precheckError });
    }

    const publicKeyPem = getPublicKeyPem(req);
    if (!publicKeyPem) {
      return res.status(503).json({ error: 'License verification not configured' });
    }

    const token = extractBearerToken(req.headers?.authorization);
    if (!token) {
      return res.status(401).json({ error: 'Missing Authorization header' });
    }

    try {
      const verified = jwt.verify(token, publicKeyPem, { algorithms: ['RS256'] });
      const payload = (verified && typeof verified === 'object') ? verified : null;
      if (!payload) {
        return res.status(401).json({ error: 'Invalid or expired license' });
      }

      if (requiredLicense && payload.lic !== requiredLicense) {
        return res.status(403).json({ error: 'Premium license required' });
      }

      if (requiredFeatures.length > 0) {
        const features = Array.isArray(payload.features) ? payload.features : [];
        const missing = requiredFeatures.filter(f => !features.includes(f));
        if (missing.length > 0) {
          return res.status(403).json({ error: formatMissingFeatureError(requiredFeatures) });
        }
      }

      if (typeof validatePayload === 'function') {
        const validationError = validatePayload(req, payload);
        if (validationError) {
          const status = Number(validationError.status) || 403;
          const error = validationError.error || 'License validation failed';
          return res.status(status).json({ error });
        }
      }

      req[attachProperty] = payload;
      return next();
    } catch (err) {
      console.warn(`[${logPrefix}] License verification failed:`, err && err.message ? err.message : err);
      return res.status(401).json({ error: 'Invalid or expired license' });
    }
  };
}

module.exports = {
  extractBearerToken,
  makeFeatureLicenseMiddleware,
  makeLicensePublicKeyHandler,
};

db/pluginDb.js — Plugin database adapters

SQLite & Postgres adapters implementing the host's PluginDb interface (migrations, queries, transactions).

/**
 * EphemeDb — internal interface implemented by both the SQLite and Postgres adapters.
 * This mirrors PluginDb from @epheme/plugin-sdk exactly, plus the internal
 * lifecycle methods the plugin host needs.
 */

'use strict';

function createSqliteDb({ pluginId, file }) {
  const Database = require('better-sqlite3');
  const db = new Database(file);
  db.pragma('journal_mode = WAL');
  db.pragma('foreign_keys = ON');

  db.exec(`
    CREATE TABLE IF NOT EXISTS _epheme_migrations (
      idx     INTEGER PRIMARY KEY,
      applied INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
    )
  `);

  async function query(sql, params = []) {
    const stmt = db.prepare(sql);
    return stmt.all(...params);
  }

  async function run(sql, params = []) {
    const stmt = db.prepare(sql);
    stmt.run(...params);
  }

  async function transaction(fn) {
    db.exec('BEGIN');
    try {
      const result = await fn(instance);
      db.exec('COMMIT');
      return result;
    } catch (err) {
      db.exec('ROLLBACK');
      throw err;
    }
  }

  async function migrate(steps) {
    const applied = new Set(
      db.prepare('SELECT idx FROM _epheme_migrations').all().map(r => r.idx)
    );
    for (let i = 0; i < steps.length; i++) {
      if (applied.has(i)) continue;
      db.exec(steps[i]);
      db.prepare('INSERT INTO _epheme_migrations (idx) VALUES (?)').run(i);
    }
  }

  function close() { db.close(); }
  const instance = { query, run, transaction, migrate, close };
  return instance;
}

function createPostgresDb({ pluginId, url }) {
  const { Pool } = require('pg');
  const pool = new Pool({ connectionString: url });

  let migrationTableReady = false;
  async function ensureMigrationTable(client) {
    if (migrationTableReady) return;
    await client.query(`
      CREATE TABLE IF NOT EXISTS _epheme_migrations (
        plugin_id TEXT NOT NULL,
        idx       INTEGER NOT NULL,
        applied   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
        PRIMARY KEY (plugin_id, idx)
      )
    `);
    migrationTableReady = true;
  }

  function translatePlaceholders(sql) {
    let i = 0;
    return sql.replace(/\?/g, () => `$${++i}`);
  }

  async function query(sql, params = []) { const { rows } = await pool.query(translatePlaceholders(sql), params); return rows; }
  async function run(sql, params = []) { await pool.query(translatePlaceholders(sql), params); }

  async function transaction(fn) {
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      const txDb = {
        query:       (s, p = []) => client.query(translatePlaceholders(s), p).then(r => r.rows),
        run:         (s, p = []) => client.query(translatePlaceholders(s), p).then(() => undefined),
        transaction: (innerFn)   => innerFn(txDb),
        migrate:     ()          => Promise.reject(new Error('migrate() cannot be called inside a transaction')),
        close:       ()          => {},
      };
      const result = await fn(txDb);
      await client.query('COMMIT');
      return result;
    } catch (err) {
      await client.query('ROLLBACK');
      throw err;
    } finally { client.release(); }
  }

  async function migrate(steps) {
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      await ensureMigrationTable(client);
      const { rows: applied } = await client.query('SELECT idx FROM _epheme_migrations WHERE plugin_id = $1', [pluginId]);
      const appliedSet = new Set(applied.map(r => r.idx));
      for (let i = 0; i < steps.length; i++) {
        if (appliedSet.has(i)) continue;
        await client.query(translatePlaceholders(steps[i]));
        await client.query('INSERT INTO _epheme_migrations (plugin_id, idx) VALUES ($1, $2)', [pluginId, i]);
      }
      await client.query('COMMIT');
    } catch (err) { await client.query('ROLLBACK'); throw err; } finally { client.release(); }
  }

  async function close() { await pool.end(); }
  return { query, run, transaction, migrate, close };
}

function createPluginDb({ dialect, file, url, pluginId }) {
  if (dialect === 'sqlite') {
    if (!file) throw new Error(`createPluginDb: 'file' is required for sqlite dialect (plugin: ${pluginId})`);
    return createSqliteDb({ pluginId, file });
  }
  if (dialect === 'postgres') {
    if (!url) throw new Error(`createPluginDb: 'url' is required for postgres dialect (plugin: ${pluginId})`);
    return createPostgresDb({ pluginId, url });
  }
  throw new Error(`createPluginDb: unknown dialect '${dialect}' (plugin: ${pluginId})`);
}

module.exports = { createPluginDb };

index.js — Package exports

Top-level exports for `@epheme/core` that re-export the helper modules.

// Main exports for @epheme/core
module.exports = {
  createDeviceRegistry: require('./deviceRegistry').createDeviceRegistry,
  createPluginHost: require('./pluginHost').createPluginHost,
  createLogger: require('./logger').createLogger,
  requestLogger: require('./logger').requestLogger,
  makeFeatureLicenseMiddleware: require('./licenseMiddleware').makeFeatureLicenseMiddleware,
  makeLicensePublicKeyHandler: require('./licenseMiddleware').makeLicensePublicKeyHandler,
  createPluginDb: require('./db/pluginDb').createPluginDb,
};

browser/quickConnectClient.ts — Quick Connect browser client

Small fetch-based client helpers for initiating and joining Quick Connect sessions.

export interface QuickConnectInitiateResponse {
  code: string;
  ttl: number;
}

export async function initiateQuickConnect(baseUrl: string): Promise {
  const url = new URL('/qc/initiate', baseUrl).toString();
  const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' } });
  if (!res.ok) throw new Error(`initiateQuickConnect failed: ${res.status}`);
  return res.json();
}

export async function joinQuickConnect(baseUrl: string, code: string): Promise<{ id: string }> {
  const url = new URL('/qc/join', baseUrl).toString();
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code }),
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({ error: res.statusText }));
    throw new Error(`joinQuickConnect failed: ${JSON.stringify(err)}`);
  }
  return res.json();
}

browser/deviceSession.ts — Device session helper

Client helper to manage a device JWT in browser storage and perform authenticated fetches.

export class DeviceSession {
  private storageKey = 'epheme:deviceToken';
  constructor(private storage: Storage = localStorage) {}

  setToken(token: string) {
    this.storage.setItem(this.storageKey, token);
  }

  getToken(): string | null {
    return this.storage.getItem(this.storageKey);
  }

  clear() {
    this.storage.removeItem(this.storageKey);
  }

  isAuthenticated(): boolean {
    return !!this.getToken();
  }

  async fetchWithAuth(input: RequestInfo, init?: RequestInit) {
    const token = this.getToken();
    const headers = new Headers(init && init.headers ? (init.headers as HeadersInit) : undefined);
    if (token) headers.set('Authorization', `Bearer ${token}`);
    const res = await fetch(input, { ...init, headers });
    return res;
  }
}