@interopio/gateway-server 0.19.4 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md CHANGED
@@ -10,9 +10,13 @@ The `@interopio/gateway-server` package is the web server used to run the gatewa
10
10
  The server is general purpose http server with support for:
11
11
  - HTTP and HTTPS
12
12
  - CORS configuration
13
- - Basic and OAuth2 authentication
13
+ - Basic, mTLS, and OAuth2 authentication
14
14
  - Custom API routes (both for HTTP and WebSocket)
15
15
 
16
+ ```shell
17
+ npm install @interopio/gateway-server
18
+ ```
19
+
16
20
  ## Table of Contents
17
21
 
18
22
  - [Getting Started](#getting-started)
@@ -22,16 +26,16 @@ The server is general purpose http server with support for:
22
26
  ## Getting Started
23
27
 
24
28
  ```typescript
25
- import GatewayServer, {type Server} from '@interopio/gateway-server';
29
+ import GatewayServer, { type Server } from '@interopio/gateway-server';
26
30
 
27
31
  const server: Server = await GatewayServer({
28
32
  port: 8385,
29
33
  gateway: {
30
34
  route: '/gw',
31
- access: 'authenticated',
35
+ authorize: { access: 'authenticated' },
32
36
  ping: {
33
37
  interval: 30000, // 30 seconds
34
- type: 'timestamp'
38
+ data: 'timestamp'
35
39
  }
36
40
  }
37
41
  });
@@ -44,28 +48,201 @@ await server.close();
44
48
 
45
49
  ## Configure HTTPS
46
50
 
51
+ SSL/TLS configuration supports PEM-formatted certificates. Two modes are available:
52
+
53
+ **1. Explicit certificates** - Provide your own key and certificate files (both must exist):
54
+ ```typescript
55
+ const server = await GatewayServer({
56
+ port: 8443,
57
+ ssl: {
58
+ key: "./ssl/gateway-server.key",
59
+ cert: "./ssl/gateway-server.crt",
60
+ passphrase: "secret" // optional, if key is encrypted
61
+ }
62
+ });
63
+ ```
64
+
65
+ > **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.
66
+
67
+ **2. Auto-generated server certificates** - Provide a CA key via `auth.x509.key` to auto-generate server certificates:
68
+
69
+ > **⚠️ Development only:** Auto-generated certificates are intended for development and testing. For production environments, use explicit certificates (mode 1) with proper certificate management.
70
+
71
+ > **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.
72
+
73
+ ```typescript
74
+ // Option A: Specify custom CA paths
75
+ await GatewayServer({
76
+ port: 8443,
77
+ host: "example.com", // optional: used in generated certificate's CN and SAN
78
+ ssl: {
79
+ ca: "./ssl/gateway-ca.crt", // CA certificate for client verification (optional)
80
+ key: "./ssl/gateway-server.key", // optional: save generated server key to file
81
+ cert: "./ssl/gateway-server.crt", // optional: save generated server cert to file
82
+ passphrase: undefined // optional, if key is encrypted
83
+ },
84
+ auth: {
85
+ type: 'x509',
86
+ x509: {
87
+ key: "./ssl/gateway-ca.key", // CA private key (auto-generates CA if missing)
88
+ passphrase: undefined, // optional, if CA key is encrypted
89
+ }
90
+ }
91
+ });
92
+
93
+ // Option B: Use default development CA (simplest for development)
94
+ await GatewayServer({
95
+ port: 8443,
96
+ ssl: {},
97
+ auth: {
98
+ type: 'x509',
99
+ x509: {
100
+ key: "./ssl/gateway-ca.key" // ca cert derives to ./ssl/gateway-ca.crt
101
+ }
102
+ }
103
+ });
104
+
105
+ // Option C: Minimal mode (uses gateway-ca.key and gateway-ca.crt)
106
+ await GatewayServer({
107
+ port: 8443,
108
+ ssl: {},
109
+ auth: {
110
+ type: 'x509',
111
+ x509: {
112
+ key: undefined // defaults to gateway-ca.key in current directory
113
+ }
114
+ }
115
+ });
116
+ ```
117
+
118
+ > **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.
119
+ >
120
+ > **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.
121
+ >
122
+ > **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`.
123
+ >
124
+ > **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.**
125
+
126
+ ### Generating Server Certificates with OpenSSL
127
+
128
+ To generate your own server certificates for mode 1 (explicit certificates) using OpenSSL:
129
+
130
+ > **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.
131
+
132
+ ```shell
133
+ # Set the domain name for the server certificate
134
+ DOMAIN=gateway.localhost
135
+
136
+ # Set paths to your CA certificate and key
137
+ CA_CERT=./ssl/gateway-ca.crt
138
+ CA_KEY=./ssl/gateway-ca.key
139
+
140
+ # Generate server private key (ECDSA secp256r1)
141
+ openssl ecparam -name prime256v1 -genkey -noout -out ./ssl/gateway-server.key
142
+
143
+ # Create a certificate signing request (CSR) with extensions
144
+ openssl req -new -key ./ssl/gateway-server.key -out ./ssl/gateway-server.csr \
145
+ -subj "/CN=${DOMAIN}" \
146
+ -addext "basicConstraints=CA:FALSE" \
147
+ -addext "keyUsage=critical,digitalSignature,keyEncipherment" \
148
+ -addext "extendedKeyUsage=serverAuth" \
149
+ -addext "subjectAltName=DNS:${DOMAIN},DNS:*.${DOMAIN}"
150
+
151
+ # Sign the CSR with your Root CA
152
+ openssl x509 -req -in ./ssl/gateway-server.csr \
153
+ -CA ${CA_CERT} -CAkey ${CA_KEY} \
154
+ -out ./ssl/gateway-server.crt -days 365 -sha256 \
155
+ -copy_extensions copyall
156
+ ```
157
+
158
+ > **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.
159
+
160
+ ### Mutual TLS (Client Certificate Authentication)
161
+
162
+ Enable client certificate verification for mutual TLS authentication:
163
+
164
+ ```typescript
165
+ const server = await GatewayServer({
166
+ port: 8443,
167
+ ssl: {
168
+ key: "./ssl/gateway-server.key", // Server private key
169
+ cert: "./ssl/gateway-server.crt", // Server certificate
170
+ ca: "./ssl/gateway-client-ca.crt", // CA that signed client certificates
171
+ requestCert: true, // Ask clients to send certificates
172
+ rejectUnauthorized: true // Reject clients without valid certs
173
+ }
174
+ });
175
+ ```
176
+
177
+ **Configuration options:**
178
+ - `requestCert: false` (default) - Server does not request client certificates
179
+ - `requestCert: true, rejectUnauthorized: false` - Client certs optional (allow anonymous)
180
+ - `requestCert: true, rejectUnauthorized: true` - Client certs required (enforce mutual TLS)
181
+
182
+ > **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.
183
+
184
+ #### Generating Client Certificates with OpenSSL
185
+
186
+ To generate client certificates for mutual TLS authentication using OpenSSL:
187
+
188
+ ```shell
189
+ # Set paths to your CA certificate and key
190
+ CA_CERT=./ssl/gateway-ca.crt
191
+ CA_KEY=./ssl/gateway-ca.key
192
+
193
+ # Generate client private key (ECDSA secp256r1)
194
+ openssl ecparam -name prime256v1 -genkey -noout -out ./ssl/gateway-client.key
195
+
196
+ # Create a certificate signing request (CSR) with extensions
197
+ openssl req -new -key ./ssl/gateway-client.key -out ./ssl/gateway-client.csr \
198
+ -subj "/CN=dev-user" \
199
+ -addext "basicConstraints=CA:FALSE" \
200
+ -addext "keyUsage=critical,digitalSignature,keyEncipherment" \
201
+ -addext "extendedKeyUsage=clientAuth" \
202
+ -addext "subjectAltName=email:test@example.com"
203
+
204
+ # Sign the CSR with your Root CA
205
+ openssl x509 -req -in ./ssl/gateway-client.csr \
206
+ -CA ${CA_CERT} -CAkey ${CA_KEY} \
207
+ -out ./ssl/gateway-client.crt -days 365 -sha256 \
208
+ -copy_extensions copyall
209
+
210
+ openssl pkcs12 -export -out ./ssl/gateway-client.p12 \
211
+ -inkey ./ssl/gateway-client.key -in ./ssl/gateway-client.crt \
212
+ -passout pass:changeit
213
+ ```
214
+
215
+ > **Important:** The `extendedKeyUsage=clientAuth` is critical for client certificates. Without it, the certificate may be rejected during mutual TLS authentication.
216
+
217
+ ### Full Example with Authentication
218
+
47
219
  ```typescript
48
220
  import GatewayServer, {type Server} from '@interopio/gateway-server';
49
221
 
50
222
  const server: Server = await GatewayServer({
51
223
  port: 8443,
224
+ // Enable HTTPS with Development CA
52
225
  ssl: {
53
- cert: "glue42.crt",
54
- ca: "intermediate.crt",
55
- key: "glue42.key"
226
+ ca: './ssl/gateway-ca.crt', // CA cert for client verification
227
+ rejectUnauthorized: false, // allow anonymous if no client cert
228
+ requestCert: true, // request client certificates for mutual TLS
56
229
  },
57
230
  auth: {
58
- type: 'oauth2', // or 'basic'
231
+ type: 'oauth2', // or 'basic' or 'x509'
59
232
  oauth2: {
60
233
  jwt: {
61
- issuer: 'https://auth.example.com',
62
- audience: 'https://api.example.com'
234
+ issuerUri: 'https://auth.example.com',
235
+ audience: 'https://api.example.com',
236
+ principalClaimName: 'sub', // claim to use as principal
63
237
  }
64
238
  },
239
+ x509: {
240
+ principalAltName: 'email', // extract principal from certificate email SAN (default: uses subject)
241
+ key: './ssl/gateway-ca.key' // CA key for generating certs
242
+ },
65
243
  basic: {
66
- username: 'interopio',
67
- password: 'passwordo'
68
- }
244
+ realm: 'My Gateway'
245
+ },
69
246
  },
70
247
  app: async ({handle}) => {
71
248
  handle(
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Argon2 version 1.0 (0x10)
3
+ */
4
+ export const ARGON2_VERSION_10: number;
5
+
6
+ /**
7
+ * Argon2 version 1.3 (0x13) - current version
8
+ */
9
+ export const ARGON2_VERSION_13: number;
10
+
11
+ /**
12
+ * Current Argon2 version used by default
13
+ */
14
+ export const ARGON2_VERSION: number;
15
+
16
+ /**
17
+ * Default salt length in bytes (16)
18
+ */
19
+ export const DEFAULT_SALT_LENGTH: number;
20
+
21
+ /**
22
+ * Default hash length in bytes (32)
23
+ */
24
+ export const DEFAULT_HASH_LENGTH: number;
25
+
26
+ /**
27
+ * Default parallelism parameter (4)
28
+ */
29
+ export const DEFAULT_PARALLELISM: number;
30
+
31
+ /**
32
+ * Default memory cost in KiB (65536 = 64 MB)
33
+ */
34
+ export const DEFAULT_MEMORY: number;
35
+
36
+ /**
37
+ * Default number of iterations (3)
38
+ */
39
+ export const DEFAULT_PASSES: number;
40
+
41
+ /**
42
+ * Argon2 algorithm variants
43
+ */
44
+ export type Argon2Algorithm = 'argon2d' | 'argon2i' | 'argon2id';
45
+
46
+ /**
47
+ * Argon2 hash parameters
48
+ */
49
+ export type Argon2HashParameters = {
50
+ memory: number;
51
+ passes: number;
52
+ parallelism: number;
53
+ nonce: Buffer;
54
+ };
55
+
56
+ /**
57
+ * Decoded Argon2 hash structure
58
+ */
59
+ export type Argon2Hash = {
60
+ algorithm: Argon2Algorithm;
61
+ version: number;
62
+ parameters: Argon2HashParameters;
63
+ hash: Buffer;
64
+ };
65
+
66
+ /**
67
+ * Hash a password using Argon2.
68
+ *
69
+ * @param password - The plain text password to hash
70
+ * @param options - Optional parameters. If not provided, defaults will be used.
71
+ * @returns The encoded Argon2 hash string
72
+ */
73
+ export function hash(
74
+ password: string,
75
+ options?: {
76
+ saltLength?: number;
77
+ hashLength?: number;
78
+ parallelism?: number;
79
+ memory?: number;
80
+ passes?: number;
81
+ }
82
+ ): Promise<string>;
83
+
84
+ /**
85
+ * Verify a password against an Argon2 hash.
86
+ *
87
+ * @param encodedHash - The encoded Argon2 hash to verify against
88
+ * @param password - The plain text password to verify
89
+ * @returns true if password matches the hash, false otherwise
90
+ */
91
+ export function verify(encodedHash: string, password: string): Promise<boolean>;
92
+
93
+ /**
94
+ * Create an Argon2 hash from parameters.
95
+ * Low-level wrapper around node:crypto's argon2Sync.
96
+ *
97
+ * @param algorithm - Argon2 algorithm variant ('argon2d' | 'argon2i' | 'argon2id')
98
+ * @param password - The password to hash
99
+ * @param hashLength - Length of the output hash in bytes
100
+ * @param parameters - Argon2 parameters (nonce, memory, passes, parallelism). If not provided, defaults will be used.
101
+ * @returns The hash buffer
102
+ */
103
+ export function createHash(
104
+ algorithm: Argon2Algorithm,
105
+ password: string,
106
+ hashLength: number,
107
+ parameters?: Argon2HashParameters
108
+ ): Promise<Buffer>;
109
+
110
+ /**
111
+ * Decode an encoded Argon2 hash string into its components.
112
+ * Format: $argon2id$v=19$m=65536,t=3,p=4$base64salt$base64hash
113
+ *
114
+ * @param encodedHash - The encoded Argon2 hash string
115
+ * @returns Decoded hash components
116
+ */
117
+ export function decode(encodedHash: string): Argon2Hash;
118
+
119
+ /**
120
+ * Encode Argon2 hash components into a standard string format.
121
+ * Format: $argon2id$v=19$m=65536,t=3,p=4$base64salt$base64hash
122
+ *
123
+ * @param hashData - The hash components to encode
124
+ * @returns Encoded hash string
125
+ */
126
+ export function encode(hashData: Argon2Hash): string;
127
+
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Create a random salt (nonce) for cryptographic operations.
3
+ *
4
+ * @param length - Length of the salt in bytes
5
+ * @returns Random salt buffer
6
+ */
7
+ export function createSalt(length: number): Buffer;
@@ -0,0 +1,47 @@
1
+ import type { RSAKey, KJUR } from 'jsrsasign';
2
+
3
+ /**
4
+ * CA private key type - can be a PEM string or a jsrsasign key object
5
+ */
6
+ type CAPrivateKey = string | RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA;
7
+
8
+ /**
9
+ * Default Common Name for the root CA certificate.
10
+ */
11
+ export const DEFAULT_CA_NAME: string;
12
+
13
+ /**
14
+ * Generate a root Certificate Authority (CA) certificate.
15
+ *
16
+ * @param options - Options for generating the CA
17
+ * @param options.name - Common Name for the CA certificate (default: "io.Gateway Dev CA user@host")
18
+ * @param options.passphrase - Optional passphrase to protect the private key
19
+ * @returns Object containing the private key and certificate in PEM format
20
+ */
21
+ export function generateRootCA(options?: { name?: string, passphrase?: string }): {
22
+ key: string;
23
+ cert: string;
24
+ };
25
+
26
+
27
+ /**
28
+ * Generate a certificate with custom SAN (Subject Alternative Name) entries.
29
+ *
30
+ * @param caKey - CA private key (PEM string or jsrsasign key object)
31
+ * @param issuer - Issuer DN string from CA certificate
32
+ * @param sanEntries - Array of SAN entries (e.g., "localhost", "IP:192.168.1.1", "EMAIL:user@example.com")
33
+ * @param isClient - Generate client certificate (clientAuth) vs server certificate (serverAuth) (default: false)
34
+ * @param validityDays - Certificate validity in days (default: 7)
35
+ * @returns Object containing the private key and certificate in PEM format
36
+ */
37
+ export function generateCert(
38
+ caKey: CAPrivateKey,
39
+ issuer: string,
40
+ sanEntries: string[],
41
+ isClient?: boolean,
42
+ validityDays?: number
43
+ ): {
44
+ key: string;
45
+ cert: string;
46
+ };
47
+
@@ -0,0 +1,6 @@
1
+
2
+ export * as argon2 from './crypto/argon2';
3
+ export * as keygen from './crypto/keygen';
4
+ export * as mkcert from './crypto/mkcert';
5
+
6
+
@@ -9,11 +9,12 @@ import type {
9
9
  ReadonlyHttpHeaders,
10
10
  ResponseCookie
11
11
  } from './http';
12
- import type {WebSocketHandler} from './socket';
13
- import type {Principal, AuthorizationRule} from '../auth';
14
- import {AsyncLocalStorage} from 'node:async_hooks';
15
- import type {AddressInfo} from 'node:net';
16
- import {IOGateway} from '@interopio/gateway';
12
+ import type { WebSocketHandler } from './socket';
13
+ import type { Principal, AuthorizationRule } from '../auth';
14
+ import type { AsyncLocalStorage } from 'node:async_hooks';
15
+ import type { X509Certificate } from 'node:crypto';
16
+ import type { AddressInfo } from 'node:net';
17
+ import { IOGateway } from '@interopio/gateway';
17
18
 
18
19
  export type OriginFilters = {
19
20
  non_matched?: IOGateway.Filtering.Action
@@ -91,6 +92,10 @@ export interface ServerWebExchangeBuilder<Request extends ServerHttpRequest = Se
91
92
  build(): ServerWebExchange<Request, Response>
92
93
  }
93
94
 
95
+ /**
96
+ * SSL/TLS connection information
97
+ */
98
+ export type SslInfo = { peerCertificate?: X509Certificate };
94
99
 
95
100
  export type ServerHttpRequest = HttpRequest<ReadonlyHttpHeaders> & HttpInputMessage<ReadonlyHttpHeaders> & {
96
101
  readonly id: string
@@ -108,6 +113,8 @@ export type ServerHttpRequest = HttpRequest<ReadonlyHttpHeaders> & HttpInputMess
108
113
 
109
114
  readonly upgrade: boolean
110
115
  readonly remoteAddress?: AddressInfo
116
+ /** SSL/TLS connection information (available for HTTPS requests) */
117
+ readonly sslInfo?: SslInfo
111
118
  }
112
119
 
113
120
  export interface ServerHttpResponse extends HttpResponse<MutableHttpHeaders>, HttpOutputMessage<MutableHttpHeaders> {