Skip to content

Commit 3936b56

Browse files
[fix] forward cookies from fetch on redirect response (#6833)
* [fix] forward cookies from fetch on redirect response Fixes #6792 * harmonize cookie type * fix syntax Co-authored-by: Rich Harris <[email protected]>
1 parent 9dbcce9 commit 3936b56

File tree

13 files changed

+79
-27
lines changed

13 files changed

+79
-27
lines changed

.changeset/afraid-kings-mix.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+
[fix] forward cookies from fetch on redirect response

packages/kit/src/runtime/server/cookie.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { parse } from 'cookie';
1+
import { parse, serialize } from 'cookie';
22

33
/** @type {import('cookie').CookieSerializeOptions} */
44
const DEFAULT_SERIALIZE_OPTIONS = {
@@ -12,7 +12,7 @@ const DEFAULT_SERIALIZE_OPTIONS = {
1212
* @param {URL} url
1313
*/
1414
export function get_cookies(request, url) {
15-
/** @type {Map<string, {name: string; value: string; options: import('cookie').CookieSerializeOptions;}>} */
15+
/** @type {Map<string, import('./page/types').Cookie>} */
1616
const new_cookies = new Map();
1717

1818
/** @type {import('types').Cookies} */
@@ -102,3 +102,14 @@ export function path_matches(path, constraint) {
102102
if (path === normalized) return true;
103103
return path.startsWith(normalized + '/');
104104
}
105+
106+
/**
107+
* @param {Headers} headers
108+
* @param {import('./page/types').Cookie[]} cookies
109+
*/
110+
export function add_cookies_to_headers(headers, cookies) {
111+
for (const new_cookie of cookies) {
112+
const { name, value, options } = new_cookie;
113+
headers.append('set-cookie', serialize(name, value, options));
114+
}
115+
}

packages/kit/src/runtime/server/index.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as cookie from 'cookie';
21
import { render_endpoint } from './endpoint.js';
32
import { render_page } from './page/index.js';
43
import { render_response } from './page/render.js';
@@ -9,7 +8,7 @@ import { decode_params, disable_search, normalize_path } from '../../utils/url.j
98
import { exec } from '../../utils/routing.js';
109
import { render_data } from './data/index.js';
1110
import { DATA_SUFFIX } from '../../constants.js';
12-
import { get_cookies } from './cookie.js';
11+
import { add_cookies_to_headers, get_cookies } from './cookie.js';
1312
import { HttpError } from '../control.js';
1413

1514
/* global __SVELTEKIT_ADAPTER_NAME__ */
@@ -246,12 +245,7 @@ export async function respond(request, options, state) {
246245
}
247246
}
248247

249-
for (const new_cookie of Array.from(new_cookies.values())) {
250-
response.headers.append(
251-
'set-cookie',
252-
cookie.serialize(new_cookie.name, new_cookie.value, new_cookie.options)
253-
);
254-
}
248+
add_cookies_to_headers(response.headers, Array.from(new_cookies.values()));
255249

256250
return response;
257251
}

packages/kit/src/runtime/server/page/fetch.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function create_fetch({ event, options, state, route, prerender_default,
1919

2020
const initial_cookies = cookie.parse(event.request.headers.get('cookie') || '');
2121

22-
/** @type {import('set-cookie-parser').Cookie[]} */
22+
/** @type {import('./types').Cookie[]} */
2323
const set_cookies = [];
2424

2525
/**
@@ -31,8 +31,8 @@ export function create_fetch({ event, options, state, route, prerender_default,
3131
const new_cookies = {};
3232

3333
for (const cookie of set_cookies) {
34-
if (!domain_matches(url.hostname, cookie.domain)) continue;
35-
if (!path_matches(url.pathname, cookie.path)) continue;
34+
if (!domain_matches(url.hostname, cookie.options.domain)) continue;
35+
if (!path_matches(url.pathname, cookie.options.path)) continue;
3636

3737
new_cookies[cookie.name] = cookie.value;
3838
}
@@ -179,9 +179,11 @@ export function create_fetch({ event, options, state, route, prerender_default,
179179
const set_cookie = response.headers.get('set-cookie');
180180
if (set_cookie) {
181181
set_cookies.push(
182-
...set_cookie_parser
183-
.splitCookiesString(set_cookie)
184-
.map((str) => set_cookie_parser.parseString(str))
182+
...set_cookie_parser.splitCookiesString(set_cookie).map((str) => {
183+
const { name, value, ...options } = set_cookie_parser.parseString(str);
184+
// options.sameSite is string, something more specific is required - type cast is safe
185+
return /** @type{import('./types').Cookie} */ ({ name, value, options });
186+
})
185187
);
186188
}
187189

