@eggjs/cookies 3.0.1 → 4.0.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -7
- package/README.zh-CN.md +3 -9
- package/dist/cookie.d.ts +69 -0
- package/dist/cookie.js +77 -0
- package/dist/cookies.d.ts +47 -0
- package/dist/cookies.js +231 -0
- package/dist/error.d.ts +6 -0
- package/dist/error.js +11 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/keygrip.d.ts +14 -0
- package/dist/keygrip.js +98 -0
- package/package.json +27 -48
- package/dist/commonjs/cookie.d.ts +0 -62
- package/dist/commonjs/cookie.js +0 -101
- package/dist/commonjs/cookies.d.ts +0 -43
- package/dist/commonjs/cookies.js +0 -290
- package/dist/commonjs/error.d.ts +0 -3
- package/dist/commonjs/error.js +0 -11
- package/dist/commonjs/index.d.ts +0 -4
- package/dist/commonjs/index.js +0 -21
- package/dist/commonjs/keygrip.d.ts +0 -11
- package/dist/commonjs/keygrip.js +0 -120
- package/dist/commonjs/package.json +0 -3
- package/dist/esm/cookie.d.ts +0 -62
- package/dist/esm/cookie.js +0 -94
- package/dist/esm/cookies.d.ts +0 -43
- package/dist/esm/cookies.js +0 -283
- package/dist/esm/error.d.ts +0 -3
- package/dist/esm/error.js +0 -7
- package/dist/esm/index.d.ts +0 -4
- package/dist/esm/index.js +0 -5
- package/dist/esm/keygrip.d.ts +0 -11
- package/dist/esm/keygrip.js +0 -113
- package/dist/esm/package.json +0 -3
- package/src/cookie.ts +0 -160
- package/src/cookies.ts +0 -333
- package/src/error.ts +0 -6
- package/src/index.ts +0 -4
- package/src/keygrip.ts +0 -129
package/README.md
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
# @eggjs/cookies
|
|
2
2
|
|
|
3
3
|
[![NPM version][npm-image]][npm-url]
|
|
4
|
-
[![build status][ci-image]][ci-url]
|
|
5
|
-
[![Test coverage][codecov-image]][codecov-url]
|
|
6
4
|
[![npm download][download-image]][download-url]
|
|
7
5
|
|
|
8
6
|
[npm-image]: https://img.shields.io/npm/v/@eggjs/cookies.svg?style=flat-square
|
|
9
7
|
[npm-url]: https://npmjs.org/package/@eggjs/cookies
|
|
10
|
-
[ci-image]: https://github.com/eggjs/egg-cookies/actions/workflows/nodejs.yml/badge.svg
|
|
11
|
-
[ci-url]: https://github.com/eggjs/egg-cookies/actions/workflows/nodejs.yml
|
|
12
|
-
[codecov-image]: https://codecov.io/gh/eggjs/egg-cookies/branch/master/graph/badge.svg
|
|
13
|
-
[codecov-url]: https://codecov.io/gh/eggjs/egg-cookies
|
|
14
8
|
[download-image]: https://img.shields.io/npm/dm/@eggjs/cookies.svg?style=flat-square
|
|
15
9
|
[download-url]: https://npmjs.org/package/@eggjs/cookies
|
|
16
10
|
|
|
@@ -53,6 +47,6 @@ cookies.set('foo', longText);
|
|
|
53
47
|
|
|
54
48
|
## Contributors
|
|
55
49
|
|
|
56
|
-
[](https://github.com/eggjs/egg/graphs/contributors)
|
|
57
51
|
|
|
58
52
|
Made with [contributors-img](https://contrib.rocks).
|
package/README.zh-CN.md
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
# @eggjs/cookies
|
|
2
2
|
|
|
3
3
|
[![NPM version][npm-image]][npm-url]
|
|
4
|
-
[![build status][ci-image]][ci-url]
|
|
5
|
-
[![Test coverage][codecov-image]][codecov-url]
|
|
6
4
|
[![npm download][download-image]][download-url]
|
|
7
5
|
|
|
8
6
|
[npm-image]: https://img.shields.io/npm/v/@eggjs/cookies.svg?style=flat-square
|
|
9
7
|
[npm-url]: https://npmjs.org/package/@eggjs/cookies
|
|
10
|
-
[ci-image]: https://github.com/eggjs/egg-cookies/actions/workflows/nodejs.yml/badge.svg
|
|
11
|
-
[ci-url]: https://github.com/eggjs/egg-cookies/actions/workflows/nodejs.yml
|
|
12
|
-
[codecov-image]: https://codecov.io/gh/eggjs/egg-cookies/branch/master/graph/badge.svg
|
|
13
|
-
[codecov-url]: https://codecov.io/gh/eggjs/egg-cookies
|
|
14
8
|
[download-image]: https://img.shields.io/npm/dm/@eggjs/cookies.svg?style=flat-square
|
|
15
9
|
[download-url]: https://npmjs.org/package/@eggjs/cookies
|
|
16
10
|
|
|
@@ -34,8 +28,8 @@ ctx.cookies.set('key', 'value', options);
|
|
|
34
28
|
全局默认配置:
|
|
35
29
|
|
|
36
30
|
- autoChips - `Boolean` 是否开启 [CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips#security_design) 的自动适配方案,
|
|
37
|
-
会自动给 Cookie 新增一个 `_CHIPS-` 为前缀的分区 Cookie,优先读取非分区 Cookie,读取失败则尝试读取 `_CHIPS-` 前缀的同名 Cookie 适配三方 Cookie 禁止逻辑。
|
|
38
|
-
一旦 `cookies.set` 设置 `partitioned=true`,那么会强制忽略 `autoChips` 参数。
|
|
31
|
+
会自动给 Cookie 新增一个 `_CHIPS-` 为前缀的分区 Cookie,优先读取非分区 Cookie,读取失败则尝试读取 `_CHIPS-` 前缀的同名 Cookie 适配三方 Cookie 禁止逻辑。
|
|
32
|
+
一旦 `cookies.set` 设置 `partitioned=true`,那么会强制忽略 `autoChips` 参数。
|
|
39
33
|
|
|
40
34
|
## 设置 cookie
|
|
41
35
|
|
|
@@ -71,6 +65,6 @@ ctx.cookies.set('key', 'value', options);
|
|
|
71
65
|
|
|
72
66
|
## Contributors
|
|
73
67
|
|
|
74
|
-
[](https://github.com/eggjs/egg/graphs/contributors)
|
|
75
69
|
|
|
76
70
|
Made with [contributors-img](https://contrib.rocks).
|
package/dist/cookie.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/cookie.d.ts
|
|
2
|
+
interface CookieSetOptions {
|
|
3
|
+
/**
|
|
4
|
+
* The path for the cookie to be set in
|
|
5
|
+
*/
|
|
6
|
+
path?: string | null;
|
|
7
|
+
/**
|
|
8
|
+
* The domain for the cookie
|
|
9
|
+
*/
|
|
10
|
+
domain?: string | (() => string);
|
|
11
|
+
/**
|
|
12
|
+
* Is overridable
|
|
13
|
+
*/
|
|
14
|
+
overwrite?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Is the same site
|
|
17
|
+
*/
|
|
18
|
+
sameSite?: string | boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Encrypt the cookie's value or not
|
|
21
|
+
*/
|
|
22
|
+
encrypt?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Max age for browsers
|
|
25
|
+
*/
|
|
26
|
+
maxAge?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Expire time
|
|
29
|
+
*/
|
|
30
|
+
expires?: Date;
|
|
31
|
+
/**
|
|
32
|
+
* Is for http only
|
|
33
|
+
*/
|
|
34
|
+
httpOnly?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Encrypt the cookie's value or not
|
|
37
|
+
*/
|
|
38
|
+
secure?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Once `true` and secure set to `true`, ignore the secure error in a none-ssl environment.
|
|
41
|
+
*/
|
|
42
|
+
ignoreSecureError?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Is it signed or not.
|
|
45
|
+
*/
|
|
46
|
+
signed?: boolean | number;
|
|
47
|
+
/**
|
|
48
|
+
* Is it partitioned or not.
|
|
49
|
+
*/
|
|
50
|
+
partitioned?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Remove unpartitioned same name cookie or not.
|
|
53
|
+
*/
|
|
54
|
+
removeUnpartitioned?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The cookie priority.
|
|
57
|
+
*/
|
|
58
|
+
priority?: 'low' | 'medium' | 'high' | 'LOW' | 'MEDIUM' | 'HIGH';
|
|
59
|
+
}
|
|
60
|
+
declare class Cookie {
|
|
61
|
+
name: string;
|
|
62
|
+
value: string;
|
|
63
|
+
readonly attrs: CookieSetOptions;
|
|
64
|
+
constructor(name: string, value?: string | null, attrs?: CookieSetOptions);
|
|
65
|
+
toString(): string;
|
|
66
|
+
toHeader(): string;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { Cookie, CookieSetOptions };
|
package/dist/cookie.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
|
|
3
|
+
//#region src/cookie.ts
|
|
4
|
+
/**
|
|
5
|
+
* RegExp to match field-content in RFC 7230 sec 3.2
|
|
6
|
+
*
|
|
7
|
+
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
|
8
|
+
* field-vchar = VCHAR / obs-text
|
|
9
|
+
* obs-text = %x80-FF
|
|
10
|
+
*/
|
|
11
|
+
const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
|
|
12
|
+
/**
|
|
13
|
+
* RegExp to match Same-Site cookie attribute value.
|
|
14
|
+
* https://en.wikipedia.org/wiki/HTTP_cookie#SameSite_cookie
|
|
15
|
+
*/
|
|
16
|
+
const sameSiteRegExp = /^(?:none|lax|strict)$/i;
|
|
17
|
+
/**
|
|
18
|
+
* RegExp to match Priority cookie attribute value.
|
|
19
|
+
*/
|
|
20
|
+
const PRIORITY_REGEXP = /^(?:low|medium|high)$/i;
|
|
21
|
+
var Cookie = class {
|
|
22
|
+
name;
|
|
23
|
+
value;
|
|
24
|
+
attrs;
|
|
25
|
+
constructor(name, value, attrs) {
|
|
26
|
+
assert(fieldContentRegExp.test(name), "argument name is invalid");
|
|
27
|
+
assert(!value || fieldContentRegExp.test(value), "argument value is invalid");
|
|
28
|
+
this.name = name;
|
|
29
|
+
this.value = value ?? "";
|
|
30
|
+
this.attrs = mergeDefaultAttrs(attrs);
|
|
31
|
+
assert(!this.attrs.path || fieldContentRegExp.test(this.attrs.path), "argument option path is invalid");
|
|
32
|
+
if (typeof this.attrs.domain === "function") this.attrs.domain = this.attrs.domain();
|
|
33
|
+
assert(!this.attrs.domain || fieldContentRegExp.test(this.attrs.domain), "argument option domain is invalid");
|
|
34
|
+
assert(!this.attrs.sameSite || this.attrs.sameSite === true || sameSiteRegExp.test(this.attrs.sameSite), "argument option sameSite is invalid");
|
|
35
|
+
assert(!this.attrs.priority || PRIORITY_REGEXP.test(this.attrs.priority), "argument option priority is invalid");
|
|
36
|
+
if (!value) {
|
|
37
|
+
this.attrs.expires = /* @__PURE__ */ new Date(0);
|
|
38
|
+
this.attrs.maxAge = void 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
toString() {
|
|
42
|
+
return this.name + "=" + this.value;
|
|
43
|
+
}
|
|
44
|
+
toHeader() {
|
|
45
|
+
let header = this.toString();
|
|
46
|
+
const attrs = this.attrs;
|
|
47
|
+
if (attrs.path) header += "; path=" + attrs.path;
|
|
48
|
+
const maxAge = typeof attrs.maxAge === "string" ? parseInt(attrs.maxAge, 10) : attrs.maxAge;
|
|
49
|
+
if (maxAge) {
|
|
50
|
+
header += "; max-age=" + Math.round(maxAge / 1e3);
|
|
51
|
+
attrs.expires = new Date(Date.now() + maxAge);
|
|
52
|
+
}
|
|
53
|
+
if (attrs.expires) header += "; expires=" + attrs.expires.toUTCString();
|
|
54
|
+
if (attrs.domain) header += "; domain=" + attrs.domain;
|
|
55
|
+
if (attrs.priority) header += "; priority=" + attrs.priority.toLowerCase();
|
|
56
|
+
if (attrs.sameSite) header += "; samesite=" + (attrs.sameSite === true ? "strict" : attrs.sameSite.toLowerCase());
|
|
57
|
+
if (attrs.secure) header += "; secure";
|
|
58
|
+
if (attrs.httpOnly) header += "; httponly";
|
|
59
|
+
if (attrs.partitioned) header += "; partitioned";
|
|
60
|
+
return header;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
function mergeDefaultAttrs(attrs) {
|
|
64
|
+
return {
|
|
65
|
+
path: "/",
|
|
66
|
+
httpOnly: true,
|
|
67
|
+
secure: false,
|
|
68
|
+
overwrite: false,
|
|
69
|
+
sameSite: false,
|
|
70
|
+
partitioned: false,
|
|
71
|
+
priority: void 0,
|
|
72
|
+
...attrs
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { Cookie };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Keygrip } from "./keygrip.js";
|
|
2
|
+
import { CookieSetOptions } from "./cookie.js";
|
|
3
|
+
|
|
4
|
+
//#region src/cookies.d.ts
|
|
5
|
+
interface DefaultCookieOptions extends CookieSetOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Auto get and set `_CHIPS-` prefix cookie to adaptation CHIPS mode (The default value is false).
|
|
8
|
+
*/
|
|
9
|
+
autoChips?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface CookieGetOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Whether to sign or not (The default value is true).
|
|
14
|
+
*/
|
|
15
|
+
signed?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Encrypt the cookie's value or not (The default value is false).
|
|
18
|
+
*/
|
|
19
|
+
encrypt?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* cookies for egg
|
|
23
|
+
* extend pillarjs/cookies, add encrypt and decrypt
|
|
24
|
+
*/
|
|
25
|
+
declare class Cookies {
|
|
26
|
+
#private;
|
|
27
|
+
readonly ctx: Record<string, any>;
|
|
28
|
+
readonly app: Record<string, any>;
|
|
29
|
+
readonly secure: boolean;
|
|
30
|
+
constructor(ctx: Record<string, any>, keys: string[], defaultCookieOptions?: DefaultCookieOptions);
|
|
31
|
+
get keys(): Keygrip;
|
|
32
|
+
/**
|
|
33
|
+
* get cookie value by name
|
|
34
|
+
* @param {String} name - cookie's name
|
|
35
|
+
* @param {Object} opts - cookies' options
|
|
36
|
+
* - {Boolean} signed - default to true
|
|
37
|
+
* - {Boolean} encrypt - default to false
|
|
38
|
+
* @return {String} value - cookie's value
|
|
39
|
+
*/
|
|
40
|
+
get(name: string, opts?: CookieGetOptions): string | undefined;
|
|
41
|
+
_get(name: string, opts: CookieGetOptions): string | undefined;
|
|
42
|
+
set(name: string, value: string | null, opts?: CookieSetOptions): this;
|
|
43
|
+
isSameSiteNoneCompatible(userAgent: string): boolean;
|
|
44
|
+
isPartitionedCompatible(userAgent: string): boolean;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { CookieGetOptions, Cookies, DefaultCookieOptions };
|
package/dist/cookies.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Keygrip } from "./keygrip.js";
|
|
2
|
+
import { Cookie } from "./cookie.js";
|
|
3
|
+
import { CookieError } from "./error.js";
|
|
4
|
+
import assert from "node:assert";
|
|
5
|
+
import { base64decode, base64encode } from "utility";
|
|
6
|
+
import { isSameSiteNoneCompatible } from "should-send-same-site-none";
|
|
7
|
+
|
|
8
|
+
//#region src/cookies.ts
|
|
9
|
+
const keyCache = /* @__PURE__ */ new Map();
|
|
10
|
+
/**
|
|
11
|
+
* cookies for egg
|
|
12
|
+
* extend pillarjs/cookies, add encrypt and decrypt
|
|
13
|
+
*/
|
|
14
|
+
var Cookies = class {
|
|
15
|
+
#keysArray;
|
|
16
|
+
#keys;
|
|
17
|
+
#defaultCookieOptions;
|
|
18
|
+
#autoChips;
|
|
19
|
+
ctx;
|
|
20
|
+
app;
|
|
21
|
+
secure;
|
|
22
|
+
#parseChromiumResult;
|
|
23
|
+
constructor(ctx, keys, defaultCookieOptions) {
|
|
24
|
+
this.#keysArray = keys;
|
|
25
|
+
this.#defaultCookieOptions = defaultCookieOptions;
|
|
26
|
+
this.#autoChips = defaultCookieOptions?.autoChips;
|
|
27
|
+
this.ctx = ctx;
|
|
28
|
+
this.secure = this.ctx.secure;
|
|
29
|
+
this.app = ctx.app;
|
|
30
|
+
}
|
|
31
|
+
get keys() {
|
|
32
|
+
if (!this.#keys) {
|
|
33
|
+
assert(Array.isArray(this.#keysArray), ".keys required for encrypt/sign cookies");
|
|
34
|
+
const cache = keyCache.get(this.#keysArray);
|
|
35
|
+
if (cache) this.#keys = cache;
|
|
36
|
+
else {
|
|
37
|
+
this.#keys = new Keygrip(this.#keysArray);
|
|
38
|
+
keyCache.set(this.#keysArray, this.#keys);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return this.#keys;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* get cookie value by name
|
|
45
|
+
* @param {String} name - cookie's name
|
|
46
|
+
* @param {Object} opts - cookies' options
|
|
47
|
+
* - {Boolean} signed - default to true
|
|
48
|
+
* - {Boolean} encrypt - default to false
|
|
49
|
+
* @return {String} value - cookie's value
|
|
50
|
+
*/
|
|
51
|
+
get(name, opts = {}) {
|
|
52
|
+
let value = this._get(name, opts);
|
|
53
|
+
if (value === void 0 && this.#autoChips) value = this._get(this.#formatChipsCookieName(name), opts);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
_get(name, opts) {
|
|
57
|
+
const signed = computeSigned(opts);
|
|
58
|
+
const header = this.ctx.get("cookie");
|
|
59
|
+
if (!header) return;
|
|
60
|
+
const match = header.match(getPattern(name));
|
|
61
|
+
if (!match) return;
|
|
62
|
+
let value = match[1];
|
|
63
|
+
if (!opts.encrypt && !signed) return value;
|
|
64
|
+
if (signed) {
|
|
65
|
+
const sigName = name + ".sig";
|
|
66
|
+
const sigValue = this.get(sigName, { signed: false });
|
|
67
|
+
if (!sigValue) return;
|
|
68
|
+
const raw = name + "=" + value;
|
|
69
|
+
const index = this.keys.verify(raw, sigValue);
|
|
70
|
+
if (index < 0) {
|
|
71
|
+
this.set(sigName, null, {
|
|
72
|
+
path: "/",
|
|
73
|
+
signed: false,
|
|
74
|
+
overwrite: true
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (index > 0) this.set(sigName, this.keys.sign(raw), {
|
|
79
|
+
signed: false,
|
|
80
|
+
overwrite: true
|
|
81
|
+
});
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
value = base64decode(value, true, "buffer");
|
|
85
|
+
const res = this.keys.decrypt(value);
|
|
86
|
+
return res ? res.value.toString() : void 0;
|
|
87
|
+
}
|
|
88
|
+
set(name, value, opts) {
|
|
89
|
+
opts = {
|
|
90
|
+
...this.#defaultCookieOptions,
|
|
91
|
+
...opts
|
|
92
|
+
};
|
|
93
|
+
const signed = computeSigned(opts);
|
|
94
|
+
const shouldIgnoreSecureError = opts && opts.ignoreSecureError;
|
|
95
|
+
value = value || "";
|
|
96
|
+
if (!shouldIgnoreSecureError) {
|
|
97
|
+
if (!this.secure && opts.secure) throw new CookieError("Cannot send secure cookie over unencrypted connection");
|
|
98
|
+
}
|
|
99
|
+
let headers = this.ctx.response.get("set-cookie") || [];
|
|
100
|
+
if (!Array.isArray(headers)) headers = [headers];
|
|
101
|
+
if (opts.encrypt) value = value && base64encode(this.keys.encrypt(value), true);
|
|
102
|
+
if (value.length > 4093) this.app.emit("cookieLimitExceed", {
|
|
103
|
+
name,
|
|
104
|
+
value,
|
|
105
|
+
ctx: this.ctx
|
|
106
|
+
});
|
|
107
|
+
const userAgent = this.ctx.get("user-agent");
|
|
108
|
+
let isSameSiteNone = false;
|
|
109
|
+
let autoChips = !opts.partitioned && this.#autoChips;
|
|
110
|
+
if (opts.sameSite && typeof opts.sameSite === "string" && opts.sameSite.toLowerCase() === "none") {
|
|
111
|
+
isSameSiteNone = true;
|
|
112
|
+
if (opts.secure === false || !this.secure || userAgent && !this.isSameSiteNoneCompatible(userAgent)) {
|
|
113
|
+
opts.sameSite = false;
|
|
114
|
+
isSameSiteNone = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (autoChips || opts.partitioned) {
|
|
118
|
+
if (!isSameSiteNone || opts.secure === false || !this.secure || userAgent && !this.isPartitionedCompatible(userAgent)) {
|
|
119
|
+
autoChips = false;
|
|
120
|
+
opts.partitioned = false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (opts.partitioned && opts.removeUnpartitioned) {
|
|
124
|
+
if (opts.overwrite) {
|
|
125
|
+
opts.overwrite = false;
|
|
126
|
+
headers = ignoreCookiesByName(headers, name);
|
|
127
|
+
}
|
|
128
|
+
const removeCookieOpts = {
|
|
129
|
+
...opts,
|
|
130
|
+
partitioned: false
|
|
131
|
+
};
|
|
132
|
+
const removeUnpartitionedCookie = new Cookie(name, "", removeCookieOpts);
|
|
133
|
+
if (opts.secure === void 0) removeUnpartitionedCookie.attrs.secure = this.secure;
|
|
134
|
+
headers = pushCookie(headers, removeUnpartitionedCookie);
|
|
135
|
+
if (signed) {
|
|
136
|
+
removeUnpartitionedCookie.name += ".sig";
|
|
137
|
+
headers = ignoreCookiesByNameAndPath(headers, removeUnpartitionedCookie.name, removeUnpartitionedCookie.attrs.path);
|
|
138
|
+
headers = pushCookie(headers, removeUnpartitionedCookie);
|
|
139
|
+
}
|
|
140
|
+
} else if (autoChips) {
|
|
141
|
+
const newCookieName = this.#formatChipsCookieName(name);
|
|
142
|
+
const newCookieOpts = {
|
|
143
|
+
...opts,
|
|
144
|
+
partitioned: true
|
|
145
|
+
};
|
|
146
|
+
const newPartitionedCookie = new Cookie(newCookieName, value, newCookieOpts);
|
|
147
|
+
if (opts.secure === void 0) newPartitionedCookie.attrs.secure = this.secure;
|
|
148
|
+
headers = pushCookie(headers, newPartitionedCookie);
|
|
149
|
+
if (signed) {
|
|
150
|
+
newPartitionedCookie.value = value && this.keys.sign(newPartitionedCookie.toString());
|
|
151
|
+
newPartitionedCookie.name += ".sig";
|
|
152
|
+
headers = ignoreCookiesByNameAndPath(headers, newPartitionedCookie.name, newPartitionedCookie.attrs.path);
|
|
153
|
+
headers = pushCookie(headers, newPartitionedCookie);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const cookie = new Cookie(name, value, opts);
|
|
157
|
+
if (opts.secure === void 0) cookie.attrs.secure = this.secure;
|
|
158
|
+
headers = pushCookie(headers, cookie);
|
|
159
|
+
if (signed) {
|
|
160
|
+
cookie.value = value && this.keys.sign(cookie.toString());
|
|
161
|
+
cookie.name += ".sig";
|
|
162
|
+
headers = pushCookie(headers, cookie);
|
|
163
|
+
}
|
|
164
|
+
this.ctx.set("set-cookie", headers);
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
#formatChipsCookieName(name) {
|
|
168
|
+
return `_CHIPS-${name}`;
|
|
169
|
+
}
|
|
170
|
+
#parseChromiumAndMajorVersion(userAgent) {
|
|
171
|
+
if (!this.#parseChromiumResult) this.#parseChromiumResult = parseChromiumAndMajorVersion(userAgent);
|
|
172
|
+
return this.#parseChromiumResult;
|
|
173
|
+
}
|
|
174
|
+
isSameSiteNoneCompatible(userAgent) {
|
|
175
|
+
const result = this.#parseChromiumAndMajorVersion(userAgent);
|
|
176
|
+
if (result.chromium) return result.majorVersion >= 80;
|
|
177
|
+
return isSameSiteNoneCompatible(userAgent);
|
|
178
|
+
}
|
|
179
|
+
isPartitionedCompatible(userAgent) {
|
|
180
|
+
const result = this.#parseChromiumAndMajorVersion(userAgent);
|
|
181
|
+
if (result.chromium) return result.majorVersion >= 118;
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
function parseChromiumAndMajorVersion(userAgent) {
|
|
186
|
+
const m = /Chrom[^ /]{1,100}\/(\d{1,100}?)\./.exec(userAgent);
|
|
187
|
+
if (!m) return {
|
|
188
|
+
chromium: false,
|
|
189
|
+
majorVersion: 0
|
|
190
|
+
};
|
|
191
|
+
return {
|
|
192
|
+
chromium: true,
|
|
193
|
+
majorVersion: parseInt(m[1])
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const _patternCache = /* @__PURE__ */ new Map();
|
|
197
|
+
function getPattern(name) {
|
|
198
|
+
const cache = _patternCache.get(name);
|
|
199
|
+
if (cache) return cache;
|
|
200
|
+
const reg = /* @__PURE__ */ new RegExp("(?:^|;) *" + name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") + "=([^;]*)");
|
|
201
|
+
_patternCache.set(name, reg);
|
|
202
|
+
return reg;
|
|
203
|
+
}
|
|
204
|
+
function computeSigned(opts) {
|
|
205
|
+
if (opts.encrypt) return false;
|
|
206
|
+
return opts.signed !== false;
|
|
207
|
+
}
|
|
208
|
+
function pushCookie(cookies, cookie) {
|
|
209
|
+
if (cookie.attrs.overwrite) cookies = ignoreCookiesByName(cookies, cookie.name);
|
|
210
|
+
cookies.push(cookie.toHeader());
|
|
211
|
+
return cookies;
|
|
212
|
+
}
|
|
213
|
+
function ignoreCookiesByName(cookies, name) {
|
|
214
|
+
const prefix = `${name}=`;
|
|
215
|
+
return cookies.filter((c) => !c.startsWith(prefix));
|
|
216
|
+
}
|
|
217
|
+
function ignoreCookiesByNameAndPath(cookies, name, path) {
|
|
218
|
+
if (!path) return ignoreCookiesByName(cookies, name);
|
|
219
|
+
const prefix = `${name}=`;
|
|
220
|
+
const includedPath = `; path=${path};`;
|
|
221
|
+
const endsWithPath = `; path=${path}`;
|
|
222
|
+
return cookies.filter((c) => {
|
|
223
|
+
if (c.startsWith(prefix)) {
|
|
224
|
+
if (c.includes(includedPath) || c.endsWith(endsWithPath)) return false;
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
//#endregion
|
|
231
|
+
export { Cookies };
|
package/dist/error.d.ts
ADDED
package/dist/error.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/error.ts
|
|
2
|
+
var CookieError = class extends Error {
|
|
3
|
+
constructor(message, options) {
|
|
4
|
+
super(message, options);
|
|
5
|
+
this.name = this.constructor.name;
|
|
6
|
+
if ("captureStackTrace" in Error) Error.captureStackTrace(this, this.constructor);
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
export { CookieError };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Keygrip } from "./keygrip.js";
|
|
2
|
+
import { Cookie, CookieSetOptions } from "./cookie.js";
|
|
3
|
+
import { CookieGetOptions, Cookies, DefaultCookieOptions } from "./cookies.js";
|
|
4
|
+
import { CookieError } from "./error.js";
|
|
5
|
+
export { Cookie, CookieError, CookieGetOptions, CookieSetOptions, Cookies, DefaultCookieOptions, Keygrip };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/keygrip.d.ts
|
|
2
|
+
declare class Keygrip {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(keys: string[]);
|
|
5
|
+
encrypt(data: string, key?: string): Buffer<ArrayBuffer>;
|
|
6
|
+
decrypt(data: string | Buffer): {
|
|
7
|
+
value: Buffer;
|
|
8
|
+
index: number;
|
|
9
|
+
} | false;
|
|
10
|
+
sign(data: string | Buffer, key?: string): string;
|
|
11
|
+
verify(data: string, digest: string): number;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { Keygrip };
|
package/dist/keygrip.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { debuglog } from "node:util";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
|
|
5
|
+
//#region src/keygrip.ts
|
|
6
|
+
const debug = debuglog("egg/cookies:keygrip");
|
|
7
|
+
const KEY_LEN = 32;
|
|
8
|
+
const IV_SIZE = 16;
|
|
9
|
+
const passwordCache = /* @__PURE__ */ new Map();
|
|
10
|
+
const replacer = {
|
|
11
|
+
"/": "_",
|
|
12
|
+
"+": "-",
|
|
13
|
+
"=": ""
|
|
14
|
+
};
|
|
15
|
+
function constantTimeCompare(a, b) {
|
|
16
|
+
if (a.length !== b.length) return false;
|
|
17
|
+
return crypto.timingSafeEqual(a, b);
|
|
18
|
+
}
|
|
19
|
+
var Keygrip = class {
|
|
20
|
+
#keys;
|
|
21
|
+
#hash = "sha256";
|
|
22
|
+
#cipher = "aes-256-cbc";
|
|
23
|
+
constructor(keys) {
|
|
24
|
+
assert(Array.isArray(keys) && keys.length > 0, "keys must be provided and should be an array");
|
|
25
|
+
this.#keys = keys;
|
|
26
|
+
}
|
|
27
|
+
encrypt(data, key) {
|
|
28
|
+
key = key || this.#keys[0];
|
|
29
|
+
const password = keyToPassword(key);
|
|
30
|
+
const cipher = crypto.createCipheriv(this.#cipher, password.key, password.iv);
|
|
31
|
+
return crypt(cipher, data);
|
|
32
|
+
}
|
|
33
|
+
decrypt(data) {
|
|
34
|
+
const keys = this.#keys;
|
|
35
|
+
for (let i = 0; i < keys.length; i++) {
|
|
36
|
+
const value = this.#decryptByKey(data, keys[i]);
|
|
37
|
+
if (value !== false) return {
|
|
38
|
+
value,
|
|
39
|
+
index: i
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
#decryptByKey(data, key) {
|
|
45
|
+
try {
|
|
46
|
+
const password = keyToPassword(key);
|
|
47
|
+
const cipher = crypto.createDecipheriv(this.#cipher, password.key, password.iv);
|
|
48
|
+
return crypt(cipher, data);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
debug("crypt error: %s", err);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
sign(data, key) {
|
|
55
|
+
key = key || this.#keys[0];
|
|
56
|
+
return crypto.createHmac(this.#hash, key).update(data).digest("base64").replace(/\/|\+|=/g, (x) => {
|
|
57
|
+
return replacer[x];
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
verify(data, digest) {
|
|
61
|
+
const keys = this.#keys;
|
|
62
|
+
for (let i = 0; i < keys.length; i++) {
|
|
63
|
+
const key = keys[i];
|
|
64
|
+
if (constantTimeCompare(Buffer.from(digest), Buffer.from(this.sign(data, key)))) {
|
|
65
|
+
debug("data %s match key %s, index: %d", data, key, i);
|
|
66
|
+
return i;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return -1;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function crypt(cipher, data) {
|
|
73
|
+
const text = Buffer.isBuffer(data) ? cipher.update(data) : cipher.update(data, "utf-8");
|
|
74
|
+
const pad = cipher.final();
|
|
75
|
+
return Buffer.concat([text, pad]);
|
|
76
|
+
}
|
|
77
|
+
function keyToPassword(key) {
|
|
78
|
+
if (passwordCache.has(key)) return passwordCache.get(key);
|
|
79
|
+
const bytes = Buffer.alloc(KEY_LEN + IV_SIZE);
|
|
80
|
+
let lastHash = null, nBytes = 0;
|
|
81
|
+
while (nBytes < bytes.length) {
|
|
82
|
+
const hash = crypto.createHash("md5");
|
|
83
|
+
if (lastHash) hash.update(lastHash);
|
|
84
|
+
hash.update(key);
|
|
85
|
+
lastHash = hash.digest();
|
|
86
|
+
lastHash.copy(bytes, nBytes);
|
|
87
|
+
nBytes += lastHash.length;
|
|
88
|
+
}
|
|
89
|
+
const password = {
|
|
90
|
+
key: bytes.subarray(0, KEY_LEN),
|
|
91
|
+
iv: bytes.subarray(KEY_LEN, bytes.length)
|
|
92
|
+
};
|
|
93
|
+
passwordCache.set(key, password);
|
|
94
|
+
return password;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
export { Keygrip };
|