From 0d14fb85fc437e4648c76adef9d4e5bb879b932b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Wed, 8 Nov 2023 20:51:00 +0100 Subject: [PATCH 01/10] add partioned --- cookie.js | 6 +++++- test/cookie.test.js | 37 +++++++++++++++++++++++++++++++++++++ types/plugin.d.ts | 2 +- types/plugin.test-d.ts | 1 + 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/cookie.js b/cookie.js index 349db26..493bbdb 100644 --- a/cookie.js +++ b/cookie.js @@ -95,7 +95,7 @@ function parse (str, options) { const key = str.substring(pos, eqIdx++).trim() // only assign once - if (undefined === result[key]) { + if (result[key] === undefined) { const val = (str.charCodeAt(eqIdx) === 0x22) ? str.substring(eqIdx + 1, terminatorPos - 1).trim() : str.substring(eqIdx, terminatorPos).trim() @@ -183,6 +183,10 @@ function serialize (name, val, options) { str += '; Secure' } + if (opt.partitioned) { + str += '; Partitioned' + } + if (opt.sameSite) { const sameSite = typeof opt.sameSite === 'string' ? opt.sameSite.toLowerCase() diff --git a/test/cookie.test.js b/test/cookie.test.js index 767302a..1875ebc 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -92,6 +92,43 @@ test('should set multiple cookies', (t) => { }) }) +test('should set multiple cookies', (t) => { + t.plan(11) + const fastify = Fastify() + fastify.register(plugin) + + fastify.get('/', (req, reply) => { + reply + .setCookie('foo', 'foo') + .cookie('bar', 'test') + .setCookie('wee', 'woo', { + partitioned: true, + secure: true + }) + .send({ hello: 'world' }) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 200) + t.same(JSON.parse(res.body), { hello: 'world' }) + + const cookies = res.cookies + t.equal(cookies.length, 3) + t.equal(cookies[0].name, 'foo') + t.equal(cookies[0].value, 'foo') + t.equal(cookies[1].name, 'bar') + t.equal(cookies[1].value, 'test') + t.equal(cookies[2].name, 'wee') + t.equal(cookies[2].value, 'woo') + + t.equal(res.headers['set-cookie'][2], 'wee=woo; Secure; Partitioned') + }) +}) + test('cookies get set correctly with millisecond dates', (t) => { t.plan(8) const fastify = Fastify() diff --git a/types/plugin.d.ts b/types/plugin.d.ts index 23e7500..d337675 100644 --- a/types/plugin.d.ts +++ b/types/plugin.d.ts @@ -125,9 +125,9 @@ declare namespace fastifyCookie { httpOnly?: boolean; /** A `number` in seconds that specifies the `Expires` attribute by adding the specified seconds to the current date. If both `expires` and `maxAge` are set, then `expires` is used. */ maxAge?: number; + partitioned?: boolean; /** The `Path` attribute. Defaults to `/` (the root path). */ path?: string; - priority?: "low" | "medium" | "high"; /** A `boolean` or one of the `SameSite` string attributes. E.g.: `lax`, `none` or `strict`. */ sameSite?: 'lax' | 'none' | 'strict' | boolean; /** The `boolean` value of the `Secure` attribute. Set this option to false when communicating over an unencrypted (HTTP) connection. Value can be set to `auto`; in this case the `Secure` attribute will be set to false for HTTP request, in case of HTTPS it will be set to true. Defaults to true. */ diff --git a/types/plugin.test-d.ts b/types/plugin.test-d.ts index 12ac223..9d8da4d 100644 --- a/types/plugin.test-d.ts +++ b/types/plugin.test-d.ts @@ -172,6 +172,7 @@ const parseOptions: fastifyCookieStar.CookieSerializeOptions = { sameSite: 'lax', secure: true, signed: true, + partitioned: false, }; expectType(parseOptions); From c761c84966a90166b7059c3d868f2597f354c68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Wed, 8 Nov 2023 20:54:00 +0100 Subject: [PATCH 02/10] add more test --- test/cookie.test.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/cookie.test.js b/test/cookie.test.js index 1875ebc..b05faad 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -93,14 +93,16 @@ test('should set multiple cookies', (t) => { }) test('should set multiple cookies', (t) => { - t.plan(11) + t.plan(12) const fastify = Fastify() fastify.register(plugin) fastify.get('/', (req, reply) => { reply .setCookie('foo', 'foo') - .cookie('bar', 'test') + .cookie('bar', 'test', { + partitioned: true + }) .setCookie('wee', 'woo', { partitioned: true, secure: true @@ -125,6 +127,7 @@ test('should set multiple cookies', (t) => { t.equal(cookies[2].name, 'wee') t.equal(cookies[2].value, 'woo') + t.equal(res.headers['set-cookie'][1], 'bar=test; Partitioned') t.equal(res.headers['set-cookie'][2], 'wee=woo; Secure; Partitioned') }) }) From 17c72307974fe8b0fc89366723ef50cfbd29dfc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Fri, 10 Nov 2023 01:53:45 +0300 Subject: [PATCH 03/10] Update cookie.js Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> --- cookie.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cookie.js b/cookie.js index 493bbdb..9a8a022 100644 --- a/cookie.js +++ b/cookie.js @@ -183,6 +183,8 @@ function serialize (name, val, options) { str += '; Secure' } + // Draft implementation to support Chrome from 2024-Q1 forward. + // See https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#section-2.1 if (opt.partitioned) { str += '; Partitioned' } From 4d1ee3605dab387bcc9a2b0e7e8181b2034162ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sat, 11 Nov 2023 16:23:39 +0100 Subject: [PATCH 04/10] add readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 740b6ca..16e4fbe 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ app.register(cookie, { - More sophisticated cookie signing mechanisms can be implemented by supplying an `Object`. Read more about it in [Custom cookie signer](#custom-cookie-signer). - `parseOptions`: An `Object` to pass as options to [cookie parse](https://github.com/jshttp/cookie#cookieparsestr-options). + - **Note:** The experimental `partitioned` option can be enabled in order to set the non-standard [Partitioned attribute](https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#name-the-partitioned-attribute). The reason for its existence is to support Chrome's [upcoming changes](https://github.com/fastify/fastify-cookie/pull/261#issuecomment-1803234334). ## API From b9036110b60f7d290371e6f4685296cdeafc2316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 12 Nov 2023 02:40:00 +0100 Subject: [PATCH 05/10] import readme --- README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 16e4fbe..0dbbbda 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,90 @@ app.register(cookie, { - An `Array` can be passed if key rotation is desired. Read more about it in [Rotating signing secret](#rotating-secret). - More sophisticated cookie signing mechanisms can be implemented by supplying an `Object`. Read more about it in [Custom cookie signer](#custom-cookie-signer). -- `parseOptions`: An `Object` to pass as options to [cookie parse](https://github.com/jshttp/cookie#cookieparsestr-options). - - **Note:** The experimental `partitioned` option can be enabled in order to set the non-standard [Partitioned attribute](https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#name-the-partitioned-attribute). The reason for its existence is to support Chrome's [upcoming changes](https://github.com/fastify/fastify-cookie/pull/261#issuecomment-1803234334). +- `parseOptions`: An `Object` to modify the serialization of set cookies. + +#### parseOptions + +##### domain + +Specifies the value for the [`Domain` `Set-Cookie` attribute][rfc-6265-5.2.3]. By default, no +domain is set, and most clients will consider the cookie to apply to only the current domain. + +##### encode + +Specifies a function that will be used to encode a cookie's value. Since value of a cookie +has a limited character set (and must be a simple string), this function can be used to encode +a value into a string suited for a cookie's value. + +The default function is the global `encodeURIComponent`, which will encode a JavaScript string +into UTF-8 byte sequences and then URL-encode any that fall outside of the cookie range. + +##### expires + +Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute][rfc-6265-5.2.1]. +By default, no expiration is set, and most clients will consider this a "non-persistent cookie" and +will delete it on a condition like exiting a web browser application. + +**Note:** the [cookie storage model specification][rfc-6265-5.3] states that if both `expires` and +`maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, +so if both are set, they should point to the same date and time. + +##### httpOnly + +Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute][rfc-6265-5.2.6]. When truthy, +the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set. + +**Note:** be careful when setting this to `true`, as compliant clients will not allow client-side +JavaScript to see the cookie in `document.cookie`. + +##### maxAge + +Specifies the `number` (in seconds) to be the value for the [`Max-Age` `Set-Cookie` attribute][rfc-6265-5.2.2]. +The given number will be converted to an integer by rounding down. By default, no maximum age is set. + +**Note:** the [cookie storage model specification][rfc-6265-5.3] states that if both `expires` and +`maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, +so if both are set, they should point to the same date and time. + +##### partitioned + +Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) +attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the +`Partitioned` attribute is not set. + +**Note:** This is an attribute that has not yet been fully standardized, and may change in the future. +This also means many clients may ignore this attribute until they understand it. + +More information about can be found in [the proposal](https://github.com/privacycg/CHIPS). + +##### path + +Specifies the value for the [`Path` `Set-Cookie` attribute][rfc-6265-5.2.4]. By default, the path +is considered the ["default path"][rfc-6265-5.1.4]. + +##### sameSite + +Specifies the `boolean` or `string` to be the value for the [`SameSite` `Set-Cookie` attribute][rfc-6265bis-09-5.4.7]. + + - `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement. + - `false` will not set the `SameSite` attribute. + - `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement. + - `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie. + - `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement. + +More information about the different enforcement levels can be found in +[the specification][rfc-6265bis-09-5.4.7]. + +**Note:** This is an attribute that has not yet been fully standardized, and may change in the future. +This also means many clients may ignore this attribute until they understand it. + +##### secure + +Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute][rfc-6265-5.2.5]. When truthy, +the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. + +**Note:** be careful when setting this to `true`, as compliant clients will not send the cookie back to +the server in the future if the browser does not have an HTTPS connection. ## API From ad49da2aadfd1f09b52bab05d5d676d5148dece4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 12 Nov 2023 02:42:14 +0100 Subject: [PATCH 06/10] add exp --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0dbbbda..7cabcf9 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ The given number will be converted to an integer by rounding down. By default, n `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time. -##### partitioned +##### partitioned (**experimental**: non-standard) Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the From 407a5b1fa0b61e07e8a7ef63ff15a442638d9470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 12 Nov 2023 02:45:56 +0100 Subject: [PATCH 07/10] Revert "add exp" This reverts commit ad49da2aadfd1f09b52bab05d5d676d5148dece4. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7cabcf9..0dbbbda 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ The given number will be converted to an integer by rounding down. By default, n `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time. -##### partitioned (**experimental**: non-standard) +##### partitioned Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the From 98f5fd1ca5e6d68eb59ed8c89fdaa29509739de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 12 Nov 2023 14:37:25 +0300 Subject: [PATCH 08/10] Warning Co-authored-by: Manuel Spigolon --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0dbbbda..c487f07 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-ht attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set. -**Note:** This is an attribute that has not yet been fully standardized, and may change in the future. +⚠️ **Warning:** This is an attribute that has not yet been fully standardized, and may change in the future without reflecting the semver versioning. This also means many clients may ignore this attribute until they understand it. More information about can be found in [the proposal](https://github.com/privacycg/CHIPS). From b8d00f12f2ad54de8874d70cf46f28d753cfd09e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 12 Nov 2023 12:49:24 +0100 Subject: [PATCH 09/10] add links --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c487f07..dd222c0 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ app.register(cookie, { ##### domain -Specifies the value for the [`Domain` `Set-Cookie` attribute][rfc-6265-5.2.3]. By default, no +Specifies the value for the [`Domain` `Set-Cookie` attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3). By default, no domain is set, and most clients will consider the cookie to apply to only the current domain. ##### encode @@ -97,17 +97,17 @@ into UTF-8 byte sequences and then URL-encode any that fall outside of the cooki ##### expires -Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute][rfc-6265-5.2.1]. +Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.1). By default, no expiration is set, and most clients will consider this a "non-persistent cookie" and will delete it on a condition like exiting a web browser application. -**Note:** the [cookie storage model specification][rfc-6265-5.3] states that if both `expires` and +**Note:** the [cookie storage model specification](https://datatracker.ietf.org/doc/html/rfc6265#section-5.3) states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time. ##### httpOnly -Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute][rfc-6265-5.2.6]. When truthy, +Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.6). When truthy, the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set. **Note:** be careful when setting this to `true`, as compliant clients will not allow client-side @@ -115,32 +115,32 @@ JavaScript to see the cookie in `document.cookie`. ##### maxAge -Specifies the `number` (in seconds) to be the value for the [`Max-Age` `Set-Cookie` attribute][rfc-6265-5.2.2]. +Specifies the `number` (in seconds) to be the value for the [`Max-Age` `Set-Cookie` attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2). The given number will be converted to an integer by rounding down. By default, no maximum age is set. -**Note:** the [cookie storage model specification][rfc-6265-5.3] states that if both `expires` and +**Note:** the [cookie storage model specification](https://datatracker.ietf.org/doc/html/rfc6265#section-5.3) states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time. ##### partitioned -Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) +Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#section-2.1) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set. ⚠️ **Warning:** This is an attribute that has not yet been fully standardized, and may change in the future without reflecting the semver versioning. -This also means many clients may ignore this attribute until they understand it. +This also means many clients may ignore the attribute until they understand it. More information about can be found in [the proposal](https://github.com/privacycg/CHIPS). ##### path -Specifies the value for the [`Path` `Set-Cookie` attribute][rfc-6265-5.2.4]. By default, the path -is considered the ["default path"][rfc-6265-5.1.4]. +Specifies the value for the [`Path` `Set-Cookie` attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4). By default, the path +is considered the ["default path"](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4). ##### sameSite -Specifies the `boolean` or `string` to be the value for the [`SameSite` `Set-Cookie` attribute][rfc-6265bis-09-5.4.7]. +Specifies the `boolean` or `string` to be the value for the [`SameSite` `Set-Cookie` attribute](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7). - `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement. - `false` will not set the `SameSite` attribute. @@ -149,14 +149,14 @@ Specifies the `boolean` or `string` to be the value for the [`SameSite` `Set-Coo - `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement. More information about the different enforcement levels can be found in -[the specification][rfc-6265bis-09-5.4.7]. +[the specification](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7). **Note:** This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. ##### secure -Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute][rfc-6265-5.2.5]. When truthy, +Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.5). When truthy, the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. **Note:** be careful when setting this to `true`, as compliant clients will not send the cookie back to From 385831f735d7df5dd18b2f0b8adece74fcdc8195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Wed, 15 Nov 2023 11:25:35 +0100 Subject: [PATCH 10/10] link comment --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index dd222c0..321acf4 100644 --- a/README.md +++ b/README.md @@ -128,8 +128,7 @@ Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](https://datat attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set. -⚠️ **Warning:** This is an attribute that has not yet been fully standardized, and may change in the future without reflecting the semver versioning. -This also means many clients may ignore the attribute until they understand it. +⚠️ **Warning:** [This is an attribute that has not yet been fully standardized](https://github.com/fastify/fastify-cookie/pull/261#issuecomment-1803234334), and may change in the future without reflecting the semver versioning. This also means many clients may ignore the attribute until they understand it. More information about can be found in [the proposal](https://github.com/privacycg/CHIPS).