agilebeat/proxy-plus-koa-based

By agilebeat

Updated 5 months ago

Koa-based proxy server with dynamic routing, WebSocket and MCP support allowing quick integration.

Image
Security
Web servers
Monitoring & observability
0

3.0K

agilebeat/proxy-plus-koa-based repository overview

Proxy Plus (Koa-based)

This project is a proxy server built using Koa. It is designed to forward, filter, and log HTTP and WebSocket requests between clients and backend services, providing a flexible middleware architecture for custom logic and integrations.

WebSocket support is included, which is especially useful when proxying the Neo4j server or other services that require real-time, bidirectional communication.


Features

  • HTTP and WebSocket proxying with Koa
  • Middleware support for logging, user context, and policy enforcement
  • Easily extendable for custom connectors and routes
  • Drop-in extensibility: integrate external user stores by adding custom JS connector plugins in connectors/plugins (for example dist/connectors/plugins), and add custom authorization logic by dropping policies in pep/policies (for example dist/pep/policies).
  • Dynamic routing and static file serving
  • Conditional redirects based on request headers
  • MCP streamable HTTP support with graceful upstream error handling
  • Policy-aware services page button rendering

Usage

  1. Install dependencies:

    npm install
    
  2. Start the proxy server:

    npm start
    
  3. Configure your client or application to send requests through this proxy.


Project Structure

  • src/app.ts - Main application entry point
  • src/middleware/ - Koa middleware (context, logger, policy, user)
  • src/routes/ - Route handlers
  • src/utils/ - Utility functions
  • src/connectors/ - Example connectors
  • src/config/defaultEnv.ts - Default dynamic route configuration

Environment Variables

  • DYNAMIC_ROUTES: JSON string for dynamic route configuration (DynamicRoute[]).

    If DYNAMIC_ROUTES is not set, the app uses DEFAULT_DYNAMIC_ROUTES from src/config/defaultEnv.ts.

    Parsing and interpolation behavior:

    • Parsed once at startup.
    • Invalid JSON falls back to an empty route list.
    • Any string value can include placeholders like {ENV_NAME}.
    • Placeholders are resolved recursively across all nested route fields.
    • Special placeholders:
      • {DEFAULT_DYNAMIC_ROUTES_INVENTORY_PREFIX} -> value of DYNAMIC_ROUTES_INVENTORY_PREFIX
      • {MCP_NEO4J_AUTH_HEADER} -> value of MCP_NEO4J_AUTH_HEADER
    • Any other placeholder uses process.env[PLACEHOLDER].
    • Unknown placeholders are left unchanged.

    Route registration precedence (per route object):

    1. If redirect exists, register a redirect route.
    2. Else if splashPage is true, register the services inventory page route.
    3. Else if relativeFilePath exists, register a static file route.
    4. Else if target exists, register a proxied route.
    5. Otherwise the route is ignored.

    Notes:

    • WebSocket upgrade requests are handled by src/routes/proxy-ws.ts; HTTP redirect/proxy handlers skip them.
    • The splashPage route is served at DYNAMIC_ROUTES_INVENTORY_PREFIX.

    Supported DynamicRoute fields:

    FieldTypeBehavior
    namestringDisplay/logging name; also used for services page button text.
    routestringKoa route path/pattern (for example /analytics/(.*)).
    targetstringUpstream target URL for proxied HTTP routes.
    protocol'http' | 'mcp-streamable-http'Explicit protocol marker for proxied routes; use 'mcp-streamable-http' for MCP endpoints.
    rewritebasebooleanFor HTML responses, injects <base href="..."> and patches CSP base-uri.
    redirectstring | { default, conditionalRedirects[] }Redirects request instead of proxying; can be header-conditional.
    conditionalReturnsArray<{ condition, headerName, includes, return }>Returns a local JSON payload when header condition matches.
    subpathReturnsArray<{ path, return }>Returns a local JSON payload when ctx.path starts with path.
    requestHeaderRulesRequestHeaderRule[]Rewrites outbound proxy request headers before forwarding.
    splashPagebooleanRegisters the services inventory HTML page (DYNAMIC_ROUTES_INVENTORY_PREFIX).
    relativeFilePathstringServes local file content for the route.
    paramsstringQuery/path suffix appended to the services page button link.
    policyNamestringPolicy metadata used by policy mapping/execution.
    connectorNamestringConnector plugin metadata used by connector mapping.
    iconstringInline SVG/HTML snippet rendered in the services page button.
    doNotRenderButtonbooleanExcludes route from services inventory button rendering.
    hideIfNoAccessbooleanIf unauthorized, hides button instead of rendering a disabled one.
    websocket.handler'neo4j-bolt' | 'attu'Enables WebSocket handling for matching route path.
    websocket.targetstringUpstream WebSocket target URL.
    websocket.authHeaderstringOptional auth header used by attu WebSocket handler.
    websocket.preserveQueryStringbooleanWhen true, forwards query string in attu WebSocket handler.

    Return-key based responses (redirect.conditionalRedirects[].return, conditionalReturns[].return, subpathReturns[].return) currently support:

    • NEO4J_BROWSER_MANIFEST

    requestHeaderRules details:

    • operation: "create" -> sets header only if missing.
    • operation: "update" -> always sets/replaces header.
    • operation: "patch" -> regex replace on existing header value.
    • operation: "delete" -> removes header.
    • Optional when supports header conditions using includes, equals, matches (+ regex flags), or exists.

    Proxied HTTP runtime behavior:

    • Forwards request method, path, and query string to target after stripping the route prefix.
    • Rewrites outbound request headers via requestHeaderRules and updates host to the target host.
    • Rewrites absolute upstream redirect Location headers (3xx) back under the proxied route prefix.
    • For rewritebase: true + HTML responses, removes existing <base>, injects a new <base href="...">, patches CSP base-uri, and omits stale content-length.
    • Streams text/event-stream responses directly and closes streams gracefully on upstream/downstream failures.
    • For protocol: "mcp-streamable-http" failures, returns gentle 503 errors (jsonrpc payload for POST, plain text for GET).
  • IGNORE_URLS_FOR_LOGGING_BY_PREFIX:
    Comma-separated list or JSON array of URL path prefixes to ignore in request logging. Useful for suppressing logs for health checks, static assets, or other non-essential endpoints.
    Default: ['/_app', '/health', '/metrics', '/favicon.ico', '/robots.txt', '/static', '/public']

  • DYNAMIC_ROUTES_INVENTORY_PREFIX:
    Defines the services inventory route path used by splash pages and templates.
    Default: /services

  • MCP_NEO4J_AUTH_HEADER:
    Default auth header value that can be injected via {MCP_NEO4J_AUTH_HEADER} placeholder in DYNAMIC_ROUTES.

  • UPSTREAM_ERROR_MSG:
    HTML shown for non-MCP upstream proxy failures.

  • ACCESS_DENY_ERROR_MSG:
    HTML shown by the policy middleware when access is denied.

  • DYNAMIC_ROUTES_SERVICES_HTML:
    HTML template for the services inventory page, where buttons are injected at <!--SERVICES_BUTTONS-->.


