Skip to content

Commit 268ee6b

Browse files
expose event.clientAddress (#4289)
* expose event.clientAddress * Fix check error * implement for dev and preview * implement for Netlify * implement cloudflare and vercel * throw error if adapter does not specify getClientAddress * update adapter-cloudflare-workers * button up types * add getClientAddress to adapter-node behind trustProxy option * update docs * changesets * docs * set address header explicitly * lint * error on misconfigured header Co-authored-by: Ben McCann <[email protected]>
1 parent c3c700f commit 268ee6b

File tree

22 files changed

+234
-77
lines changed

22 files changed

+234
-77
lines changed

.changeset/bright-taxis-remain.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@sveltejs/adapter-cloudflare': patch
3+
'@sveltejs/adapter-cloudflare-workers': patch
4+
'@sveltejs/adapter-netlify': patch
5+
'@sveltejs/adapter-node': patch
6+
'@sveltejs/adapter-vercel': patch
7+
---
8+
9+
Provide getClientAddress function

.changeset/sour-hounds-punch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
[breaking] require adapters to supply a getClientAddress function

.changeset/wild-snails-wait.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
expose client IP address as event.clientAddress

documentation/docs/09-adapters.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ Within the `adapt` method, there are a number of things that an adapter should d
103103
- Clear out the build directory
104104
- Write SvelteKit output with `builder.writeClient`, `builder.writePrerendered`, `builder.writeServer`, and `builder.writeStatic`
105105
- Output code that:
106-
- Imports `App` from `${builder.getServerDirectory()}/app.js`
106+
- Imports `Server` from `${builder.getServerDirectory()}/index.js`
107107
- Instantiates the app with a manifest generated with `builder.generateManifest({ relativePath })`
108-
- Listens for requests from the platform, converts them to a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) if necessary, calls the `render` function to generate a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) and responds with it
108+
- Listens for requests from the platform, converts them to a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) if necessary, calls the `server.respond(request, { getClientAddress })` function to generate a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) and responds with it
109109
- expose any platform-specific information to SvelteKit via the `platform` option passed to `server.respond`
110110
- Globally shims `fetch` to work on the target platform, if necessary. SvelteKit provides a `@sveltejs/kit/install-fetch` helper for platforms that can use `node-fetch`
111111
- Bundle the output to avoid needing to install dependencies on the target platform, if necessary

packages/adapter-cloudflare-workers/files/entry.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ async function handle(event) {
5050

5151
// dynamically-generated pages
5252
try {
53-
return await server.respond(request);
53+
return await server.respond(request, {
54+
getClientAddress() {
55+
return request.headers.get('cf-connecting-ip');
56+
}
57+
});
5458
} catch (e) {
5559
return new Response('Error rendering route:' + (e.message || e.toString()), { status: 500 });
5660
}

packages/adapter-cloudflare/files/worker.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,12 @@ export default {
5050

5151
// dynamically-generated pages
5252
try {
53-
return await server.respond(req, { platform: { env, context } });
53+
return await server.respond(req, {
54+
platform: { env, context },
55+
getClientAddress() {
56+
return req.headers.get('cf-connecting-ip');
57+
}
58+
});
5459
} catch (e) {
5560
return new Response('Error rendering route: ' + (e.message || e.toString()), { status: 500 });
5661
}

packages/adapter-netlify/src/handler.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ export function init(manifest) {
1010
const server = new Server(manifest);
1111

1212
return async (event, context) => {
13-
const rendered = await server.respond(to_request(event), { platform: { context } });
13+
const rendered = await server.respond(to_request(event), {
14+
platform: { context },
15+
getClientAddress() {
16+
return event.headers['x-nf-client-connection-ip'];
17+
}
18+
});
1419

1520
const partial_response = {
1621
statusCode: rendered.status,

packages/adapter-node/README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export default {
2525
protocol: 'PROTOCOL_HEADER',
2626
host: 'HOST_HEADER'
2727
}
28-
}
28+
},
29+
xForwardedForIndex: -1
2930
})
3031
}
3132
};
@@ -63,6 +64,14 @@ PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host node build
6364

