Skip to content

Commit

Permalink
Allow setting multiple cookies in Netlify adapter (#3092)
Browse files Browse the repository at this point in the history
* Allow setting multiple cookies in Netlify adapter

* Adds a changeset

* Set the response status code

* Add a comment on why this is needed
  • Loading branch information
matthewp authored Apr 12, 2022
1 parent c459c87 commit a5caf08
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/sharp-moose-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/netlify': patch
---

Fixes setting multiple cookies with the Netlify adapter
31 changes: 27 additions & 4 deletions packages/integrations/netlify/src/netlify-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,37 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
};
}

const response = await app.render(request);
const response: Response = await app.render(request);
const responseBody = await response.text();

return {
statusCode: 200,
headers: Object.fromEntries(response.headers.entries()),
const responseHeaders = Object.fromEntries(response.headers.entries());
const fnResponse: any = {
statusCode: response.status,
headers: responseHeaders,
body: responseBody,
};

// Special-case set-cookie which has to be set an different way :/
// The fetch API does not have a way to get multiples of a single header, but instead concatenates
// them. There are non-standard ways to do it, and node-fetch gives us headers.raw()
// See https://github.com/whatwg/fetch/issues/973 for discussion
if (response.headers.has('set-cookie') && 'raw' in response.headers) {
// Node fetch allows you to get the raw headers, which includes multiples of the same type.
// This is needed because Set-Cookie *must* be called for each cookie, and can't be
// concatenated together.
type HeadersWithRaw = Headers & {
raw: () => Record<string, string[]>;
};

const rawPacked = (response.headers as HeadersWithRaw).raw();
if('set-cookie' in rawPacked) {
fnResponse.multiValueHeaders = {
'set-cookie': rawPacked['set-cookie']
}
}
}

return fnResponse;
};

return { handler };
Expand Down
50 changes: 50 additions & 0 deletions packages/integrations/netlify/test/cookies.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { expect } from 'chai';
import { load as cheerioLoad } from 'cheerio';
import { loadFixture } from '../../../astro/test/test-utils.js';
import netlifyAdapter from '../dist/index.js';
import { fileURLToPath } from 'url';

describe('Cookies', () => {
/** @type {import('../../../astro/test/test-utils').Fixture} */
let fixture;

before(async () => {
fixture = await loadFixture({
root: new URL('./fixtures/cookies/', import.meta.url).toString(),
experimental: {
ssr: true,
},
adapter: netlifyAdapter({
dist: new URL('./fixtures/cookies/dist/', import.meta.url),
}),
site: `http://example.com`,
vite: {
resolve: {
alias: {
'@astrojs/netlify/netlify-functions.js': fileURLToPath(
new URL('../dist/netlify-functions.js', import.meta.url)
),
},
},
},
});
await fixture.build();
});

it('Can set multiple', async () => {
const entryURL = new URL('./fixtures/cookies/dist/functions/entry.mjs', import.meta.url);
const { handler } = await import(entryURL);
const resp = await handler({
httpMethod: 'POST',
headers: {},
rawUrl: 'http://example.com/login',
body: '{}',
isBase64Encoded: false
});
expect(resp.statusCode).to.equal(301);
expect(resp.headers.location).to.equal('/');
expect(resp.multiValueHeaders).to.be.deep.equal({
'set-cookie': [ 'foo=foo; HttpOnly', 'bar=bar; HttpOnly' ]
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<html>
<head><title>Testing</title></head>
<body>
<h1>Testing</h1>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

export function post() {
const headers = new Headers();
headers.append('Set-Cookie', `foo=foo; HttpOnly`);
headers.append('Set-Cookie', `bar=bar; HttpOnly`);
headers.append('Location', '/');

return new Response('', {
status: 301,
headers,
});
}

0 comments on commit a5caf08

Please sign in to comment.