Examples Using DEFAULT_DYNAMIC_ROUTES

DEFAULT_DYNAMIC_ROUTES in src/config/defaultEnv.ts is the built-in route set when DYNAMIC_ROUTES is not provided.
The examples below are copied from that default and can be reused as a starting point for custom DYNAMIC_ROUTES values.

Example 1: Root redirect + conditional return + websocket
{
  "name": "root",
  "route": "/",
  "policyName": "mock-always-allow",
  "connectorName": "simple",
  "websocket": {
    "handler": "neo4j-bolt",
    "target": "ws://10.29.1.86:7687"
  },
  "redirect": {
    "default": "/services",
    "conditionalRedirects": [
      {
        "condition": "header",
        "headerName": "accept",
        "includes": "application/json",
        "return": "NEO4J_BROWSER_MANIFEST"
      }
    ]
  }
}
Example 2: Static file route from defaults
{
  "name": "static-files-browser-config-json",
  "route": "/browser/:neo4j.browser.config.json",
  "policyName": "mock-always-allow",
  "connectorName": "simple",
  "relativeFilePath": "src/config/neo4j.browser.config.json"
}
Example 3: Proxied HTML app with base rewriting
{
  "name": "Data Browser",
  "route": "/analytics/(.*)",
  "target": "http://10.29.1.86:3001",
  "rewritebase": true,
  "policyName": "mock-always-allow",
  "connectorName": "simple"
}
Example 4: MCP route with header injection placeholder
{
  "name": "Link Analytics AI",
  "route": "/mcp",
  "target": "http://10.29.1.86:7475/mcp",
  "protocol": "mcp-streamable-http",
  "requestHeaderRules": [
    {
      "operation": "create",
      "headerName": "Authorization",
      "value": "{MCP_NEO4J_AUTH_HEADER}"
    }
  ],
  "policyName": "mock-always-allow",
  "connectorName": "mock",
  "doNotRenderButton": true
}

TODO

  • Current backlog is tracked in Todo.md.

Security Scanning (Trivy)

This repo includes a GitHub Actions workflow that runs Trivy on every PR and on pushes to main, and uploads results to GitHub Code Scanning:

  • Workflow: .github/workflows/trivy.yml
  • Output: trivy.sarif uploaded to the repo Security tab

License

MIT


In order to work with neo4j authentication for read-only databases use following URL structure:

Tag summary

Content type

Image

Digest

sha256:09136c238

Size

97.5 MB

Last updated

5 months ago

docker pull agilebeat/proxy-plus-koa-based