@yongdall/user-cookie 0.3.0 → 0.5.0

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,8 @@
1
1
  {
2
2
  "name": "@yongdall/user-cookie",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "dependencies": {
5
- "@yongdall/http": "^0.3.0",
6
- "@yongdall/core": "^0.3.0"
5
+ "@yongdall/http": "^0.5.0",
6
+ "@yongdall/core": "^0.5.0"
7
7
  }
8
8
  }
@@ -1,8 +1,8 @@
1
1
  import { existUser, useTenant } from "@yongdall/core";
2
2
  import { k99Context } from "@yongdall/http";
3
3
 
4
- //#region plugins/user-cookie/hooks.yongdall.mjs
5
- /** @import { Hooks } from '@yongdall/core' */
4
+ //#region plugins/user-cookie/yongdall/user.mjs
5
+ /** @import { Hooks, UserManager } from '@yongdall/core' */
6
6
  const userLoggedCookie = "user-logged-token";
7
7
  const cookieTime = 1440 * 60 * 1e3;
8
8
  /**
@@ -64,55 +64,71 @@ async function toUserCookie(user) {
64
64
  await hash((await useTenant()).salt, user, timestamp)
65
65
  ].join(".");
66
66
  }
67
- /** @type {Hooks.Define['userManager']} */
68
- const userManager = {
69
- async get() {
70
- const ctx = k99Context();
71
- if (!ctx) return null;
72
- const cookie = ctx.cookies[userLoggedCookie];
73
- if (!cookie) return null;
74
- const userInfo = await parseUserCookie(cookie);
75
- if (!userInfo) {
76
- ctx.clearCookie(userLoggedCookie, {
77
- httpOnly: true,
78
- path: "/"
79
- });
80
- return null;
81
- }
82
- const [userId, reset] = userInfo;
83
- if (!existUser(userId)) return null;
84
- if (reset) ctx.setCookie(userLoggedCookie, await toUserCookie(userId), {
85
- httpOnly: true,
86
- path: "/"
87
- });
88
- return userId;
89
- },
90
- async set(userId) {
91
- const ctx = k99Context();
92
- if (!ctx) return null;
93
- ctx.setCookie(userLoggedCookie, await toUserCookie(userId), {
94
- httpOnly: true,
95
- path: "/"
96
- });
97
- },
98
- async login(userId) {
99
- const ctx = k99Context();
100
- if (!ctx) return null;
101
- ctx.setCookie(userLoggedCookie, await toUserCookie(userId), {
102
- httpOnly: true,
103
- path: "/"
104
- });
105
- },
106
- exit() {
107
- const ctx = k99Context();
108
- if (!ctx) return null;
67
+ /** @type {UserManager['get']} */
68
+ const get = async () => {
69
+ const ctx = k99Context();
70
+ if (!ctx) return null;
71
+ const cookie = ctx.cookies[userLoggedCookie];
72
+ if (!cookie) return null;
73
+ const userInfo = await parseUserCookie(cookie);
74
+ if (!userInfo) {
109
75
  ctx.clearCookie(userLoggedCookie, {
110
76
  httpOnly: true,
111
77
  path: "/"
112
78
  });
79
+ return null;
113
80
  }
81
+ const [userId, reset] = userInfo;
82
+ if (!existUser(userId)) return null;
83
+ if (reset) ctx.setCookie(userLoggedCookie, await toUserCookie(userId), {
84
+ httpOnly: true,
85
+ path: "/"
86
+ });
87
+ return userId;
88
+ };
89
+ /**
90
+ *
91
+ * @param {string} userId
92
+ * @returns
93
+ */
94
+ /** @type {UserManager['set']} */
95
+ const set = async (userId) => {
96
+ if (!userId) return null;
97
+ const ctx = k99Context();
98
+ if (!ctx) return null;
99
+ ctx.setCookie(userLoggedCookie, await toUserCookie(userId), {
100
+ httpOnly: true,
101
+ path: "/"
102
+ });
103
+ return userId;
104
+ };
105
+ /**
106
+ *
107
+ * @param {string} userId
108
+ * @returns
109
+ */
110
+ /** @type {UserManager['login']} */
111
+ const login = async (userId) => {
112
+ if (!userId) return null;
113
+ const ctx = k99Context();
114
+ if (!ctx) return null;
115
+ ctx.setCookie(userLoggedCookie, await toUserCookie(userId), {
116
+ httpOnly: true,
117
+ path: "/"
118
+ });
119
+ return userId;
120
+ };
121
+ /** @type {UserManager['exit']} */
122
+ const exit = () => {
123
+ const ctx = k99Context();
124
+ if (!ctx) return null;
125
+ ctx.clearCookie(userLoggedCookie, {
126
+ httpOnly: true,
127
+ path: "/"
128
+ });
129
+ return null;
114
130
  };
115
131
 
116
132
  //#endregion
117
- export { userManager };
118
- //# sourceMappingURL=hooks.yongdall.mjs.map
133
+ export { exit, get, login, set };
134
+ //# sourceMappingURL=user.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.mjs","names":[],"sources":["../../../plugins/user-cookie/yongdall/user.mjs"],"sourcesContent":["/** @import { Hooks, UserManager } from '@yongdall/core' */\nimport { existUser } from '@yongdall/core';\nimport { useTenant } 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 tenant = await useTenant();\n\tif (await hash(tenant.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 tenant = await useTenant();\n\treturn [timestamp, user, await hash(tenant.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":"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"}
@@ -0,0 +1 @@
1
+ label: 用户登陆状态维持
@@ -1 +0,0 @@
1
- {"version":3,"file":"hooks.yongdall.mjs","names":[],"sources":["../../plugins/user-cookie/hooks.yongdall.mjs"],"sourcesContent":["/** @import { Hooks } from '@yongdall/core' */\nimport { existUser } from '@yongdall/core';\nimport { useTenant } 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 tenant = await useTenant();\n\tif (await hash(tenant.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 tenant = await useTenant();\n\treturn [timestamp, user, await hash(tenant.salt, user, timestamp)].join('.');\n}\n/** @type {Hooks.Define['userManager']} */\nexport const userManager = {\n\tasync get() {\n\t\tconst ctx = k99Context();\n\t\tif (!ctx) { return null; }\n\t\tconst cookie = ctx.cookies[userLoggedCookie];\n\t\tif (!cookie) { return null; }\n\t\tconst userInfo = await parseUserCookie(cookie);\n\t\tif (!userInfo) { ctx.clearCookie(userLoggedCookie, { httpOnly: true, path: '/' }); return null; }\n\t\tconst [userId, reset] = userInfo;\n\t\tif (!existUser(userId)) { return null; }\n\t\tif (reset) { ctx.setCookie(userLoggedCookie, await toUserCookie(userId), { httpOnly: true, path: '/' }); }\n\t\treturn userId;\n\t},\n\t/**\n\t * \n\t * @param {string} userId \n\t * @returns \n\t */\n\tasync set(userId) {\n\t\tconst ctx = k99Context();\n\t\tif (!ctx) { return null; }\n\t\tctx.setCookie(userLoggedCookie, await toUserCookie(userId), { httpOnly: true, path: '/' });\n\t},\n\t/**\n\t * \n\t * @param {string} userId \n\t * @returns \n\t */\n\tasync login(userId) {\n\t\tconst ctx = k99Context();\n\t\tif (!ctx) { return null; }\n\t\tctx.setCookie(userLoggedCookie, await toUserCookie(userId), { httpOnly: true, path: '/' });\n\t},\n\texit() {\n\t\tconst ctx = k99Context();\n\t\tif (!ctx) { return null; }\n\t\tctx.clearCookie(userLoggedCookie, { httpOnly: true, path: '/' });\n\t},\n};\n"],"mappings":"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"}