Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fresh-deserts-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sveltejs/kit": patch
---

fix: make cookie options optional
8 changes: 4 additions & 4 deletions documentation/docs/20-core-concepts/30-form-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export const actions = {
const password = data.get('password');

const user = await db.getUser(email);
cookies.set('sessionid', await db.createSession(user), { path: '/' });
cookies.set('sessionid', await db.createSession(user));

return { success: true };
},
Expand Down Expand Up @@ -198,7 +198,7 @@ export const actions = {
return fail(400, { email, incorrect: true });
}+++

cookies.set('sessionid', await db.createSession(user), { path: '/' });
cookies.set('sessionid', await db.createSession(user));

return { success: true };
},
Expand Down Expand Up @@ -261,7 +261,7 @@ export const actions = {
return fail(400, { email, incorrect: true });
}

cookies.set('sessionid', await db.createSession(user), { path: '/' });
cookies.set('sessionid', await db.createSession(user));

+++ if (url.searchParams.has('redirectTo')) {
redirect(303, url.searchParams.get('redirectTo'));
Expand Down Expand Up @@ -333,7 +333,7 @@ export function load(event) {
/** @satisfies {import('./$types').Actions} */
export const actions = {
logout: async (event) => {
event.cookies.delete('sessionid', { path: '/' });
event.cookies.delete('sessionid');
event.locals.user = null;
}
};
Expand Down
8 changes: 4 additions & 4 deletions packages/kit/src/exports/public.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ export interface Cookies {
* @param value the cookie value
* @param opts the options passed to `cookie.stringifySetCookie` with the SvelteKit defaults described above. See documentation [here](https://github.com/jshttp/cookie?tab=readme-ov-file#cookiestringifysetcookiesetcookieobj-options)
*/
set: (name: string, value: string, opts: import('cookie').SerializeOptions) => void;
set: (name: string, value: string, opts?: import('cookie').SerializeOptions) => void;

/**
* Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.
Expand All @@ -379,7 +379,7 @@ export interface Cookies {
* @param name the name of the cookie
* @param opts the options passed to `cookie.stringifySetCookie` with the SvelteKit defaults described above. See documentation [here](https://github.com/jshttp/cookie?tab=readme-ov-file#cookiestringifysetcookiesetcookieobj-options)
*/
delete: (name: string, opts: import('cookie').SerializeOptions) => void;
delete: (name: string, opts?: import('cookie').SerializeOptions) => void;

/**
* Parses a single `Set-Cookie` header. This allows you to apply cookies received from an external source:
Expand All @@ -394,7 +394,7 @@ export interface Cookies {
*
* for (const str of response.headers.getSetCookie()) {
* const { name, value, ...options } = cookies.parse(str);
* cookies.set(name, value, { ...options, path: '/' });
* cookies.set(name, value, options);
* }
*
* // ...
Expand All @@ -415,7 +415,7 @@ export interface Cookies {
* @param value the cookie value
* @param opts the options passed to `cookie.stringifySetCookie` with the SvelteKit defaults described above. See documentation [here](https://github.com/jshttp/cookie?tab=readme-ov-file#cookiestringifysetcookiesetcookieobj-options)
*/
serialize: (name: string, value: string, opts: import('cookie').SerializeOptions) => string;
serialize: (name: string, value: string, opts?: import('cookie').SerializeOptions) => string;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/kit/src/runtime/app/server/remote/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function derive_remote_function_event(event, state, allow_cookies) {
throw new Error('Cannot set cookies in `query` or `prerender` functions');
}

if (opts.path && !opts.path.startsWith('/')) {
if (opts?.path && !opts.path.startsWith('/')) {
throw new Error('Cookies set in remote functions must have an absolute path');
}

Expand All @@ -104,7 +104,7 @@ function derive_remote_function_event(event, state, allow_cookies) {
throw new Error('Cannot delete cookies in `query` or `prerender` functions');
}

if (opts.path && !opts.path.startsWith('/')) {
if (opts?.path && !opts.path.startsWith('/')) {
throw new Error('Cookies deleted in remote functions must have an absolute path');
}

Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export function get_cookies(request, url) {

parse: parseSetCookie,

serialize(name, value, { encode, ...options }) {
serialize(name, value, { encode, ...options } = {}) {
let path = options.path ?? '/';

if (!options.domain || options.domain === url.hostname) {
Expand Down
50 changes: 25 additions & 25 deletions packages/kit/src/runtime/server/cookie.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,27 @@ describe.skipIf(!process.env.DEV)('cookies in dev', () => {
const { cookies } = cookies_setup();

// name ("a=") is 2 bytes, so the value alone must stay under 4094 bytes
expect(() => cookies.set('a', 'a'.repeat(4096), { path: '/' })).toThrowError(
expect(() => cookies.set('a', 'a'.repeat(4096))).toThrowError(
'Cookie "a" is too large, and will be discarded by the browser'
);
});

test('does not throw if cookie name/value is at the 4,096 byte limit', () => {
const { cookies } = cookies_setup();

expect(() => cookies.set('a', 'a'.repeat(4095), { path: '/' })).not.toThrow();
expect(() => cookies.set('a', 'a'.repeat(4095))).not.toThrow();
});

test('secure defaults to false when served over http (e.g. --host)', () => {
const { cookies, new_cookies } = cookies_setup({ href: 'http://192.168.0.1:5173' });
cookies.set('a', 'b', { path: '/' });
cookies.set('a', 'b');
const opts = new_cookies.get('/?a')?.options;
assert.equal(opts?.secure, false);
});

test('secure defaults to false even when served over https', () => {
const { cookies, new_cookies } = cookies_setup({ href: 'https://192.168.0.1:5173' });
cookies.set('a', 'b', { path: '/' });
cookies.set('a', 'b');
const opts = new_cookies.get('/?a')?.options;
assert.equal(opts?.secure, false);
});
Expand Down Expand Up @@ -97,25 +97,25 @@ describe.skipIf(!!process.env.DEV)('cookies in prod', () => {

test('a cookie should not be present after it is deleted', () => {
const { cookies } = cookies_setup();
cookies.set('a', 'b', { path: '/' });
cookies.set('a', 'b');
expect(cookies.get('a')).toEqual('b');
cookies.delete('a', { path: '/' });
cookies.delete('a');
assert.isUndefined(cookies.get('a'));
});

test('getAll should not include deleted cookies', () => {
const { cookies } = cookies_setup({ headers: { cookie: 'session=abc' } });
cookies.set('session', 'abc', { path: '/' });
cookies.set('session', 'abc');
expect(cookies.getAll()).toEqual([{ name: 'session', value: 'abc' }]);

cookies.delete('session', { path: '/' });
cookies.delete('session');
assert.isUndefined(cookies.get('session'));
expect(cookies.getAll()).toEqual([]);
});

test('default values when set is called', () => {
const { cookies, new_cookies } = cookies_setup();
cookies.set('a', 'b', { path: '/' });
cookies.set('a', 'b');
const opts = new_cookies.get('/?a')?.options;
assert.equal(opts?.secure, true);
assert.equal(opts?.httpOnly, true);
Expand All @@ -135,14 +135,14 @@ describe.skipIf(!!process.env.DEV)('cookies in prod', () => {

test('default values when on localhost', () => {
const { cookies, new_cookies } = cookies_setup({ href: 'http://localhost:1234' });
cookies.set('a', 'b', { path: '/' });
cookies.set('a', 'b');
const opts = new_cookies.get('/?a')?.options;
assert.equal(opts?.secure, false);
});

test('secure defaults to true on http on a non-localhost host', () => {
const { cookies, new_cookies } = cookies_setup({ href: 'http://192.168.0.1:5173' });
cookies.set('a', 'b', { path: '/' });
cookies.set('a', 'b');
const opts = new_cookies.get('/?a')?.options;
assert.equal(opts?.secure, true);
});
Expand All @@ -159,7 +159,7 @@ describe.skipIf(!!process.env.DEV)('cookies in prod', () => {

test('default values when delete is called', () => {
const { cookies, new_cookies } = cookies_setup();
cookies.delete('a', { path: '/' });
cookies.delete('a');
const opts = new_cookies.get('/?a')?.options;
assert.equal(opts?.secure, true);
assert.equal(opts?.httpOnly, true);
Expand Down Expand Up @@ -188,17 +188,17 @@ describe.skipIf(!!process.env.DEV)('cookies in prod', () => {

test('last cookie set with the same name wins', () => {
const { cookies, new_cookies } = cookies_setup();
cookies.set('a', 'foo', { path: '/' });
cookies.set('a', 'bar', { path: '/' });
cookies.set('a', 'foo');
cookies.set('a', 'bar');
const entry = new_cookies.get('/?a');
assert.equal(entry?.value, 'bar');
});

test('cookie names are case sensitive', () => {
const { cookies, new_cookies } = cookies_setup();
// not that one should do this, but we follow the spec...
cookies.set('a', 'foo', { path: '/' });
cookies.set('A', 'bar', { path: '/' });
cookies.set('a', 'foo');
cookies.set('A', 'bar');
const entrya = new_cookies.get('/?a');
const entryA = new_cookies.get('/?A');
assert.equal(entrya?.value, 'foo');
Expand All @@ -213,7 +213,7 @@ describe.skipIf(!!process.env.DEV)('cookies in prod', () => {
cookie: 'a=f%C3%BC; b=foo+bar' // a=fü
}
});
cookies.set('c', 'fö', { path: '/' }); // should use default encoding
cookies.set('c', 'fö'); // should use default encoding
cookies.set('d', 'fö', { path: '/', encode: () => 'öf' }); // should respect `encode`
const header = get_cookie_header(new URL(href), 'e=f%C3%A4; f=foo+bar');
assert.equal(header, 'a=f%C3%BC; b=foo+bar; c=f%C3%B6; d=öf; e=f%C3%A4; f=foo+bar');
Expand All @@ -223,9 +223,9 @@ describe.skipIf(!!process.env.DEV)('cookies in prod', () => {
const { cookies } = cookies_setup();
expect(cookies.getAll()).toEqual([{ name: 'a', value: 'b' }]);

cookies.set('a', 'foo', { path: '/' });
cookies.set('a', 'bar', { path: '/' });
cookies.set('b', 'baz', { path: '/' });
cookies.set('a', 'foo');
cookies.set('a', 'bar');
cookies.set('b', 'baz');

expect(cookies.getAll()).toEqual([
{ name: 'a', value: 'bar' },
Expand Down Expand Up @@ -262,15 +262,15 @@ describe.skipIf(!!process.env.DEV)('cookies in prod', () => {
test('reproduce issue #13947: multiple cookies with same name but different paths', () => {
// Test on root path to see if most specific cookie wins
const { cookies: root_cookies } = cookies_setup({ href: 'https://example.com/' });
root_cookies.set('key', 'value_root', { path: '/' });
root_cookies.set('key', 'value_root');
root_cookies.set('key', 'value_foo', { path: '/foo' });

// When on root path, should get the root cookie
expect(root_cookies.get('key')).toEqual('value_root');

// Test on /foo path to see if more specific cookie wins
const { cookies: foo_cookies } = cookies_setup({ href: 'https://example.com/foo' });
foo_cookies.set('key', 'value_root', { path: '/' });
foo_cookies.set('key', 'value_root');
foo_cookies.set('key', 'value_foo', { path: '/foo' });

// When on /foo path, should get the more specific /foo cookie
Expand All @@ -295,7 +295,7 @@ describe.skipIf(!!process.env.DEV)('cookies in prod', () => {
const { cookies } = cookies_setup({ href: 'https://example.com/x/y/z' });

// Set cookies with increasing path specificity
cookies.set('n', '1', { path: '/' });
cookies.set('n', '1');
cookies.set('n', '2', { path: '/x' });
cookies.set('n', '3', { path: '/x/y' });
cookies.set('n', '4', { path: '/x/y/z' });
Expand All @@ -308,7 +308,7 @@ describe.skipIf(!!process.env.DEV)('cookies in prod', () => {
const { cookies } = cookies_setup();

// Set a cookie the old way
cookies.set('old-style', 'value', { path: '/' });
cookies.set('old-style', 'value');

// Should be retrievable without specifying path
expect(cookies.get('old-style')).toEqual('value');
Expand All @@ -321,7 +321,7 @@ describe.skipIf(!!process.env.DEV)('cookies in prod', () => {
// Set cookies with the same name but different path specificity
// Setting most specific first, then less specific ones to expose the bug
cookies.set('duplicate', 'foobar_value', { path: '/foo/bar' }); // Most specific
cookies.set('duplicate', 'root_value', { path: '/' }); // Least specific
cookies.set('duplicate', 'root_value'); // Least specific
cookies.set('duplicate', 'foo_value', { path: '/foo' }); // Middle specificity

const all = cookies.getAll();
Expand Down
1 change: 0 additions & 1 deletion packages/kit/test/apps/async/src/hooks.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export async function handle({ event, resolve }) {
// against the same server clobber each other's state and flake.
if (!event.cookies.get('session')) {
event.cookies.set('session', crypto.randomUUID(), {
path: '/',
httpOnly: true,
sameSite: 'lax'
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const blocked_operations = query(() => {
const results = [];

try {
cookies.set('illegal', 'yes', { path: '/' });
cookies.set('illegal', 'yes');
results.push('cookies.set succeeded');
} catch (e) {
results.push(/** @type {Error} */ (e).message);
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/test/apps/basics/src/routes/+layout.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function load({ cookies, locals, fetch }) {

const should_fail = cookies.get('fail-type');
if (should_fail) {
cookies.delete('fail-type', { path: '/' });
cookies.delete('fail-type');
if (should_fail === 'expected') {
error(401, 'Not allowed');
} else if (should_fail === 'unexpected') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
* @type {import('./$types').RequestHandler} param0
*/
export function GET({ cookies, url }) {
cookies.set('fail-type', url.searchParams.get('type') ?? '', { path: '/' });
cookies.set('fail-type', url.searchParams.get('type') ?? '');
return new Response();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import { json } from '@sveltejs/kit';

/** @type {import('./$types').RequestHandler} */
export function GET({ cookies }) {
cookies.set(
'cache-control-bust-count',
+(cookies.get('cache-control-bust-count') ?? 0) + 1 + '',
{ path: '/' }
);
cookies.set('cache-control-bust-count', +(cookies.get('cache-control-bust-count') ?? 0) + 1 + '');

return json({});
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { json } from '@sveltejs/kit';
export function GET({ cookies }) {
cookies.set(
'cache-control-default-count',
+(cookies.get('cache-control-default-count') ?? 0) + 1 + '',
{ path: '/' }
+(cookies.get('cache-control-default-count') ?? 0) + 1 + ''
);

return json({});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { json } from '@sveltejs/kit';
export function GET({ cookies }) {
cookies.set(
'cache-control-force-count',
+(cookies.get('cache-control-force-count') ?? 0) + 1 + '',
{ path: '/' }
+(cookies.get('cache-control-force-count') ?? 0) + 1 + ''
);

return json({});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export async function GET({ request, setHeaders, cookies }) {
export function POST({ cookies }) {
cookies.set(
'fetch-cache-control-headers-diff',
String(+(cookies.get('fetch-cache-control-headers-diff') ?? 0) + 1),
{ path: '/' }
String(+(cookies.get('fetch-cache-control-headers-diff') ?? 0) + 1)
);

return new Response();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/** @type {import('./$types').RequestHandler} */
export function GET({ cookies }) {
cookies.set('shadow-redirect-fetch', 'happy', {
path: '/',
secure: false // safari
});
return new Response('ok');
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/test/apps/options-2/src/hooks.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
export async function handle({ event, resolve }) {
// isolates the in-memory count in count.remote.js per browser session
Comment thread
vercel[bot] marked this conversation as resolved.
if (!event.cookies.get('session')) {
event.cookies.set('session', crypto.randomUUID(), { path: '/' });
event.cookies.set('session', crypto.randomUUID());
}

return resolve(event, {
Expand Down
Loading
Loading