6465
> [`x-forwarded-proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) and [`x-forwarded-host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host) are de facto standard headers that forward the original protocol and host if you're using a reverse proxy (think load balancers and CDNs). You should only set these variables if you trust the reverse proxy.
6566
67+
The [RequestEvent](https://kit.svelte.dev/docs/types#additional-types-requestevent) object passed to hooks and endpoints includes an `event.clientAddress` property representing the client's IP address. By default this is the connecting `remoteAddress`. If your server is behind one or more proxies (such as a load balancer), this value will contain the innermost proxy's IP address rather than the client's, so we need to specify an `ADDRESS_HEADER` to read the address from:
68+
69+
```
70+
ADDRESS_HEADER=True-Client-IP node build
71+
```
72+
73+
> Headers can easily be spoofed. As with `PROTOCOL_HEADER` and `HOST_HEADER`, you should [know what you're doing](https://adam-p.ca/blog/2022/03/x-forwarded-for/) before setting these.
74+
6675
All of these environment variables can be changed, if necessary, using the `env` option:
6776

6877
```js
@@ -71,6 +80,7 @@ env: {
7180
port: 'MY_PORT_VARIABLE',
7281
origin: 'MY_ORIGINURL',
7382
headers: {
83+
address: 'MY_ADDRESS_HEADER',
7484
protocol: 'MY_PROTOCOL_HEADER',
7585
host: 'MY_HOST_HEADER'
7686
}
@@ -84,6 +94,24 @@ MY_ORIGINURL=https://my.site \
8494
node build
8595
```
8696

97+
### xForwardedForIndex
98+
99+
If the `ADDRESS_HEADER` is `X-Forwarded-For`, the header value will contain a comma-separated list of IP addresses. For example, if there are three proxies between your server and the client, proxy 3 will forward the addresses of the client and the first two proxies:
100+
101+
```
102+
<client address>, <proxy 1 address>, <proxy 2 address>
103+
```
104+
105+
To get the client address we could use `xForwardedFor: 0` or `xForwardedFor: -3`, which counts back from the number of addresses.
106+
107+
**X-Forwarded-For is [trivial to spoof](https://adam-p.ca/blog/2022/03/x-forwarded-for/), howevever**:
108+
109+
```
110+
<spoofed address>, <client address>, <proxy 1 address>, <proxy 2 address>
111+
```
112+
113+
For that reason you should always use a negative number (depending on the number of proxies) if you need to trust `event.clientAddress`. In the above example, `0` would yield the spoofed address while `-3` would continue to work.
114+
87115
## Custom server
88116

89117
The adapter creates two files in your build directory — `index.js` and `handler.js`. Running `index.js` — e.g. `node build`, if you use the default build directory — will start a server on the configured port.

packages/adapter-node/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ interface AdapterOptions {
1515
port?: string;
1616
origin?: string;
1717
headers?: {
18+
address?: string;
1819
protocol?: string;
1920
host?: string;
2021
};
2122
};
23+
xForwardedForIndex?: number;
2224
}
2325

2426
declare function plugin(options?: AdapterOptions): Adapter;

packages/adapter-node/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ export default function ({
1919
port: port_env = 'PORT',
2020
origin: origin_env = 'ORIGIN',
2121
headers: {
22+
address: address_header_env = 'ADDRESS_HEADER',
2223
protocol: protocol_header_env = 'PROTOCOL_HEADER',
2324
host: host_header_env = 'HOST_HEADER'
2425
} = {}
25-
} = {}
26+
} = {},
27+
xForwardedForIndex = -1
2628
} = {}) {
2729
return {
2830
name: '@sveltejs/adapter-node',
@@ -52,7 +54,9 @@ export default function ({
5254
PORT_ENV: JSON.stringify(port_env),
5355
ORIGIN: origin_env ? `process.env[${JSON.stringify(origin_env)}]` : 'undefined',
5456
PROTOCOL_HEADER: JSON.stringify(protocol_header_env),
55-
HOST_HEADER: JSON.stringify(host_header_env)
57+
HOST_HEADER: JSON.stringify(host_header_env),
58+
ADDRESS_HEADER: JSON.stringify(address_header_env),
59+
X_FORWARDED_FOR_INDEX: JSON.stringify(xForwardedForIndex)
5660
}
5761
});
5862

0 commit comments

Comments
 (0)