Skip to content

Commit

Permalink
feat: add removeUnpartitioned property (#9)
Browse files Browse the repository at this point in the history
* feat: add removeUnpartitioned

* chore: update readme
  • Loading branch information
czy88840616 authored Dec 28, 2023
1 parent 971f5b5 commit 1c94e4b
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Set a cookie through `cookies.set(key, value, options)`. The parameters supporte
- signed - Whether `Boolean` needs to sign the cookie or not, the signed parameter needs to be passed when cooperating with get. At this time, the front-end cannot tamper with the cookie. The default is true.
- encrypt - Whether `Boolean` needs to encrypt the cookie, you need to pass the encrypt parameter when using get. At this time, the front-end cannot read the real cookie value, and the default is false.
- partitioned - Whether `Boolean` sets cookies for independent partition state ([CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips)). Note that this configuration will only take effect if 'secure' is true.
- removeUnpartitioned - `Boolean` Whether to delete the cookie with the same name in the non-independent partition state. Note that this configuration will only take effect when `partitioned` is true.
- priority - `String` sets the [priority of the cookie](https://developer.chrome.com/blog/new-in-devtools-81?hl=zh-cn#cookiepriority), the optional value is `Low` , `Medium`, `High`, only valid for Chrome >= 81 version.

## Read cookie
Expand Down
1 change: 1 addition & 0 deletions README.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ ctx.cookies.get('foo', { encrypt: true });
- signed - `Boolean` 是否需要对 cookie 进行签名,需要配合 get 时传递 signed 参数,此时前端无法篡改这个 cookie,默认为 true。
- encrypt - `Boolean` 是否需要对 cookie 进行加密,需要配合 get 时传递 encrypt 参数,此时前端无法读到真实的 cookie 值,默认为 false。
- partitioned - `Boolean` 是否设置独立分区状态([CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips))的 Cookie。注意,只有 `secure` 为 true 的时候此配置才会生效。
- removeUnpartitioned - `Boolean` 是否删除非独立分区状态的同名 cookie。注意,只有 `partitioned` 为 true 的时候此配置才会生效。
- priority - `String` 设置 cookie 的 [优先级](https://developer.chrome.com/blog/new-in-devtools-81?hl=zh-cn#cookiepriority),可选值为 `Low``Medium``High`,仅对 Chrome >= 81 版本有效。

## 读取 cookie
Expand Down
31 changes: 30 additions & 1 deletion src/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,30 @@ export class Cookies {
}
}

// remove unpartitioned same name cookie first
if (opts.partitioned && opts.removeUnpartitioned) {
const overwrite = opts.overwrite;
if (overwrite) {
opts.overwrite = false;
headers = ignoreCookiesByName(headers, name);
}
const removeCookieOpts = Object.assign({}, opts, {
partitioned: false,
});
const removeUnpartitionedCookie = new Cookie(name, '', removeCookieOpts);
// if user not set secure, reset secure to ctx.secure
if (opts.secure === undefined)
removeUnpartitionedCookie.attrs.secure = this.secure;

headers = pushCookie(headers, removeUnpartitionedCookie);
// signed
if (signed) {
removeUnpartitionedCookie.name += '.sig';
headers = ignoreCookiesByName(headers, removeUnpartitionedCookie.name);
headers = pushCookie(headers, removeUnpartitionedCookie);
}
}

if (opts.priority) {
if (!userAgent || (userAgent && !this.isPriorityCompatible(userAgent))) {
// ignore priority when not secure or incompatible clients
Expand Down Expand Up @@ -253,12 +277,17 @@ function computeSigned(opts) {

function pushCookie(cookies, cookie) {
if (cookie.attrs.overwrite) {
cookies = cookies.filter(c => !c.startsWith(cookie.name + '='));
cookies = ignoreCookiesByName(cookies, cookie.name);
}
cookies.push(cookie.toHeader());
return cookies;
}

function ignoreCookiesByName(cookies, name) {
const prefix = `${name}=`;
return cookies.filter(c => !c.startsWith(prefix));
}

export function urlSafeEncode(encode: string): string {
return encode.replace(/\+/g, '-').replace(/\//g, '_');
}
Expand Down
4 changes: 4 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,8 @@ export interface CookieSetOptions {
* If this is true, Cookies from embedded sites will be partitioned and only readable from the same top level site from which it was created.
*/
partitioned?: boolean;
/**
* Remove unpartitioned same name cookie or not.
*/
removeUnpartitioned?: boolean;
}
46 changes: 46 additions & 0 deletions test/cookies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,5 +557,51 @@ describe('test/cookies.test.ts', () => {
assert(str.includes('; path=/; httponly'));
}
});

it('should remove unpartitioned property first', () => {
const cookies = CreateCookie({
secure: true,
headers: {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.3945.29 Safari/537.36',
},
}, { secure: true }, { partitioned: true, removeUnpartitioned: true });
const opts = {
signed: 1,
} as any;
cookies.set('foo', 'hello', opts);

assert(opts.signed === 1);
assert(opts.secure === undefined);
const headers = cookies.ctx.response.headers['set-cookie'];
// console.log(headers);
assert.equal(headers.length, 4);
assert.equal(headers[0], 'foo=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure; httponly');
assert.equal(headers[1], 'foo.sig=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure; httponly');
assert.equal(headers[2], 'foo=hello; path=/; secure; httponly; partitioned');
assert.equal(headers[3], 'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; secure; httponly; partitioned');
});

it('should remove unpartitioned property first with overwrite = true', () => {
const cookies = CreateCookie({
secure: true,
headers: {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.3945.29 Safari/537.36',
},
}, { secure: true }, { partitioned: true, removeUnpartitioned: true, overwrite: true });
const opts = {
signed: 1,
} as any;
cookies.set('foo', 'hello2222', opts);
cookies.set('foo', 'hello', opts);

assert(opts.signed === 1);
assert(opts.secure === undefined);
const headers = cookies.ctx.response.headers['set-cookie'];
assert.equal(headers.length, 4);
assert.equal(headers[0], 'foo=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure; httponly');
assert.equal(headers[1], 'foo.sig=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure; httponly');
assert.equal(headers[2], 'foo=hello; path=/; secure; httponly; partitioned');
assert.equal(headers[3], 'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; secure; httponly; partitioned');
});
});
});

0 comments on commit 1c94e4b

Please sign in to comment.