packages/kit/src/runtime/server/page/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export async function render_page(event, route, page, options, state, resolve_op
217217
});
218218
}
219219

220-
return redirect_response(err.status, err.location);
220+
return redirect_response(err.status, err.location, cookies);
221221
}
222222

223223
const status = err instanceof HttpError ? err.status : 500;

packages/kit/src/runtime/server/page/render.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { devalue } from 'devalue';
22
import { readable, writable } from 'svelte/store';
3-
import * as cookie from 'cookie';
43
import { hash } from '../../hash.js';
54
import { serialize_data } from './serialize_data.js';
65
import { s } from '../../../utils/misc.js';
76
import { Csp } from './csp.js';
7+
import { add_cookies_to_headers } from '../cookie.js';
88

99
// TODO rename this function/module
1010

@@ -18,7 +18,7 @@ const updated = {
1818
* @param {{
1919
* branch: Array<import('./types').Loaded>;
2020
* fetched: Array<import('./types').Fetched>;
21-
* cookies: import('set-cookie-parser').Cookie[];
21+
* cookies: import('./types').Cookie[];
2222
* options: import('types').SSROptions;
2323
* state: import('types').SSRState;
2424
* page_config: { ssr: boolean; csr: boolean };
@@ -328,11 +328,7 @@ export async function render_response({
328328
headers.set('content-security-policy-report-only', report_only_header);
329329
}
330330

331-
for (const new_cookie of cookies) {
332-
const { name, value, ...options } = new_cookie;
333-
// @ts-expect-error
334-
headers.append('set-cookie', cookie.serialize(name, value, options));
335-
}
331+
add_cookies_to_headers(headers, cookies);
336332

337333
if (link_header_preloads.size) {
338334
headers.set('link', Array.from(link_header_preloads).join(', '));

packages/kit/src/runtime/server/page/types.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { CookieSerializeOptions } from 'cookie';
12
import { SSRNode, CspDirectives } from 'types';
2-
import { HttpError } from '../../control.js';
33

44
export interface Fetched {
55
url: string;
@@ -33,3 +33,9 @@ export interface CspOpts {
3333
dev: boolean;
3434
prerender: boolean;
3535
}
36+
37+
export interface Cookie {
38+
name: string;
39+
value: string;
40+
options: CookieSerializeOptions;
41+
}

packages/kit/src/runtime/server/utils.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { devalue } from 'devalue';
22
import { DATA_SUFFIX } from '../../constants.js';
33
import { negotiate } from '../../utils/http.js';
44
import { HttpError } from '../control.js';
5+
import { add_cookies_to_headers } from './cookie.js';
56

67
/** @param {any} body */
78
export function is_pojo(body) {
@@ -169,10 +170,13 @@ export function handle_error_and_jsonify(event, options, error) {
169170
/**
170171
* @param {number} status
171172
* @param {string} location
173+
* @param {import('./page/types.js').Cookie[]} [cookies]
172174
*/
173-
export function redirect_response(status, location) {
174-
return new Response(undefined, {
175+
export function redirect_response(status, location, cookies = []) {
176+
const response = new Response(undefined, {
175177
status,
176178
headers: { location }
177179
});
180+
add_cookies_to_headers(response.headers, cookies);
181+
return response;
178182
}

packages/kit/test/apps/basics/src/routes/shadowed/+page.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<a href="/shadowed/simple">simple</a>
22
<a href="/shadowed/redirect-get">redirect-get</a>
33
<a href="/shadowed/redirect-get-with-cookie">redirect-get-with-cookie</a>
4+
<a href="/shadowed/redirect-get-with-cookie-from-fetch">redirect-get-with-cookie-from-fetch</a>
45
<a href="/shadowed/error-get">error-get</a>
56
<a href="/shadowed/no-get">no-get</a>
67
<a href="/shadowed/dynamic/foo">dynamic/foo</a>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { redirect } from '@sveltejs/kit';
2+
3+
/** @type {import('./$types').PageLoad} */
4+
export async function load({ fetch }) {
5+
await fetch('/shadowed/redirect-get-with-cookie-from-fetch/endpoint');
6+
throw redirect(302, '/shadowed/redirected');
7+
}

0 commit comments

Comments
 (0)