@yongdall/user-cookie 0.6.0 → 0.6.2

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/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@yongdall/user-cookie",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "dependencies": {
5
- "@yongdall/http": "^0.5.0",
6
- "@yongdall/core": "^0.5.2"
5
+ "@yongdall/http": "^0.6.2",
6
+ "@yongdall/core": "^0.6.2",
7
+ "@yongdall/user-status-token": "^0.6.2"
7
8
  }
8
9
  }
package/yongdall/user.mjs CHANGED
@@ -1,76 +1,17 @@
1
- import { existUser, useSalt } from "@yongdall/core";
1
+ import { existUser } from "@yongdall/core";
2
+ import * as UserStatusToken from "@yongdall/user-status-token";
2
3
  import { k99Context } from "@yongdall/http";
3
4
 
4
- //#region plugins/user-cookie/yongdall/user.mjs
5
- /** @import { UserManager } from '@yongdall/core' */
5
+ //#region plugins/user-cookie/yongdall/user.mts
6
6
  const userLoggedCookie = "user-logged-token";
7
7
  const cookieTime = 1440 * 60 * 1e3;
8
- /**
9
- * 使用 HMAC-SHA256 对一个或多个值进行哈希,使用 salt 作为密钥。
10
- * 输出为 base64url 格式。
11
- *
12
- * @param {string | ArrayBuffer | ArrayBufferView<ArrayBuffer>} salt
13
- * @param {string | number} value
14
- * @param {...(string | number)} values
15
- * @returns {Promise<string>}
16
- */
17
- async function hash(salt, value, ...values) {
18
- /** @type {ArrayBuffer | ArrayBufferView<ArrayBuffer>} */
19
- let keyBuffer;
20
- if (salt instanceof ArrayBuffer || ArrayBuffer.isView(salt)) keyBuffer = salt;
21
- else if (typeof salt === "string") keyBuffer = new TextEncoder().encode(salt);
22
- else throw new Error("`salt` 参数必须为字符串或 Uint8Array");
23
- const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, {
24
- name: "HMAC",
25
- hash: "SHA-256"
26
- }, false, ["sign"]);
27
- /** @type {string[]} */
28
- let data = [];
29
- if (typeof value === "number") data.push(String(value));
30
- else if (typeof value === "string") data.push(JSON.stringify(value).slice(1, -1));
31
- else throw new Error("`value` 参数必须为字符串或数字");
32
- for (const v of values) if (typeof v === "number") data.push(String(v));
33
- else if (typeof v === "string") data.push(JSON.stringify(v).slice(1, -1));
34
- const messageBuffer = new TextEncoder().encode(data.join("\n"));
35
- const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageBuffer);
36
- return btoa(String.fromCharCode(...new Uint8Array(signature))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
37
- }
38
- /**
39
- *
40
- * @param {string} cookie
41
- * @returns {Promise<[string, boolean]?>}
42
- */
43
- async function parseUserCookie(cookie) {
44
- if (typeof cookie !== "string") return null;
45
- const list = cookie.split(".");
46
- if (list.length !== 3) return null;
47
- const timestamp = Number(list[0]);
48
- if (!Number.isSafeInteger(timestamp)) return null;
49
- const time = Date.now() - timestamp;
50
- if (time > cookieTime) return null;
51
- if (await hash(await useSalt(), list[1], list[0]) !== list[2]) return null;
52
- return [list[1], time > cookieTime * 2 / 3];
53
- }
54
- /**
55
- *
56
- * @param {string} user
57
- * @returns {Promise<string>}
58
- */
59
- async function toUserCookie(user) {
60
- const timestamp = Date.now();
61
- return [
62
- timestamp,
63
- user,
64
- await hash(await useSalt(), user, timestamp)
65
- ].join(".");
66
- }
67
- /** @type {UserManager['get']} */
68
8
  const get = async () => {
69
9
  const ctx = k99Context();
70
10
  if (!ctx) return null;
11
+ if (ctx.requestHeaders.get("X-Cookie-Auth")?.trim().toLowerCase() === "disabled") return null;
71
12
  const cookie = ctx.cookies[userLoggedCookie];
72
13
  if (!cookie) return null;
73
- const userInfo = await parseUserCookie(cookie);
14
+ const userInfo = await UserStatusToken.parse(cookie, cookieTime);
74
15
  if (!userInfo) {
75
16
  ctx.clearCookie(userLoggedCookie, {
76
17
  httpOnly: true,
@@ -78,47 +19,35 @@ const get = async () => {
78
19
  });
79
20
  return null;
80
21
  }
81
- const [userId, reset] = userInfo;
22
+ const [userId, percent] = userInfo;
82
23
  if (!existUser(userId)) return null;
83
- if (reset) ctx.setCookie(userLoggedCookie, await toUserCookie(userId), {
24
+ if (percent > 2 / 3) ctx.setCookie(userLoggedCookie, await UserStatusToken.create(userId), {
84
25
  httpOnly: true,
85
26
  path: "/"
86
27
  });
87
28
  return userId;
88
29
  };
89
- /**
90
- *
91
- * @param {string} userId
92
- * @returns
93
- */
94
- /** @type {UserManager['set']} */
95
30
  const set = async (userId) => {
96
31
  if (!userId) return null;
97
32
  const ctx = k99Context();
98
33
  if (!ctx) return null;
99
- ctx.setCookie(userLoggedCookie, await toUserCookie(userId), {
34
+ ctx.setCookie(userLoggedCookie, await UserStatusToken.create(userId), {
100
35
  httpOnly: true,
101
36
  path: "/"
102
37
  });
103
- return userId;
38
+ return true;
104
39
  };
105
- /**
106
- *
107
- * @param {string} userId
108
- * @returns
109
- */
110
- /** @type {UserManager['login']} */
111
40
  const login = async (userId) => {
112
41
  if (!userId) return null;
113
42
  const ctx = k99Context();
114
43
  if (!ctx) return null;
115
- ctx.setCookie(userLoggedCookie, await toUserCookie(userId), {
44
+ if (ctx.requestHeaders.get("X-Cookie-Auth")?.trim().toLowerCase() === "disabled") return null;
45
+ ctx.setCookie(userLoggedCookie, await UserStatusToken.create(userId), {
116
46
  httpOnly: true,
117
47
  path: "/"
118
48
  });
119
- return userId;
49
+ return true;
120
50
  };
121
- /** @type {UserManager['exit']} */
122
51
  const exit = () => {
123
52
  const ctx = k99Context();
124
53
  if (!ctx) return null;
@@ -1 +1 @@
1
- {"version":3,"file":"user.mjs","names":[],"sources":["../../../plugins/user-cookie/yongdall/user.mjs"],"sourcesContent":["/** @import { UserManager } from '@yongdall/core' */\nimport { existUser, useSalt } from '@yongdall/core';\n\nimport { k99Context } from '@yongdall/http';\n\nconst userLoggedCookie = 'user-logged-token';\nconst cookieTime = 24 * 60 * 60 * 1000;\n\n\n\n/**\n * 使用 HMAC-SHA256 对一个或多个值进行哈希,使用 salt 作为密钥。\n * 输出为 base64url 格式。\n *\n * @param {string | ArrayBuffer | ArrayBufferView<ArrayBuffer>} salt\n * @param {string | number} value\n * @param {...(string | number)} values\n * @returns {Promise<string>}\n */\nasync function hash(salt, value, ...values) {\n\t// 1. 准备密钥(salt)\n\t/** @type {ArrayBuffer | ArrayBufferView<ArrayBuffer>} */\n\tlet keyBuffer;\n\tif (salt instanceof ArrayBuffer || ArrayBuffer.isView(salt)) {\n\t\tkeyBuffer = salt;\n\t} else if (typeof salt === 'string') {\n\t\tkeyBuffer = new TextEncoder().encode(salt);\n\t} else {\n\t\tthrow new Error('`salt` 参数必须为字符串或 Uint8Array');\n\t}\n\n\t// 2. 导入 HMAC 密钥\n\tconst cryptoKey = await crypto.subtle.importKey(\n\t\t'raw',\n\t\tkeyBuffer,\n\t\t{ name: 'HMAC', hash: 'SHA-256' },\n\t\tfalse,\n\t\t['sign']\n\t);\n\n\t// 3. 构造输入数据\n\t/** @type {string[]} */\n\tlet data = [];\n\n\t// 处理第一个 value\n\tif (typeof value === 'number') {\n\t\tdata.push(String(value));\n\t} else if (typeof value === 'string') {\n\t\t// JSON.stringify(value).slice(1, -1) 相当于去掉引号,等价于直接编码字符串内容\n\t\tdata.push(JSON.stringify(value).slice(1, -1));\n\t} else {\n\t\tthrow new Error('`value` 参数必须为字符串或数字');\n\t}\n\n\t// 处理后续 values\n\tfor (const v of values) {\n\t\tif (typeof v === 'number') {\n\t\t\tdata.push(String(v));\n\t\t} else if (typeof v === 'string') {\n\t\t\tdata.push(JSON.stringify(v).slice(1, -1));\n\t\t}\n\t}\n\n\t// 合并所有数据\n\tconst messageBuffer = new TextEncoder().encode(data.join('\\n'));\n\n\t// 4. 生成 HMAC\n\tconst signature = await crypto.subtle.sign('HMAC', cryptoKey, messageBuffer);\n\n\t// 5. 转为 base64url 格式\n\tconst base64 = btoa(String.fromCharCode(...new Uint8Array(signature)));\n\treturn base64\n\t\t.replace(/\\+/g, '-')\n\t\t.replace(/\\//g, '_')\n\t\t.replace(/=+$/, ''); // 移除填充\n}\n/**\n * \n * @param {string} cookie \n * @returns {Promise<[string, boolean]?>}\n */\nasync function parseUserCookie(cookie) {\n\tif (typeof cookie !== 'string') { return null; }\n\tconst list = cookie.split('.');\n\tif (list.length !== 3) { return null; }\n\tconst timestamp = Number(list[0]);\n\tif (!Number.isSafeInteger(timestamp)) { return null; }\n\tconst time = Date.now() - timestamp;\n\tif (time > cookieTime) { return null; }\n\tconst salt = await useSalt();\n\tif (await hash(salt, list[1], list[0]) !== list[2]) { return null; }\n\treturn [list[1], time > cookieTime * 2 / 3];\n}\n\n/**\n * \n * @param {string} user \n * @returns {Promise<string>}\n */\nasync function toUserCookie(user) {\n\tconst timestamp = Date.now();\n\tconst salt = await useSalt();\n\treturn [timestamp, user, await hash(salt, user, timestamp)].join('.');\n}\n/** @type {UserManager['get']} */\nexport const get = async () => {\n\tconst ctx = k99Context();\n\tif (!ctx) { return null; }\n\tconst cookie = ctx.cookies[userLoggedCookie];\n\tif (!cookie) { return null; }\n\tconst userInfo = await parseUserCookie(cookie);\n\tif (!userInfo) { ctx.clearCookie(userLoggedCookie, { httpOnly: true, path: '/' }); return null; }\n\tconst [userId, reset] = userInfo;\n\tif (!existUser(userId)) { return null; }\n\tif (reset) { ctx.setCookie(userLoggedCookie, await toUserCookie(userId), { httpOnly: true, path: '/' }); }\n\treturn userId;\n};\n/**\n * \n * @param {string} userId \n * @returns \n */\n/** @type {UserManager['set']} */\nexport const set = async (userId) => {\n\tif (!userId) { return null; }\n\tconst ctx = k99Context();\n\tif (!ctx) { return null; }\n\tctx.setCookie(userLoggedCookie, await toUserCookie(userId), { httpOnly: true, path: '/' });\n\treturn userId;\n};\n/**\n * \n * @param {string} userId \n * @returns \n */\n/** @type {UserManager['login']} */\nexport const login = async (userId) => {\n\tif (!userId) { return null; }\n\tconst ctx = k99Context();\n\tif (!ctx) { return null; }\n\tctx.setCookie(userLoggedCookie, await toUserCookie(userId), { httpOnly: true, path: '/' });\n\treturn userId;\n};\n/** @type {UserManager['exit']} */\nexport const exit = () => {\n\tconst ctx = k99Context();\n\tif (!ctx) { return null; }\n\tctx.clearCookie(userLoggedCookie, { httpOnly: true, path: '/' });\n\treturn null;\n};\n"],"mappings":";;;;;AAKA,MAAM,mBAAmB;AACzB,MAAM,aAAa,OAAU,KAAK;;;;;;;;;;AAalC,eAAe,KAAK,MAAM,OAAO,GAAG,QAAQ;;CAG3C,IAAI;AACJ,KAAI,gBAAgB,eAAe,YAAY,OAAO,KAAK,CAC1D,aAAY;UACF,OAAO,SAAS,SAC1B,aAAY,IAAI,aAAa,CAAC,OAAO,KAAK;KAE1C,OAAM,IAAI,MAAM,8BAA8B;CAI/C,MAAM,YAAY,MAAM,OAAO,OAAO,UACrC,OACA,WACA;EAAE,MAAM;EAAQ,MAAM;EAAW,EACjC,OACA,CAAC,OAAO,CACR;;CAID,IAAI,OAAO,EAAE;AAGb,KAAI,OAAO,UAAU,SACpB,MAAK,KAAK,OAAO,MAAM,CAAC;UACd,OAAO,UAAU,SAE3B,MAAK,KAAK,KAAK,UAAU,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC;KAE7C,OAAM,IAAI,MAAM,sBAAsB;AAIvC,MAAK,MAAM,KAAK,OACf,KAAI,OAAO,MAAM,SAChB,MAAK,KAAK,OAAO,EAAE,CAAC;UACV,OAAO,MAAM,SACvB,MAAK,KAAK,KAAK,UAAU,EAAE,CAAC,MAAM,GAAG,GAAG,CAAC;CAK3C,MAAM,gBAAgB,IAAI,aAAa,CAAC,OAAO,KAAK,KAAK,KAAK,CAAC;CAG/D,MAAM,YAAY,MAAM,OAAO,OAAO,KAAK,QAAQ,WAAW,cAAc;AAI5E,QADe,KAAK,OAAO,aAAa,GAAG,IAAI,WAAW,UAAU,CAAC,CAAC,CAEpE,QAAQ,OAAO,IAAI,CACnB,QAAQ,OAAO,IAAI,CACnB,QAAQ,OAAO,GAAG;;;;;;;AAOrB,eAAe,gBAAgB,QAAQ;AACtC,KAAI,OAAO,WAAW,SAAY,QAAO;CACzC,MAAM,OAAO,OAAO,MAAM,IAAI;AAC9B,KAAI,KAAK,WAAW,EAAK,QAAO;CAChC,MAAM,YAAY,OAAO,KAAK,GAAG;AACjC,KAAI,CAAC,OAAO,cAAc,UAAU,CAAI,QAAO;CAC/C,MAAM,OAAO,KAAK,KAAK,GAAG;AAC1B,KAAI,OAAO,WAAc,QAAO;AAEhC,KAAI,MAAM,KADG,MAAM,SAAS,EACP,KAAK,IAAI,KAAK,GAAG,KAAK,KAAK,GAAM,QAAO;AAC7D,QAAO,CAAC,KAAK,IAAI,OAAO,aAAa,IAAI,EAAE;;;;;;;AAQ5C,eAAe,aAAa,MAAM;CACjC,MAAM,YAAY,KAAK,KAAK;AAE5B,QAAO;EAAC;EAAW;EAAM,MAAM,KADlB,MAAM,SAAS,EACc,MAAM,UAAU;EAAC,CAAC,KAAK,IAAI;;;AAGtE,MAAa,MAAM,YAAY;CAC9B,MAAM,MAAM,YAAY;AACxB,KAAI,CAAC,IAAO,QAAO;CACnB,MAAM,SAAS,IAAI,QAAQ;AAC3B,KAAI,CAAC,OAAU,QAAO;CACtB,MAAM,WAAW,MAAM,gBAAgB,OAAO;AAC9C,KAAI,CAAC,UAAU;AAAE,MAAI,YAAY,kBAAkB;GAAE,UAAU;GAAM,MAAM;GAAK,CAAC;AAAE,SAAO;;CAC1F,MAAM,CAAC,QAAQ,SAAS;AACxB,KAAI,CAAC,UAAU,OAAO,CAAI,QAAO;AACjC,KAAI,MAAS,KAAI,UAAU,kBAAkB,MAAM,aAAa,OAAO,EAAE;EAAE,UAAU;EAAM,MAAM;EAAK,CAAC;AACvG,QAAO;;;;;;;;AAQR,MAAa,MAAM,OAAO,WAAW;AACpC,KAAI,CAAC,OAAU,QAAO;CACtB,MAAM,MAAM,YAAY;AACxB,KAAI,CAAC,IAAO,QAAO;AACnB,KAAI,UAAU,kBAAkB,MAAM,aAAa,OAAO,EAAE;EAAE,UAAU;EAAM,MAAM;EAAK,CAAC;AAC1F,QAAO;;;;;;;;AAQR,MAAa,QAAQ,OAAO,WAAW;AACtC,KAAI,CAAC,OAAU,QAAO;CACtB,MAAM,MAAM,YAAY;AACxB,KAAI,CAAC,IAAO,QAAO;AACnB,KAAI,UAAU,kBAAkB,MAAM,aAAa,OAAO,EAAE;EAAE,UAAU;EAAM,MAAM;EAAK,CAAC;AAC1F,QAAO;;;AAGR,MAAa,aAAa;CACzB,MAAM,MAAM,YAAY;AACxB,KAAI,CAAC,IAAO,QAAO;AACnB,KAAI,YAAY,kBAAkB;EAAE,UAAU;EAAM,MAAM;EAAK,CAAC;AAChE,QAAO"}
1
+ {"version":3,"file":"user.mjs","names":[],"sources":["../../../plugins/user-cookie/yongdall/user.mts"],"sourcesContent":["import type { UserManager } from '@yongdall/core'\n\nimport { existUser } from '@yongdall/core';\nimport * as UserStatusToken from '@yongdall/user-status-token';\n\nimport { k99Context } from '@yongdall/http';\n\nconst userLoggedCookie = 'user-logged-token';\nconst cookieTime = 24 * 60 * 60 * 1000;\n\nexport const get: UserManager['get'] = async () => {\n\tconst ctx = k99Context();\n\tif (!ctx) { return null; }\n\tconst cookieAuth = ctx.requestHeaders.get('X-Cookie-Auth')?.trim().toLowerCase()\n\tif (cookieAuth === 'disabled') { return null; }\n\tconst cookie = ctx.cookies[userLoggedCookie];\n\tif (!cookie) { return null; }\n\tconst userInfo = await UserStatusToken.parse(cookie, cookieTime);\n\tif (!userInfo) { ctx.clearCookie(userLoggedCookie, { httpOnly: true, path: '/' }); return null; }\n\tconst [userId, percent] = userInfo;\n\tif (!existUser(userId)) { return null; }\n\tif (percent > 2 / 3) { ctx.setCookie(userLoggedCookie, await UserStatusToken.create(userId), { httpOnly: true, path: '/' }); }\n\treturn userId;\n};\n\nexport const set: UserManager['set'] = async (userId) => {\n\tif (!userId) { return null; }\n\tconst ctx = k99Context();\n\tif (!ctx) { return null; }\n\tctx.setCookie(userLoggedCookie, await UserStatusToken.create(userId), { httpOnly: true, path: '/' });\n\treturn true;\n};\n\nexport const login: UserManager['login'] = async (userId) => {\n\tif (!userId) { return null; }\n\tconst ctx = k99Context();\n\tif (!ctx) { return null; }\n\tconst cookieAuth = ctx.requestHeaders.get('X-Cookie-Auth')?.trim().toLowerCase()\n\tif (cookieAuth === 'disabled') { return null; }\n\tctx.setCookie(userLoggedCookie, await UserStatusToken.create(userId), { httpOnly: true, path: '/' });\n\treturn true;\n};\n\nexport const exit: UserManager['exit'] = () => {\n\tconst ctx = k99Context();\n\tif (!ctx) { return null; }\n\tctx.clearCookie(userLoggedCookie, { httpOnly: true, path: '/' });\n\treturn null;\n};\n"],"mappings":";;;;;AAOA,MAAM,mBAAmB;AACzB,MAAM,aAAa,OAAU,KAAK;AAElC,MAAa,MAA0B,YAAY;CAClD,MAAM,MAAM,YAAY;AACxB,KAAI,CAAC,IAAO,QAAO;AAEnB,KADmB,IAAI,eAAe,IAAI,gBAAgB,EAAE,MAAM,CAAC,aAAa,KAC7D,WAAc,QAAO;CACxC,MAAM,SAAS,IAAI,QAAQ;AAC3B,KAAI,CAAC,OAAU,QAAO;CACtB,MAAM,WAAW,MAAM,gBAAgB,MAAM,QAAQ,WAAW;AAChE,KAAI,CAAC,UAAU;AAAE,MAAI,YAAY,kBAAkB;GAAE,UAAU;GAAM,MAAM;GAAK,CAAC;AAAE,SAAO;;CAC1F,MAAM,CAAC,QAAQ,WAAW;AAC1B,KAAI,CAAC,UAAU,OAAO,CAAI,QAAO;AACjC,KAAI,UAAU,IAAI,EAAK,KAAI,UAAU,kBAAkB,MAAM,gBAAgB,OAAO,OAAO,EAAE;EAAE,UAAU;EAAM,MAAM;EAAK,CAAC;AAC3H,QAAO;;AAGR,MAAa,MAA0B,OAAO,WAAW;AACxD,KAAI,CAAC,OAAU,QAAO;CACtB,MAAM,MAAM,YAAY;AACxB,KAAI,CAAC,IAAO,QAAO;AACnB,KAAI,UAAU,kBAAkB,MAAM,gBAAgB,OAAO,OAAO,EAAE;EAAE,UAAU;EAAM,MAAM;EAAK,CAAC;AACpG,QAAO;;AAGR,MAAa,QAA8B,OAAO,WAAW;AAC5D,KAAI,CAAC,OAAU,QAAO;CACtB,MAAM,MAAM,YAAY;AACxB,KAAI,CAAC,IAAO,QAAO;AAEnB,KADmB,IAAI,eAAe,IAAI,gBAAgB,EAAE,MAAM,CAAC,aAAa,KAC7D,WAAc,QAAO;AACxC,KAAI,UAAU,kBAAkB,MAAM,gBAAgB,OAAO,OAAO,EAAE;EAAE,UAAU;EAAM,MAAM;EAAK,CAAC;AACpG,QAAO;;AAGR,MAAa,aAAkC;CAC9C,MAAM,MAAM,YAAY;AACxB,KAAI,CAAC,IAAO,QAAO;AACnB,KAAI,YAAY,kBAAkB;EAAE,UAAU;EAAM,MAAM;EAAK,CAAC;AAChE,QAAO"}