# io.Gateway Server


[![npm version](https://img.shields.io/npm/v/@interopio/gateway-server.svg)](https://www.npmjs.com/package/@interopio/gateway-server)

## Overview

The `@interopio/gateway-server` package is the web server used to run the gateway as a standalone server app accessible via WebSocket.

The server is general purpose http server with support for:
- HTTP and HTTPS
- CORS configuration
- Basic, mTLS, and OAuth2 authentication
- Custom API routes (both for HTTP and WebSocket)

```shell
npm install @interopio/gateway-server
```

## Table of Contents

- [Getting Started](#getting-started)
- [Configure HTTPS](#configure-https)
- [Management](#management)
- [@glue42/gateway-ent Compatibility](#glue42gateway-ent-compatibility)

## Getting Started

### CLI:
```shell
npx @interopio/gateway-server run --port 8385 --gateway
```
### API:
```typescript
import GatewayServer, { type Server } from '@interopio/gateway-server';

const server: Server = await GatewayServer({
    port: 8385,
    gateway: {
        route: '/gw',
        authorize: { access: 'authenticated' },
        ping: {
            interval: 30000,  // 30 seconds
            data: 'timestamp'
        }
    }
});

server.address; // Bound address info
server.gateway; // Gateway instance

await server.close();
```

## Configure HTTPS

SSL/TLS configuration supports PEM-formatted certificates. Two modes are available:

**1. Explicit certificates** - Provide your own key and certificate files (both must exist):
```typescript
const server = await GatewayServer({
    port: 8443,
    ssl: {
        key: "./ssl/gateway-server.key",
        cert: "./ssl/gateway-server.crt",
        passphrase: "secret"             // optional, if key is encrypted
    }
});
```

> **Recommended for production:** Generate your own server certificates using a trusted CA or create your own CA infrastructure. See [Generating Server Certificates with OpenSSL](#generating-server-certificates-with-openssl) below.

**2. Auto-generated server certificates** - Provide a CA key via `auth.x509.key` to auto-generate server certificates:

> **⚠️ Development only:** Auto-generated certificates are intended for development and testing. For production environments, use explicit certificates (mode 1) with proper certificate management.

> **Note:** The CA private key is stored under `auth.x509.key` since its primary purpose is generating client certificates for X.509 authentication. It is incidentally used to generate self-signed server certificates when no explicit server cert is provided and ssl is enabled.

```typescript
// Option A: Specify custom CA paths
await GatewayServer({
    port: 8443,
    host: "example.com",                    // optional: used in generated certificate's CN and SAN
    ssl: {
        ca: "./ssl/gateway-ca.crt",        // CA certificate for client verification (optional)
        key: "./ssl/gateway-server.key",    // optional: save generated server key to file
        cert: "./ssl/gateway-server.crt",  // optional: save generated server cert to file
        passphrase: undefined               // optional, if key is encrypted
    },
    auth: {
        type: 'x509',
        x509: {
            key: "./ssl/gateway-ca.key",    // CA private key (auto-generates CA if missing)
            passphrase: undefined,          // optional, if CA key is encrypted
        }
    }
});

// Option B: Use default development CA (simplest for development)
await GatewayServer({
    port: 8443,
    ssl: {},
    auth: {
        type: 'x509',
        x509: {
            key: "./ssl/gateway-ca.key" // ca cert derives to ./ssl/gateway-ca.crt
        }
    }
});

// Option C: Minimal mode (uses gateway-ca.key and gateway-ca.crt)
await GatewayServer({
    port: 8443,
    ssl: {},
    auth: {
        type: 'x509',
        x509: {
            key: undefined              // defaults to gateway-ca.key in current directory
        }
    }
});
```

> **Client Configuration:** For mode 2, distribute the Root CA certificate (`.crt` file) to clients. Clients must import this CA into their trust store. Server certificates are regenerated on each startup (in memory or saved to disk if key/cert paths specified) and are automatically trusted by clients who trust the Root CA.
>
> **Auto-generation:** If CA files don't exist (specified in `auth.x509.key`), they are automatically generated and saved to disk. The CA certificate path is derived from the key path by replacing the extension with `.crt` (if not explicitly specified via `ssl.ca`). The CA is valid for 10 years and uses ECDSA secp384r1. Server certificates are regenerated on startup with 7-day validity.
>
> **Hostname in certificates:** When auto-generating server certificates (mode 2), the `host` parameter (if specified) is used as the Common Name (CN) and in the Subject Alternative Name (SAN) of the certificate. If `host` is not specified, defaults to `localhost`.
>
> **Note:** When both `ssl.key` and `ssl.crt` files exist, mode 1 is used (explicit certificates). Otherwise mode 2 is used (auto-generated from CA key in `auth.x509.key`). **For production deployments, always use mode 1 with properly managed certificates.**

### Generating Server Certificates with OpenSSL

To generate your own server certificates for mode 1 (explicit certificates) using OpenSSL:

> **Production Best Practice:** Use this approach to create properly signed certificates for your production environment. Ensure certificates are renewed before expiration and follow your organization's certificate management policies.

```shell
# Set the domain name for the server certificate
DOMAIN=gateway.localhost

# Set paths to your CA certificate and key
CA_CERT=./ssl/gateway-ca.crt
CA_KEY=./ssl/gateway-ca.key

# Generate server private key (ECDSA secp256r1)
openssl ecparam -name prime256v1 -genkey -noout -out ./ssl/gateway-server.key

# Create a certificate signing request (CSR) with extensions
openssl req -new -key ./ssl/gateway-server.key -out ./ssl/gateway-server.csr \
  -subj "/CN=${DOMAIN}" \
  -addext "basicConstraints=CA:FALSE" \
  -addext "keyUsage=critical,digitalSignature,keyEncipherment" \
  -addext "extendedKeyUsage=serverAuth" \
  -addext "subjectAltName=DNS:${DOMAIN},DNS:*.${DOMAIN}"

# Sign the CSR with your Root CA
openssl x509 -req -in ./ssl/gateway-server.csr \
  -CA ${CA_CERT} -CAkey ${CA_KEY} \
  -out ./ssl/gateway-server.crt -days 365 -sha256 \
  -copy_extensions copyall
```

> **Important:** The `subjectAltName` with DNS entries is **required** for server certificates. Modern browsers and clients reject certificates without SAN, even if the Common Name (CN) matches the hostname.

### Mutual TLS (Client Certificate Authentication)

Enable client certificate verification for mutual TLS authentication:

```typescript
const server = await GatewayServer({
    port: 8443,
    ssl: {
        key: "./ssl/gateway-server.key",    // Server private key
        cert: "./ssl/gateway-server.crt",  // Server certificate
        ca: "./ssl/gateway-client-ca.crt",  // CA that signed client certificates
        requestCert: true,          // Ask clients to send certificates
        rejectUnauthorized: true    // Reject clients without valid certs
    }
});
```

**Configuration options:**
- `requestCert: false` (default) - Server does not request client certificates
- `requestCert: true, rejectUnauthorized: false` - Client certs optional (allow anonymous)
- `requestCert: true, rejectUnauthorized: true` - Client certs required (enforce mutual TLS)

> **Note:** When `requestCert` is `false`, clients will not send certificates even if they have them. The server must explicitly request them during the TLS handshake.

#### Generating Client Certificates with OpenSSL

To generate client certificates for mutual TLS authentication using OpenSSL:

```shell
# Set paths to your CA certificate and key
CA_CERT=./ssl/gateway-ca.crt
CA_KEY=./ssl/gateway-ca.key

# Generate client private key (ECDSA secp256r1)
openssl ecparam -name prime256v1 -genkey -noout -out ./ssl/gateway-client.key

# Create a certificate signing request (CSR) with extensions
openssl req -new -key ./ssl/gateway-client.key -out ./ssl/gateway-client.csr \
  -subj "/CN=dev-user" \
  -addext "basicConstraints=CA:FALSE" \
  -addext "keyUsage=critical,digitalSignature,keyEncipherment" \
  -addext "extendedKeyUsage=clientAuth" \
  -addext "subjectAltName=email:test@example.com"

# Sign the CSR with your Root CA
openssl x509 -req -in ./ssl/gateway-client.csr \
  -CA ${CA_CERT} -CAkey ${CA_KEY} \
  -out ./ssl/gateway-client.crt -days 365 -sha256 \
  -copy_extensions copyall

openssl pkcs12 -export -out ./ssl/gateway-client.p12 \
  -inkey ./ssl/gateway-client.key -in ./ssl/gateway-client.crt \
  -passout pass:changeit
```

> **Important:** The `extendedKeyUsage=clientAuth` is critical for client certificates. Without it, the certificate may be rejected during mutual TLS authentication.

### Full Example with Authentication

```typescript
import GatewayServer, {type Server} from '@interopio/gateway-server';

const server: Server = await GatewayServer({
    port: 8443,
    // Enable HTTPS with Development CA
    ssl: {
        ca: './ssl/gateway-ca.crt', // CA cert for client verification
        rejectUnauthorized: false, // allow anonymous if no client cert
        requestCert: true, // request client certificates for mutual TLS
    },
    auth: {
        type: 'oauth2', // or 'basic' or 'x509'
        oauth2: {
            jwt: {
                issuerUri: 'https://auth.example.com',
                audience: 'https://api.example.com',
                principalClaimName: 'sub', // claim to use as principal
            }
        },
        x509: {
            principalAltName: 'email', // extract principal from certificate email SAN (default: uses subject)
            key: './ssl/gateway-ca.key'  // CA key for generating certs
        },
        basic: {
            realm: 'My Gateway'
        },
    },
    app: async ({handle}) => {
        handle(
            {
                request: {method: 'GET', path: '/api/metrics'},
                options: {cors: true, authorize: {access: 'authenticated'}},
                handler: async ({response}) => {
                    response.setStatusCode({value: 200}); // OK
                    await response.end();
                }
            },
            {
                request: {method: 'POST', path: '/api/metrics'},
                options: {cors: true, authorize: {access: 'authenticated'}},
                handler: async ({request, response}) => {
                    response.statusCode({value: 202}); // Accepted
                    await response.end();
                    try {
                        const update = await request.json();
                        console.log(`${JSON.stringify(update)}`);
                    } catch (e) {
                        console.error('Error processing metrics:', e);
                    }
                }
            });
    },
});

await server.close();

```

## Management

The gateway server supports a management interface for remote administration via named pipes (Windows) or Unix sockets. This allows external tools to send commands like `info` (get server status) or `shutdown` (gracefully stop the server).

### CLI

Use the `manage` command to send management commands to a running gateway server:

```shell
# Get server info
npx @interopio/gateway-server manage --path \\.\pipe\glue42-gateway-xxx info

# Shutdown the server
npx @interopio/gateway-server manage --path \\.\pipe\glue42-gateway-xxx shutdown
```

### API

```typescript
import { manage } from '@interopio/gateway-server/tools';

// Send a command to a running gateway server
const result = await manage.sendCommand(
    { path: '\\\\.\\pipe\\glue42-gateway-xxx' },
    { command: 'info' }
);
```

### Server Configuration

```typescript
function getPath() {
    const prefix = `glue42-gateway`;
    const env = process.env['GLUE-ENV'] || 'DEMO';
    const region = process.env['GLUE-REGION'] || 'INTEROP.IO';
    return process.platform === 'win32'
        ? `\\\\.\\pipe\\glue42-gateway-${env}-${region}-${process.env.USERNAME}`
        : `${tmpdir()}/${prefix}-${env}-${region}-${process.env.USERNAME}.sock`;
}

const server: GatewayServer.Server = await GatewayServerFactory({
    management: {
        server: {
            path: getPath()
        },
        commands: {
            shutdown: { enabled: false },
        }
    }
});
```

## `@glue42/gateway-ent` Compatibility

This package aims to provide compatibility with `@glue42/gateway-ent` proprietary package used in Glue42 Desktop (now io.Connect Desktop)

```javascript
import * as gw from '@interopio/gateway-server/gateway-ent';
```

## Changelog

See [changelog](./changelog.md)

## License

[Interop.io Developer License Agreement](license.md)
