Skip to content

Commit a5caf08

Browse files
authored
Allow setting multiple cookies in Netlify adapter (#3092)
* Allow setting multiple cookies in Netlify adapter * Adds a changeset * Set the response status code * Add a comment on why this is needed
1 parent c459c87 commit a5caf08

5 files changed

Lines changed: 100 additions & 4 deletions

File tree

.changeset/sharp-moose-perform.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@astrojs/netlify': patch
3+
---
4+
5+
Fixes setting multiple cookies with the Netlify adapter

packages/integrations/netlify/src/netlify-functions.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,37 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
3333
};
3434
}
3535

36-
const response = await app.render(request);
36+
const response: Response = await app.render(request);
3737
const responseBody = await response.text();
3838

39-
return {
40-
statusCode: 200,
41-
headers: Object.fromEntries(response.headers.entries()),
39+
const responseHeaders = Object.fromEntries(response.headers.entries());
40+
const fnResponse: any = {
41+
statusCode: response.status,
42+
headers: responseHeaders,
4243
body: responseBody,
4344
};
45+
46+
// Special-case set-cookie which has to be set an different way :/
47+
// The fetch API does not have a way to get multiples of a single header, but instead concatenates
48+
// them. There are non-standard ways to do it, and node-fetch gives us headers.raw()
49+
// See https://github.com/whatwg/fetch/issues/973 for discussion
50+
if (response.headers.has('set-cookie') && 'raw' in response.headers) {
51+
// Node fetch allows you to get the raw headers, which includes multiples of the same type.
52+
// This is needed because Set-Cookie *must* be called for each cookie, and can't be
53+
// concatenated together.
54+
type HeadersWithRaw = Headers & {
55+
raw: () => Record<string, string[]>;
56+
};
57+
58+
const rawPacked = (response.headers as HeadersWithRaw).raw();
59+
if('set-cookie' in rawPacked) {
60+
fnResponse.multiValueHeaders = {
61+
'set-cookie': rawPacked['set-cookie']
62+
}
63+
}
64+
}
65+
66+
return fnResponse;
4467
};
4568

4669
return { handler };
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { expect } from 'chai';
2+
import { load as cheerioLoad } from 'cheerio';
3+
import { loadFixture } from '../../../astro/test/test-utils.js';
4+
import netlifyAdapter from '../dist/index.js';
5+
import { fileURLToPath } from 'url';
6+
7+
describe('Cookies', () => {
8+
/** @type {import('../../../astro/test/test-utils').Fixture} */
9+
let fixture;
10+
11+
before(async () => {
12+
fixture = await loadFixture({
13+
root: new URL('./fixtures/cookies/', import.meta.url).toString(),
14+
experimental: {
15+
ssr: true,
16+
},
17+
adapter: netlifyAdapter({
18+
dist: new URL('./fixtures/cookies/dist/', import.meta.url),
19+
}),
20+
site: `http://example.com`,
21+
vite: {
22+
resolve: {
23+
alias: {
24+
'@astrojs/netlify/netlify-functions.js': fileURLToPath(
25+
new URL('../dist/netlify-functions.js', import.meta.url)
26+
),
27+
},
28+
},
29+
},
30+
});
31+
await fixture.build();
32+
});
33+
34+
it('Can set multiple', async () => {
35+
const entryURL = new URL('./fixtures/cookies/dist/functions/entry.mjs', import.meta.url);
36+
const { handler } = await import(entryURL);
37+
const resp = await handler({
38+
httpMethod: 'POST',
39+
headers: {},
40+
rawUrl: 'http://example.com/login',
41+
body: '{}',
42+
isBase64Encoded: false
43+
});
44+
expect(resp.statusCode).to.equal(301);
45+
expect(resp.headers.location).to.equal('/');
46+
expect(resp.multiValueHeaders).to.be.deep.equal({
47+
'set-cookie': [ 'foo=foo; HttpOnly', 'bar=bar; HttpOnly' ]
48+
});
49+
});
50+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<html>
2+
<head><title>Testing</title></head>
3+
<body>
4+
<h1>Testing</h1>
5+
</body>
6+
</html>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
export function post() {
3+
const headers = new Headers();
4+
headers.append('Set-Cookie', `foo=foo; HttpOnly`);
5+
headers.append('Set-Cookie', `bar=bar; HttpOnly`);
6+
headers.append('Location', '/');
7+
8+
return new Response('', {
9+
status: 301,
10+
headers,
11+
});
12+
}

0 commit comments

Comments
 (0)