@ripple-ts/adapter-node 0.2.211 → 0.2.213

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @ripple-ts/adapter-node
2
2
 
3
+ ## 0.2.213
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies []:
8
+ - @ripple-ts/adapter@0.2.213
9
+
10
+ ## 0.2.212
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies []:
15
+ - @ripple-ts/adapter@0.2.212
16
+
3
17
  ## 0.2.211
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @ripple-ts/adapter-node
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/%40ripple-ts%2Fadapter-node?logo=npm)](https://www.npmjs.com/package/@ripple-ts/adapter-node)
4
+ [![npm downloads](https://img.shields.io/npm/dm/%40ripple-ts%2Fadapter-node?logo=npm&label=downloads)](https://www.npmjs.com/package/@ripple-ts/adapter-node)
5
+
3
6
  Node.js adapter for Ripple metaframework apps.
4
7
 
5
8
  It bridges Node's `IncomingMessage`/`ServerResponse` API to Web
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Node.js adapter for Ripple metaframework (Web Request/Response bridge)",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.211",
6
+ "version": "0.2.213",
7
7
  "type": "module",
8
8
  "module": "src/index.js",
9
9
  "main": "src/index.js",
@@ -15,7 +15,7 @@
15
15
  }
16
16
  },
17
17
  "dependencies": {
18
- "@ripple-ts/adapter": "0.2.211"
18
+ "@ripple-ts/adapter": "0.2.213"
19
19
  },
20
20
  "homepage": "https://ripplejs.com",
21
21
  "repository": {
package/src/index.js CHANGED
@@ -30,18 +30,29 @@ function normalize_forwarded_value(value) {
30
30
  /**
31
31
  * @param {import('node:http').IncomingMessage} node_request
32
32
  * @param {AbortSignal} signal
33
+ * @param {boolean} [trust_proxy=false]
33
34
  * @returns {Request}
34
35
  */
35
- function node_request_to_web_request(node_request, signal) {
36
- const forwarded_proto = normalize_forwarded_value(
37
- first_header_value(node_request.headers['x-forwarded-proto']),
38
- );
39
- const forwarded_host = normalize_forwarded_value(
40
- first_header_value(node_request.headers['x-forwarded-host']),
41
- );
42
-
43
- const proto = forwarded_proto ?? 'http';
44
- const host = forwarded_host ?? first_header_value(node_request.headers.host) ?? 'localhost';
36
+ function node_request_to_web_request(node_request, signal, trust_proxy = false) {
37
+ let proto = 'http';
38
+ let host = first_header_value(node_request.headers.host) ?? 'localhost';
39
+
40
+ if (trust_proxy) {
41
+ const forwarded_proto = normalize_forwarded_value(
42
+ first_header_value(node_request.headers['x-forwarded-proto']),
43
+ );
44
+ const forwarded_host = normalize_forwarded_value(
45
+ first_header_value(node_request.headers['x-forwarded-host']),
46
+ );
47
+
48
+ if (forwarded_proto) proto = forwarded_proto;
49
+ if (forwarded_host) host = forwarded_host;
50
+ } else {
51
+ // Derive protocol from the socket when not trusting proxy headers
52
+ if (/** @type {import('node:tls').TLSSocket} */ (node_request.socket).encrypted) {
53
+ proto = 'https';
54
+ }
55
+ }
45
56
 
46
57
  const raw_url = node_request.url ?? '/';
47
58
  const base = `${proto}://${host}`;
@@ -114,21 +125,6 @@ function web_response_to_node_response(web_response, node_response, request_meth
114
125
  node_stream.pipe(node_response);
115
126
  }
116
127
 
117
- /** @typedef {import('@ripple-ts/adapter').ServeStaticDirectoryOptions} StaticServeOptions */
118
-
119
- /**
120
- * @typedef {{
121
- * port?: number,
122
- * hostname?: string,
123
- * middleware?: ((
124
- * req: import('node:http').IncomingMessage,
125
- * res: import('node:http').ServerResponse,
126
- * next: (error?: any) => void
127
- * ) => void) | null,
128
- * static?: StaticServeOptions | false,
129
- * }} ServeOptions
130
- */
131
-
132
128
  /**
133
129
  * @param {(req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse, next: (error?: any) => void) => void} middleware
134
130
  * @param {import('node:http').IncomingMessage} node_request
@@ -160,9 +156,7 @@ function run_node_middleware(middleware, node_request, node_response) {
160
156
  }
161
157
 
162
158
  /**
163
- * @param {(request: Request, platform?: any) => Response | Promise<Response>} fetch_handler
164
- * @param {ServeOptions} [options]
165
- * @returns {{ listen: (port?: number) => import('node:http').Server, close: () => void }}
159
+ * @type {typeof import('@ripple-ts/adapter-node').serve}
166
160
  */
167
161
  export function serve(fetch_handler, options = {}) {
168
162
  const {
@@ -170,6 +164,7 @@ export function serve(fetch_handler, options = {}) {
170
164
  hostname = DEFAULT_HOSTNAME,
171
165
  middleware = null,
172
166
  static: static_options = {},
167
+ trustProxy: trust_proxy = false,
173
168
  } = options;
174
169
 
175
170
  /** @type {ReturnType<typeof serveStatic> | null} */
@@ -191,7 +186,11 @@ export function serve(fetch_handler, options = {}) {
191
186
 
192
187
  try {
193
188
  const run_fetch_handler = async () => {
194
- const request = node_request_to_web_request(node_request, abort_controller.signal);
189
+ const request = node_request_to_web_request(
190
+ node_request,
191
+ abort_controller.signal,
192
+ trust_proxy,
193
+ );
195
194
  return await fetch_handler(request, { node_request, node_response });
196
195
  };
197
196
 
@@ -130,6 +130,7 @@ describe('@ripple-ts/adapter-node serve()', () => {
130
130
  expect(response.headers.get('x-response')).toBe('ok');
131
131
  expect(await response.text()).toBe('created');
132
132
  },
133
+ { trustProxy: true },
133
134
  );
134
135
 
135
136
  expect(captured).toEqual({
@@ -140,6 +141,32 @@ describe('@ripple-ts/adapter-node serve()', () => {
140
141
  });
141
142
  });
142
143
 
144
+ it('ignores x-forwarded-* headers when trustProxy is not enabled', async () => {
145
+ /** @type {{ url: string } | null} */
146
+ let captured = null;
147
+
148
+ await with_server(
149
+ async (request) => {
150
+ captured = { url: request.url };
151
+ return new Response('ok');
152
+ },
153
+ async (base_url) => {
154
+ await fetch(`${base_url}/test`, {
155
+ headers: {
156
+ 'x-forwarded-proto': 'https',
157
+ 'x-forwarded-host': 'evil.com',
158
+ },
159
+ });
160
+ },
161
+ );
162
+
163
+ // Should NOT use forwarded headers; URL should use http and the real host
164
+ expect(captured).not.toBeNull();
165
+ const url = new URL((captured ?? { url: '' }).url);
166
+ expect(url.protocol).toBe('http:');
167
+ expect(url.hostname).not.toBe('evil.com');
168
+ });
169
+
143
170
  it('runs middleware before handler when middleware calls next()', async () => {
144
171
  /** @type {string[]} */
145
172
  const calls = [];
package/types/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type {
2
2
  AdapterCoreOptions,
3
- FetchHandler,
4
- ServeResult,
3
+ ServeFunction,
5
4
  ServeStaticOptions as BaseServeStaticOptions,
6
5
  ServeStaticDirectoryOptions as BaseServeStaticDirectoryOptions,
7
6
  } from '@ripple-ts/adapter';
@@ -25,13 +24,14 @@ export type StaticMiddleware = (
25
24
  next: (error?: any) => void,
26
25
  ) => void;
27
26
 
28
- export function serve(
29
- fetch_handler: FetchHandler<{
27
+ export const serve: ServeFunction<
28
+ {
30
29
  node_request: import('node:http').IncomingMessage;
31
30
  node_response: import('node:http').ServerResponse;
32
- }>,
33
- options?: ServeOptions,
34
- ): ServeResult<import('node:http').Server>;
31
+ },
32
+ ServeOptions,
33
+ import('node:http').Server
34
+ >;
35
35
 
36
36
  /**
37
37
  * Create a middleware that serves static files from a directory