@yongdall/user-cookie 0.4.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
|
import { existUser, useTenant } from "@yongdall/core";
|
|
2
2
|
import { k99Context } from "@yongdall/http";
|
|
3
3
|
|
|
4
|
-
//#region plugins/user-cookie/
|
|
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 {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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 {
|
|
118
|
-
//# sourceMappingURL=
|
|
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: 用户登陆状态维持
|
package/hooks.yongdall.mjs.map
DELETED
|
@@ -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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"}
|