@alibarbar/common 1.1.1 → 1.1.3
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 +10 -0
- package/dist/crypto.cjs +184 -248
- package/dist/crypto.d.mts +50 -103
- package/dist/crypto.d.ts +50 -103
- package/dist/crypto.js +170 -234
- package/dist/{index-DchqyDBQ.d.mts → index-CqWtZgzO.d.mts} +1 -1
- package/dist/{index-DchqyDBQ.d.ts → index-CqWtZgzO.d.ts} +1 -1
- package/dist/index.cjs +432 -951
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +423 -930
- package/dist/services.cjs +82 -60
- package/dist/services.d.mts +1 -1
- package/dist/services.d.ts +1 -1
- package/dist/services.js +82 -60
- package/dist/storage.cjs +331 -854
- package/dist/storage.d.mts +58 -128
- package/dist/storage.d.ts +58 -128
- package/dist/storage.js +323 -848
- package/dist/upload.d.mts +1 -1
- package/dist/upload.d.ts +1 -1
- package/package.json +32 -27
package/dist/storage.cjs
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var JSEncrypt = require('jsencrypt');
|
|
6
|
+
var CryptoJS = require('crypto-js');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var JSEncrypt__default = /*#__PURE__*/_interopDefault(JSEncrypt);
|
|
11
|
+
var CryptoJS__default = /*#__PURE__*/_interopDefault(CryptoJS);
|
|
12
|
+
|
|
13
|
+
// src/helper/encryption/index.ts
|
|
4
14
|
function base64Encode(data) {
|
|
5
15
|
if (typeof data === "string") {
|
|
6
16
|
return btoa(unescape(encodeURIComponent(data)));
|
|
@@ -12,954 +22,421 @@ function base64Encode(data) {
|
|
|
12
22
|
}
|
|
13
23
|
return btoa(binary);
|
|
14
24
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
return decodeURIComponent(escape(atob(data)));
|
|
18
|
-
} catch {
|
|
19
|
-
throw new Error("Invalid Base64 string");
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
function generateRandomString(length, charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") {
|
|
23
|
-
let result = "";
|
|
24
|
-
for (let i = 0; i < length; i++) {
|
|
25
|
-
result += charset.charAt(Math.floor(Math.random() * charset.length));
|
|
26
|
-
}
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
async function generateRSAKeyPair(modulusLength = 2048) {
|
|
30
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
31
|
-
throw new Error("Web Crypto API is not available");
|
|
32
|
-
}
|
|
33
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
34
|
-
{
|
|
35
|
-
name: "RSA-OAEP",
|
|
36
|
-
modulusLength,
|
|
37
|
-
publicExponent: new Uint8Array([1, 0, 1]),
|
|
38
|
-
hash: "SHA-256"
|
|
39
|
-
},
|
|
40
|
-
true,
|
|
41
|
-
["encrypt", "decrypt"]
|
|
42
|
-
);
|
|
43
|
-
return {
|
|
44
|
-
publicKey: keyPair.publicKey,
|
|
45
|
-
privateKey: keyPair.privateKey
|
|
46
|
-
};
|
|
25
|
+
function generateRandomAESKeyString() {
|
|
26
|
+
return CryptoJS__default.default.lib.WordArray.random(256 / 8).toString();
|
|
47
27
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const chunks = [];
|
|
56
|
-
for (let i = 0; i < dataBuffer.length; i += maxChunkSize) {
|
|
57
|
-
const chunk = dataBuffer.slice(i, i + maxChunkSize);
|
|
58
|
-
const encrypted = await crypto.subtle.encrypt(
|
|
59
|
-
{
|
|
60
|
-
name: "RSA-OAEP"
|
|
61
|
-
},
|
|
62
|
-
publicKey,
|
|
63
|
-
chunk
|
|
64
|
-
);
|
|
65
|
-
chunks.push(encrypted);
|
|
66
|
-
}
|
|
67
|
-
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
|
|
68
|
-
const merged = new Uint8Array(totalLength);
|
|
69
|
-
let offset = 0;
|
|
70
|
-
for (const chunk of chunks) {
|
|
71
|
-
merged.set(new Uint8Array(chunk), offset);
|
|
72
|
-
offset += chunk.byteLength;
|
|
73
|
-
}
|
|
74
|
-
return base64Encode(merged.buffer);
|
|
75
|
-
}
|
|
76
|
-
async function rsaDecrypt(encryptedData, privateKey) {
|
|
77
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
78
|
-
throw new Error("Web Crypto API is not available");
|
|
79
|
-
}
|
|
80
|
-
const binaryString = atob(encryptedData);
|
|
81
|
-
const encryptedArray = new Uint8Array(binaryString.length);
|
|
82
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
83
|
-
encryptedArray[i] = binaryString.charCodeAt(i);
|
|
84
|
-
}
|
|
85
|
-
const chunkSize = 256;
|
|
86
|
-
const chunks = [];
|
|
87
|
-
for (let i = 0; i < encryptedArray.length; i += chunkSize) {
|
|
88
|
-
const chunk = encryptedArray.slice(i, i + chunkSize);
|
|
89
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
90
|
-
{
|
|
91
|
-
name: "RSA-OAEP"
|
|
92
|
-
},
|
|
93
|
-
privateKey,
|
|
94
|
-
chunk
|
|
28
|
+
function encryptJsonWithAES(data, aesKey) {
|
|
29
|
+
try {
|
|
30
|
+
const jsonString = JSON.stringify(data);
|
|
31
|
+
return CryptoJS__default.default.AES.encrypt(jsonString, aesKey).toString();
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"[encryption] AES encryptJsonWithAES failed: " + (error instanceof Error ? error.message : String(error))
|
|
95
35
|
);
|
|
96
|
-
const decoder = new TextDecoder();
|
|
97
|
-
chunks.push(decoder.decode(decrypted));
|
|
98
|
-
}
|
|
99
|
-
return chunks.join("");
|
|
100
|
-
}
|
|
101
|
-
async function exportPublicKey(publicKey) {
|
|
102
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
103
|
-
throw new Error("Web Crypto API is not available");
|
|
104
36
|
}
|
|
105
|
-
const exported = await crypto.subtle.exportKey("spki", publicKey);
|
|
106
|
-
return base64Encode(exported);
|
|
107
37
|
}
|
|
108
|
-
|
|
109
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
110
|
-
throw new Error("Web Crypto API is not available");
|
|
111
|
-
}
|
|
112
|
-
const exported = await crypto.subtle.exportKey("pkcs8", privateKey);
|
|
113
|
-
return base64Encode(exported);
|
|
114
|
-
}
|
|
115
|
-
async function importPublicKey(keyData) {
|
|
116
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
117
|
-
throw new Error("Web Crypto API is not available");
|
|
118
|
-
}
|
|
119
|
-
const keyBuffer = base64Decode(keyData);
|
|
120
|
-
const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
|
|
121
|
-
return crypto.subtle.importKey(
|
|
122
|
-
"spki",
|
|
123
|
-
keyArray.buffer,
|
|
124
|
-
{
|
|
125
|
-
name: "RSA-OAEP",
|
|
126
|
-
hash: "SHA-256"
|
|
127
|
-
},
|
|
128
|
-
true,
|
|
129
|
-
["encrypt"]
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
async function importPrivateKey(keyData) {
|
|
133
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
134
|
-
throw new Error("Web Crypto API is not available");
|
|
135
|
-
}
|
|
136
|
-
const keyBuffer = base64Decode(keyData);
|
|
137
|
-
const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
|
|
138
|
-
return crypto.subtle.importKey(
|
|
139
|
-
"pkcs8",
|
|
140
|
-
keyArray.buffer,
|
|
141
|
-
{
|
|
142
|
-
name: "RSA-OAEP",
|
|
143
|
-
hash: "SHA-256"
|
|
144
|
-
},
|
|
145
|
-
true,
|
|
146
|
-
["decrypt"]
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
async function generateHMACKey() {
|
|
150
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
151
|
-
throw new Error("Web Crypto API is not available");
|
|
152
|
-
}
|
|
153
|
-
return crypto.subtle.generateKey(
|
|
154
|
-
{
|
|
155
|
-
name: "HMAC",
|
|
156
|
-
hash: "SHA-256"
|
|
157
|
-
},
|
|
158
|
-
true,
|
|
159
|
-
["sign", "verify"]
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
async function computeHMAC(data, key) {
|
|
163
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
164
|
-
throw new Error("Web Crypto API is not available");
|
|
165
|
-
}
|
|
166
|
-
const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
167
|
-
const signature = await crypto.subtle.sign("HMAC", key, buffer);
|
|
168
|
-
return base64Encode(signature);
|
|
169
|
-
}
|
|
170
|
-
async function verifyHMAC(data, signature, key) {
|
|
171
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
172
|
-
throw new Error("Web Crypto API is not available");
|
|
173
|
-
}
|
|
38
|
+
function decryptJsonWithAES(encryptedData, aesKey) {
|
|
174
39
|
try {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
40
|
+
const decrypted = CryptoJS__default.default.AES.decrypt(encryptedData, aesKey);
|
|
41
|
+
const jsonString = decrypted.toString(CryptoJS__default.default.enc.Utf8);
|
|
42
|
+
if (!jsonString) {
|
|
43
|
+
throw new Error("decrypted JSON string is empty");
|
|
44
|
+
}
|
|
45
|
+
return JSON.parse(jsonString);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"[encryption] AES decryptJsonWithAES failed: " + (error instanceof Error ? error.message : String(error))
|
|
178
49
|
);
|
|
179
|
-
return await crypto.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
|
|
180
|
-
} catch {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
async function deriveKeyFromPassword(password, salt, iterations = 1e5, keyLength = 256) {
|
|
185
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
186
|
-
throw new Error("Web Crypto API is not available");
|
|
187
50
|
}
|
|
188
|
-
const saltBuffer = typeof salt === "string" ? new TextEncoder().encode(salt) : salt;
|
|
189
|
-
const passwordKey = await crypto.subtle.importKey(
|
|
190
|
-
"raw",
|
|
191
|
-
new TextEncoder().encode(password),
|
|
192
|
-
"PBKDF2",
|
|
193
|
-
false,
|
|
194
|
-
["deriveBits", "deriveKey"]
|
|
195
|
-
);
|
|
196
|
-
return crypto.subtle.deriveKey(
|
|
197
|
-
{
|
|
198
|
-
name: "PBKDF2",
|
|
199
|
-
salt: saltBuffer,
|
|
200
|
-
iterations,
|
|
201
|
-
hash: "SHA-256"
|
|
202
|
-
},
|
|
203
|
-
passwordKey,
|
|
204
|
-
{
|
|
205
|
-
name: "AES-GCM",
|
|
206
|
-
length: keyLength
|
|
207
|
-
},
|
|
208
|
-
false,
|
|
209
|
-
["encrypt", "decrypt"]
|
|
210
|
-
);
|
|
211
51
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
52
|
+
function getEnvPublicKey() {
|
|
53
|
+
const key = undefined.VITE_RSA_PUBLIC_KEY?.trim();
|
|
54
|
+
if (!key) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
"[encryption] VITE_RSA_PUBLIC_KEY is not set. Please configure RSA public key in environment variables."
|
|
57
|
+
);
|
|
215
58
|
}
|
|
216
|
-
|
|
217
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
218
|
-
const encrypted = await crypto.subtle.encrypt(
|
|
219
|
-
{
|
|
220
|
-
name: "AES-GCM",
|
|
221
|
-
iv
|
|
222
|
-
},
|
|
223
|
-
key,
|
|
224
|
-
dataBuffer
|
|
225
|
-
);
|
|
226
|
-
return {
|
|
227
|
-
encrypted: base64Encode(encrypted),
|
|
228
|
-
iv: base64Encode(iv.buffer)
|
|
229
|
-
};
|
|
59
|
+
return key;
|
|
230
60
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
61
|
+
function getEnvPrivateKey() {
|
|
62
|
+
const key = undefined.VITE_RSA_PRIVATE_KEY?.trim();
|
|
63
|
+
if (!key) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
"[encryption] VITE_RSA_PRIVATE_KEY is not set. Please configure RSA private key in environment variables."
|
|
66
|
+
);
|
|
234
67
|
}
|
|
235
|
-
|
|
236
|
-
atob(encryptedData).split("").map((char) => char.charCodeAt(0))
|
|
237
|
-
);
|
|
238
|
-
const ivBuffer = new Uint8Array(
|
|
239
|
-
atob(iv).split("").map((char) => char.charCodeAt(0))
|
|
240
|
-
);
|
|
241
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
242
|
-
{
|
|
243
|
-
name: "AES-GCM",
|
|
244
|
-
iv: ivBuffer
|
|
245
|
-
},
|
|
246
|
-
key,
|
|
247
|
-
encryptedBuffer
|
|
248
|
-
);
|
|
249
|
-
return new TextDecoder().decode(decrypted);
|
|
68
|
+
return key;
|
|
250
69
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
pbkdf2Iterations: 1e5,
|
|
263
|
-
keyModulusLength: 2048,
|
|
264
|
-
enableHMAC: true,
|
|
265
|
-
enableTimestampValidation: true,
|
|
266
|
-
timestampMaxAge: 7 * 24 * 60 * 60 * 1e3,
|
|
267
|
-
// 7 天
|
|
268
|
-
isProduction: false
|
|
269
|
-
};
|
|
270
|
-
var actualKeyStorageKey = null;
|
|
271
|
-
var keyUsageCount = 0;
|
|
272
|
-
var initializationPromise = null;
|
|
273
|
-
function generateKeyStorageKey() {
|
|
274
|
-
return `_sk_${generateRandomString(32)}_${Date.now()}`;
|
|
70
|
+
function base64ToPem(base64Key, type) {
|
|
71
|
+
const chunks = [];
|
|
72
|
+
for (let i = 0; i < base64Key.length; i += 64) {
|
|
73
|
+
chunks.push(base64Key.slice(i, i + 64));
|
|
74
|
+
}
|
|
75
|
+
const body = chunks.join("\n");
|
|
76
|
+
const header = type === "PUBLIC" ? "-----BEGIN PUBLIC KEY-----" : "-----BEGIN PRIVATE KEY-----";
|
|
77
|
+
const footer = type === "PUBLIC" ? "-----END PUBLIC KEY-----" : "-----END PRIVATE KEY-----";
|
|
78
|
+
return `${header}
|
|
79
|
+
${body}
|
|
80
|
+
${footer}`;
|
|
275
81
|
}
|
|
276
|
-
async function
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
} else if (keyPairInitialized && globalKeyPair) {
|
|
285
|
-
initOptions = { ...initOptions, ...options };
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
initOptions = { ...initOptions, ...options };
|
|
289
|
-
if (initOptions.keyStorageKey) {
|
|
290
|
-
actualKeyStorageKey = initOptions.keyStorageKey;
|
|
291
|
-
} else {
|
|
292
|
-
actualKeyStorageKey = generateKeyStorageKey();
|
|
293
|
-
}
|
|
294
|
-
if (initOptions.persistKeys && typeof window !== "undefined" && window.localStorage) {
|
|
295
|
-
try {
|
|
296
|
-
const storedKeys = window.localStorage.getItem(actualKeyStorageKey);
|
|
297
|
-
if (storedKeys) {
|
|
298
|
-
const keyData = JSON.parse(storedKeys);
|
|
299
|
-
if (keyData.encrypted && keyData.privateKeyEncrypted && keyData.salt) {
|
|
300
|
-
if (!initOptions.keyEncryptionPassword) {
|
|
301
|
-
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
302
|
-
console.error("Encrypted keys found but no password provided");
|
|
303
|
-
}
|
|
304
|
-
throw new Error("Password required to decrypt stored keys");
|
|
305
|
-
}
|
|
306
|
-
const saltArray = new Uint8Array(
|
|
307
|
-
atob(keyData.salt).split("").map((char) => char.charCodeAt(0))
|
|
308
|
-
);
|
|
309
|
-
const iterations = keyData.pbkdf2Iterations || initOptions.pbkdf2Iterations;
|
|
310
|
-
const derivedKey = await deriveKeyFromPassword(
|
|
311
|
-
initOptions.keyEncryptionPassword,
|
|
312
|
-
saltArray.buffer,
|
|
313
|
-
iterations
|
|
314
|
-
);
|
|
315
|
-
const decryptedPrivateKey = await aesGCMDecrypt(
|
|
316
|
-
keyData.privateKeyEncrypted,
|
|
317
|
-
keyData.iv,
|
|
318
|
-
derivedKey
|
|
319
|
-
);
|
|
320
|
-
globalKeyPair = {
|
|
321
|
-
publicKey: await importPublicKey(keyData.publicKey),
|
|
322
|
-
privateKey: await importPrivateKey(decryptedPrivateKey)
|
|
323
|
-
};
|
|
324
|
-
} else if (keyData.publicKey && keyData.privateKey) {
|
|
325
|
-
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
326
|
-
console.warn(
|
|
327
|
-
"\u26A0\uFE0F SECURITY WARNING: Loading unencrypted keys from storage. Consider re-initializing with password encryption."
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
globalKeyPair = {
|
|
331
|
-
publicKey: await importPublicKey(keyData.publicKey),
|
|
332
|
-
privateKey: await importPrivateKey(keyData.privateKey)
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
if (globalKeyPair) {
|
|
336
|
-
keyPairInitialized = true;
|
|
337
|
-
if (initOptions.enableHMAC && !globalHMACKey) {
|
|
338
|
-
globalHMACKey = await generateHMACKey();
|
|
339
|
-
}
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
82
|
+
async function rsaEncrypt(plain, publicKeyPem) {
|
|
83
|
+
try {
|
|
84
|
+
const encrypt = new JSEncrypt__default.default();
|
|
85
|
+
let publicKeyString;
|
|
86
|
+
if (typeof publicKeyPem === "string") {
|
|
87
|
+
const trimmedKey = publicKeyPem.trim();
|
|
88
|
+
if (!trimmedKey) {
|
|
89
|
+
throw new Error("Public key string is empty");
|
|
342
90
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
91
|
+
if (trimmedKey.includes("-----BEGIN")) {
|
|
92
|
+
publicKeyString = trimmedKey;
|
|
93
|
+
} else {
|
|
94
|
+
publicKeyString = base64ToPem(trimmedKey, "PUBLIC");
|
|
346
95
|
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
96
|
+
} else if (publicKeyPem instanceof CryptoJS__default.default.lib.WordArray) {
|
|
97
|
+
const base64Key = publicKeyPem.toString();
|
|
98
|
+
publicKeyString = base64ToPem(base64Key, "PUBLIC");
|
|
99
|
+
} else {
|
|
100
|
+
try {
|
|
101
|
+
const exported = await crypto.subtle.exportKey("spki", publicKeyPem);
|
|
102
|
+
const base64Key = base64Encode(exported);
|
|
103
|
+
publicKeyString = base64ToPem(base64Key, "PUBLIC");
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
"Failed to export CryptoKey. In non-HTTPS environment, use string publicKey (Base64 or PEM) directly. " + (error instanceof Error ? error.message : String(error))
|
|
107
|
+
);
|
|
354
108
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
);
|
|
368
|
-
const { encrypted, iv } = await aesGCMEncrypt(privateKeyStr, derivedKey);
|
|
369
|
-
const keyData = {
|
|
370
|
-
encrypted: true,
|
|
371
|
-
publicKey: publicKeyStr,
|
|
372
|
-
privateKeyEncrypted: encrypted,
|
|
373
|
-
iv,
|
|
374
|
-
salt: base64Encode(salt.buffer),
|
|
375
|
-
pbkdf2Iterations: initOptions.pbkdf2Iterations
|
|
376
|
-
};
|
|
377
|
-
window.localStorage.setItem(actualKeyStorageKey, JSON.stringify(keyData));
|
|
378
|
-
if (!initOptions.isProduction && typeof console !== "undefined" && console.info) {
|
|
379
|
-
console.info("\u2705 Keys encrypted and stored securely");
|
|
380
|
-
}
|
|
381
|
-
} else {
|
|
382
|
-
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
383
|
-
console.warn(
|
|
384
|
-
"\u26A0\uFE0F SECURITY WARNING: Storing private keys without encryption! Private keys will be stored in plain text (Base64 encoded). Provide keyEncryptionPassword for secure storage."
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
const keyData = {
|
|
388
|
-
publicKey: publicKeyStr,
|
|
389
|
-
privateKey: await exportPrivateKey(globalKeyPair.privateKey)
|
|
390
|
-
};
|
|
391
|
-
window.localStorage.setItem(actualKeyStorageKey, JSON.stringify(keyData));
|
|
392
|
-
}
|
|
393
|
-
} catch (error) {
|
|
394
|
-
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
395
|
-
console.error("Failed to persist keys");
|
|
396
|
-
}
|
|
109
|
+
}
|
|
110
|
+
encrypt.setPublicKey(publicKeyString);
|
|
111
|
+
const MAX_ENCRYPT_LENGTH = 200;
|
|
112
|
+
if (plain.length > MAX_ENCRYPT_LENGTH) {
|
|
113
|
+
const chunks = [];
|
|
114
|
+
for (let i = 0; i < plain.length; i += MAX_ENCRYPT_LENGTH) {
|
|
115
|
+
const chunk = plain.slice(i, i + MAX_ENCRYPT_LENGTH);
|
|
116
|
+
const encrypted2 = encrypt.encrypt(chunk);
|
|
117
|
+
if (!encrypted2) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`RSA encryption failed for chunk ${i / MAX_ENCRYPT_LENGTH + 1}. Public key may be invalid or JSEncrypt may not be loaded correctly.`
|
|
120
|
+
);
|
|
397
121
|
}
|
|
122
|
+
chunks.push(encrypted2);
|
|
398
123
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
124
|
+
return chunks.join("|");
|
|
125
|
+
}
|
|
126
|
+
const encrypted = encrypt.encrypt(plain);
|
|
127
|
+
if (!encrypted) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`RSA encryption failed. Public key may be invalid or JSEncrypt may not be loaded correctly. Plain text length: ${plain.length} bytes.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return encrypted;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error instanceof Error) {
|
|
135
|
+
if (error.message.includes("RSA encryption failed") || error.message.includes("Public key")) {
|
|
136
|
+
throw error;
|
|
402
137
|
}
|
|
403
|
-
throw error;
|
|
138
|
+
throw new Error(`[encryption] rsaEncrypt failed: ${error.message}`);
|
|
404
139
|
}
|
|
405
|
-
|
|
406
|
-
if (initOptions.enableHMAC && !globalHMACKey) {
|
|
407
|
-
globalHMACKey = await generateHMACKey();
|
|
140
|
+
throw new Error(`[encryption] rsaEncrypt failed: ${String(error)}`);
|
|
408
141
|
}
|
|
409
142
|
}
|
|
410
|
-
function
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
|
|
143
|
+
async function rsaDecrypt(cipher, privateKeyPem) {
|
|
144
|
+
const decrypt = new JSEncrypt__default.default();
|
|
145
|
+
let privateKeyString;
|
|
146
|
+
if (typeof privateKeyPem === "string") {
|
|
147
|
+
if (privateKeyPem.includes("-----BEGIN")) {
|
|
148
|
+
privateKeyString = privateKeyPem.trim();
|
|
149
|
+
} else {
|
|
150
|
+
const trimmed = privateKeyPem.trim();
|
|
151
|
+
privateKeyString = base64ToPem(trimmed, "PRIVATE");
|
|
152
|
+
}
|
|
153
|
+
} else if (privateKeyPem instanceof CryptoJS__default.default.lib.WordArray) {
|
|
154
|
+
const base64Key = privateKeyPem.toString();
|
|
155
|
+
privateKeyString = base64ToPem(base64Key, "PRIVATE");
|
|
156
|
+
} else {
|
|
419
157
|
try {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
globalKeyPair = null;
|
|
424
|
-
globalHMACKey = null;
|
|
425
|
-
keyUsageCount = 0;
|
|
158
|
+
const exported = await crypto.subtle.exportKey("pkcs8", privateKeyPem);
|
|
159
|
+
const base64Key = base64Encode(exported);
|
|
160
|
+
privateKeyString = base64ToPem(base64Key, "PRIVATE");
|
|
426
161
|
} catch (error) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
function getKeyUsageCount() {
|
|
434
|
-
return keyUsageCount;
|
|
435
|
-
}
|
|
436
|
-
function resetKeyUsageCount() {
|
|
437
|
-
keyUsageCount = 0;
|
|
438
|
-
}
|
|
439
|
-
async function ensureKeyPair() {
|
|
440
|
-
if (globalKeyPair) {
|
|
441
|
-
keyUsageCount++;
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
if (!initOptions.autoGenerateKeys) {
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
if (initializationPromise) {
|
|
448
|
-
await initializationPromise;
|
|
449
|
-
if (globalKeyPair) {
|
|
450
|
-
keyUsageCount++;
|
|
162
|
+
throw new Error(
|
|
163
|
+
"[encryption] rsaDecrypt: failed to export CryptoKey. In non-HTTPS environment, use PEM string directly."
|
|
164
|
+
);
|
|
451
165
|
}
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
initializationPromise = initializeStorageKeys();
|
|
455
|
-
try {
|
|
456
|
-
await initializationPromise;
|
|
457
|
-
} finally {
|
|
458
|
-
initializationPromise = null;
|
|
459
166
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
if (expectedType === "object" && (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))) {
|
|
468
|
-
throw new Error("Expected object but got different type");
|
|
469
|
-
}
|
|
470
|
-
if (expectedType === "array" && !Array.isArray(parsed)) {
|
|
471
|
-
throw new Error("Expected array but got different type");
|
|
472
|
-
}
|
|
473
|
-
return parsed;
|
|
474
|
-
} catch (error) {
|
|
475
|
-
if (error instanceof SyntaxError) {
|
|
476
|
-
throw new Error(`Invalid JSON format: ${error.message}`);
|
|
167
|
+
decrypt.setPrivateKey(privateKeyString);
|
|
168
|
+
const chunks = cipher.split("|");
|
|
169
|
+
const decryptedChunks = [];
|
|
170
|
+
for (const chunk of chunks) {
|
|
171
|
+
const decrypted = decrypt.decrypt(chunk);
|
|
172
|
+
if (!decrypted) {
|
|
173
|
+
throw new Error("[encryption] rsaDecrypt: RSA decryption failed");
|
|
477
174
|
}
|
|
478
|
-
|
|
175
|
+
decryptedChunks.push(decrypted);
|
|
479
176
|
}
|
|
177
|
+
return decryptedChunks.join("");
|
|
480
178
|
}
|
|
481
|
-
function
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
if (initOptions.timestampMaxAge === 0) {
|
|
486
|
-
return true;
|
|
487
|
-
}
|
|
488
|
-
const now = Date.now();
|
|
489
|
-
const age = now - timestamp;
|
|
490
|
-
const maxAge = initOptions.timestampMaxAge || 7 * 24 * 60 * 60 * 1e3;
|
|
491
|
-
if (age < -maxClockSkew) {
|
|
492
|
-
return false;
|
|
493
|
-
}
|
|
494
|
-
return age >= -maxClockSkew && age <= maxAge;
|
|
179
|
+
async function encryptWithEnvPublicKey(plain) {
|
|
180
|
+
const publicKey = getEnvPublicKey();
|
|
181
|
+
return rsaEncrypt(plain, publicKey);
|
|
495
182
|
}
|
|
496
|
-
async function
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
const hmac = await computeHMAC(encryptedData, hmacKey);
|
|
500
|
-
const item = {
|
|
501
|
-
data: encryptedData,
|
|
502
|
-
hmac,
|
|
503
|
-
encrypted: true,
|
|
504
|
-
timestamp: Date.now()
|
|
505
|
-
};
|
|
506
|
-
return JSON.stringify(item);
|
|
507
|
-
}
|
|
508
|
-
return encryptedData;
|
|
183
|
+
async function decryptWithEnvPrivateKey(cipher) {
|
|
184
|
+
const privateKey = getEnvPrivateKey();
|
|
185
|
+
return rsaDecrypt(cipher, privateKey);
|
|
509
186
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
187
|
+
|
|
188
|
+
// src/browser/SecureStorage/index.ts
|
|
189
|
+
var CONFIG = {
|
|
190
|
+
NAMESPACE: "sec_b_",
|
|
191
|
+
DEFAULT_EXPIRE: null
|
|
192
|
+
};
|
|
193
|
+
var SecureStorage = class _SecureStorage {
|
|
194
|
+
static get instances() {
|
|
195
|
+
if (!this._instances) {
|
|
196
|
+
this._instances = /* @__PURE__ */ new Map();
|
|
516
197
|
}
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
198
|
+
return this._instances;
|
|
199
|
+
}
|
|
200
|
+
constructor(options) {
|
|
201
|
+
const storageType = options.storage === window.sessionStorage ? "session" : "local";
|
|
202
|
+
const namespace = options.namespace || CONFIG.NAMESPACE;
|
|
203
|
+
const instanceKey = `${storageType}_${namespace}`;
|
|
204
|
+
const existingInstance = _SecureStorage.instances.get(instanceKey);
|
|
205
|
+
if (existingInstance) {
|
|
206
|
+
return existingInstance;
|
|
522
207
|
}
|
|
523
|
-
|
|
208
|
+
this.namespace = namespace;
|
|
209
|
+
this.storage = options.storage || window.localStorage;
|
|
210
|
+
this.defaultExpire = options.defaultExpire ?? CONFIG.DEFAULT_EXPIRE;
|
|
211
|
+
this.instanceKey = instanceKey;
|
|
212
|
+
_SecureStorage.instances.set(instanceKey, this);
|
|
524
213
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
return decryptedStr;
|
|
533
|
-
} catch {
|
|
534
|
-
throw error;
|
|
214
|
+
/**
|
|
215
|
+
* 获取单例实例(静态方法)
|
|
216
|
+
*/
|
|
217
|
+
static getInstance(options) {
|
|
218
|
+
return new _SecureStorage(options);
|
|
535
219
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
console.warn(
|
|
542
|
-
`\u26A0\uFE0F SECURITY WARNING: Reading unencrypted data for key "${key}" without encryption keys.`
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
return decryptedStr;
|
|
546
|
-
} catch (error) {
|
|
547
|
-
throw new Error(`Failed to decode storage value for key "${key}"`);
|
|
220
|
+
/**
|
|
221
|
+
* 清除所有单例实例(用于测试或重置)
|
|
222
|
+
*/
|
|
223
|
+
static clearInstances() {
|
|
224
|
+
_SecureStorage.instances.clear();
|
|
548
225
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
if (error instanceof Error && error.message.includes("HMAC verification failed")) {
|
|
568
|
-
throw error;
|
|
569
|
-
}
|
|
570
|
-
throw new Error("Failed to decrypt storage value: invalid format or corrupted data");
|
|
226
|
+
/**
|
|
227
|
+
* 获取当前所有实例的数量(用于调试)
|
|
228
|
+
*/
|
|
229
|
+
static getInstanceCount() {
|
|
230
|
+
return _SecureStorage.instances.size;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* 获取完整键名(带命名空间)
|
|
234
|
+
*/
|
|
235
|
+
_getFullKey(key) {
|
|
236
|
+
return `${this.namespace}${key}`;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 检查数据是否过期
|
|
240
|
+
*/
|
|
241
|
+
_isExpired(expire) {
|
|
242
|
+
if (!expire) return false;
|
|
243
|
+
return Date.now() > expire;
|
|
571
244
|
}
|
|
572
|
-
}
|
|
573
|
-
var localStorage = {
|
|
574
245
|
/**
|
|
575
246
|
* 设置值
|
|
576
|
-
* @param key - 键
|
|
577
|
-
* @param value - 值
|
|
578
|
-
* @param options - 选项(过期时间、密钥等)
|
|
579
|
-
* @returns Promise<void>
|
|
580
247
|
*/
|
|
581
|
-
async set(key, value,
|
|
582
|
-
if (typeof window === "undefined" || !window.localStorage) {
|
|
583
|
-
throw new Error("localStorage is not available");
|
|
584
|
-
}
|
|
585
|
-
const { expiry, publicKey } = options;
|
|
586
|
-
const item = {
|
|
587
|
-
value,
|
|
588
|
-
expiry: expiry ? Date.now() + expiry : void 0
|
|
589
|
-
};
|
|
248
|
+
async set(key, value, expire = this.defaultExpire) {
|
|
590
249
|
try {
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
250
|
+
const fullKey = this._getFullKey(key);
|
|
251
|
+
const aesKey = generateRandomAESKeyString();
|
|
252
|
+
const storageData = {
|
|
253
|
+
value,
|
|
254
|
+
expire: expire ? Date.now() + expire : null,
|
|
255
|
+
timestamp: Date.now()
|
|
256
|
+
};
|
|
257
|
+
const encryptedData = encryptJsonWithAES(storageData, aesKey);
|
|
258
|
+
const encryptedKey = await encryptWithEnvPublicKey(aesKey);
|
|
259
|
+
const finalData = {
|
|
260
|
+
data: encryptedData,
|
|
261
|
+
key: encryptedKey,
|
|
262
|
+
version: "1.0"
|
|
263
|
+
};
|
|
264
|
+
this.storage.setItem(fullKey, JSON.stringify(finalData));
|
|
265
|
+
return true;
|
|
606
266
|
} catch (error) {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
throw error;
|
|
267
|
+
console.error("[SecureStorage] \u4FDD\u5B58\u5931\u8D25:", error);
|
|
268
|
+
return false;
|
|
611
269
|
}
|
|
612
|
-
}
|
|
270
|
+
}
|
|
613
271
|
/**
|
|
614
272
|
* 获取值
|
|
615
|
-
* @param key - 键
|
|
616
|
-
* @param options - 选项(默认值、私钥等)
|
|
617
|
-
* @returns Promise<T | undefined> 值或默认值
|
|
618
273
|
*/
|
|
619
|
-
async get(key,
|
|
620
|
-
if (typeof window === "undefined" || !window.localStorage) {
|
|
621
|
-
return options.defaultValue;
|
|
622
|
-
}
|
|
623
|
-
const { defaultValue, privateKey } = options;
|
|
274
|
+
async get(key, defaultValue = null) {
|
|
624
275
|
try {
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
const keyToUse = privateKey || globalKeyPair?.privateKey;
|
|
630
|
-
if (keyToUse) {
|
|
631
|
-
try {
|
|
632
|
-
decryptedStr = await decryptValue(encryptedStr, keyToUse, globalHMACKey);
|
|
633
|
-
} catch (error) {
|
|
634
|
-
try {
|
|
635
|
-
decryptedStr = await handleDecryptError(error, encryptedStr, key);
|
|
636
|
-
} catch {
|
|
637
|
-
return defaultValue;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
} else {
|
|
641
|
-
try {
|
|
642
|
-
decryptedStr = handleNoKeyDecode(encryptedStr, key);
|
|
643
|
-
} catch {
|
|
644
|
-
return defaultValue;
|
|
645
|
-
}
|
|
276
|
+
const fullKey = this._getFullKey(key);
|
|
277
|
+
const storedData = this.storage.getItem(fullKey);
|
|
278
|
+
if (!storedData) {
|
|
279
|
+
return defaultValue;
|
|
646
280
|
}
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
281
|
+
const finalData = JSON.parse(storedData);
|
|
282
|
+
const aesKey = await decryptWithEnvPrivateKey(finalData.key);
|
|
283
|
+
const storageData = decryptJsonWithAES(finalData.data, aesKey);
|
|
284
|
+
if (this._isExpired(storageData.expire)) {
|
|
285
|
+
this.remove(key);
|
|
650
286
|
return defaultValue;
|
|
651
287
|
}
|
|
652
|
-
return
|
|
288
|
+
return storageData.value;
|
|
653
289
|
} catch (error) {
|
|
654
|
-
|
|
655
|
-
console.warn(`Failed to parse storage item for key "${key}"`);
|
|
656
|
-
}
|
|
290
|
+
console.error("[SecureStorage] \u8BFB\u53D6\u5931\u8D25:", error);
|
|
657
291
|
return defaultValue;
|
|
658
292
|
}
|
|
659
|
-
}
|
|
293
|
+
}
|
|
660
294
|
/**
|
|
661
|
-
*
|
|
662
|
-
* @param key - 键
|
|
295
|
+
* 删除值
|
|
663
296
|
*/
|
|
664
297
|
remove(key) {
|
|
665
|
-
if (typeof window === "undefined" || !window.localStorage) return;
|
|
666
298
|
try {
|
|
667
|
-
|
|
299
|
+
const fullKey = this._getFullKey(key);
|
|
300
|
+
this.storage.removeItem(fullKey);
|
|
301
|
+
return true;
|
|
668
302
|
} catch (error) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
303
|
+
console.error("[SecureStorage] \u79FB\u9664\u5931\u8D25:", error);
|
|
304
|
+
return false;
|
|
672
305
|
}
|
|
673
|
-
}
|
|
674
|
-
/**
|
|
675
|
-
* 清空所有值
|
|
676
|
-
*/
|
|
677
|
-
clear() {
|
|
678
|
-
if (typeof window === "undefined" || !window.localStorage) return;
|
|
679
|
-
try {
|
|
680
|
-
window.localStorage.clear();
|
|
681
|
-
} catch (error) {
|
|
682
|
-
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
683
|
-
console.error("localStorage.clear error");
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
},
|
|
306
|
+
}
|
|
687
307
|
/**
|
|
688
|
-
*
|
|
689
|
-
* @returns 键数组
|
|
308
|
+
* 检查键是否存在
|
|
690
309
|
*/
|
|
691
|
-
|
|
692
|
-
if (typeof window === "undefined" || !window.localStorage) return [];
|
|
693
|
-
const keys = [];
|
|
310
|
+
async has(key) {
|
|
694
311
|
try {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
312
|
+
const fullKey = this._getFullKey(key);
|
|
313
|
+
const storedData = this.storage.getItem(fullKey);
|
|
314
|
+
if (!storedData) return false;
|
|
315
|
+
const finalData = JSON.parse(storedData);
|
|
316
|
+
const aesKey = await decryptWithEnvPrivateKey(finalData.key);
|
|
317
|
+
const storageData = decryptJsonWithAES(finalData.data, aesKey);
|
|
318
|
+
if (this._isExpired(storageData.expire)) {
|
|
319
|
+
this.remove(key);
|
|
320
|
+
return false;
|
|
698
321
|
}
|
|
322
|
+
return true;
|
|
699
323
|
} catch (error) {
|
|
700
|
-
|
|
701
|
-
console.error("localStorage.keys error");
|
|
702
|
-
}
|
|
324
|
+
return false;
|
|
703
325
|
}
|
|
704
|
-
return keys;
|
|
705
326
|
}
|
|
706
|
-
};
|
|
707
|
-
var sessionStorage = {
|
|
708
327
|
/**
|
|
709
|
-
*
|
|
710
|
-
* @param key - 键
|
|
711
|
-
* @param value - 值
|
|
712
|
-
* @param options - 选项(公钥等)
|
|
713
|
-
* @returns Promise<void>
|
|
328
|
+
* 清空所有数据
|
|
714
329
|
*/
|
|
715
|
-
|
|
716
|
-
if (typeof window === "undefined" || !window.sessionStorage) {
|
|
717
|
-
throw new Error("sessionStorage is not available");
|
|
718
|
-
}
|
|
719
|
-
const { publicKey } = options;
|
|
330
|
+
clear() {
|
|
720
331
|
try {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
} else {
|
|
728
|
-
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
729
|
-
console.warn(
|
|
730
|
-
`\u26A0\uFE0F SECURITY WARNING: Storing data without encryption for key "${key}". Data is only Base64 encoded, not encrypted!`
|
|
731
|
-
);
|
|
332
|
+
let count = 0;
|
|
333
|
+
const keys = [];
|
|
334
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
335
|
+
const key = this.storage.key(i);
|
|
336
|
+
if (key && key.startsWith(this.namespace)) {
|
|
337
|
+
keys.push(key);
|
|
732
338
|
}
|
|
733
|
-
const encoded = base64Encode(jsonStr);
|
|
734
|
-
window.sessionStorage.setItem(key, encoded);
|
|
735
339
|
}
|
|
340
|
+
keys.forEach((key) => {
|
|
341
|
+
this.storage.removeItem(key);
|
|
342
|
+
count++;
|
|
343
|
+
});
|
|
344
|
+
return count;
|
|
736
345
|
} catch (error) {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
}
|
|
740
|
-
throw error;
|
|
346
|
+
console.error("[SecureStorage] \u6E05\u7A7A\u5931\u8D25:", error);
|
|
347
|
+
return 0;
|
|
741
348
|
}
|
|
742
|
-
}
|
|
349
|
+
}
|
|
743
350
|
/**
|
|
744
|
-
*
|
|
745
|
-
* @param key - 键
|
|
746
|
-
* @param options - 选项(默认值、私钥等)
|
|
747
|
-
* @returns Promise<T | undefined> 值或默认值
|
|
351
|
+
* 获取所有键名
|
|
748
352
|
*/
|
|
749
|
-
|
|
750
|
-
if (typeof window === "undefined" || !window.sessionStorage) {
|
|
751
|
-
return options.defaultValue;
|
|
752
|
-
}
|
|
753
|
-
const { defaultValue, privateKey } = options;
|
|
353
|
+
keys() {
|
|
754
354
|
try {
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
if (keyToUse) {
|
|
761
|
-
try {
|
|
762
|
-
decryptedStr = await decryptValue(encryptedStr, keyToUse, globalHMACKey);
|
|
763
|
-
} catch (error) {
|
|
764
|
-
try {
|
|
765
|
-
decryptedStr = await handleDecryptError(error, encryptedStr, key);
|
|
766
|
-
} catch {
|
|
767
|
-
return defaultValue;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
} else {
|
|
771
|
-
try {
|
|
772
|
-
decryptedStr = handleNoKeyDecode(encryptedStr, key);
|
|
773
|
-
} catch {
|
|
774
|
-
return defaultValue;
|
|
355
|
+
const keys = [];
|
|
356
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
357
|
+
const key = this.storage.key(i);
|
|
358
|
+
if (key && key.startsWith(this.namespace)) {
|
|
359
|
+
keys.push(key.substring(this.namespace.length));
|
|
775
360
|
}
|
|
776
361
|
}
|
|
777
|
-
return
|
|
778
|
-
} catch {
|
|
779
|
-
return defaultValue;
|
|
780
|
-
}
|
|
781
|
-
},
|
|
782
|
-
/**
|
|
783
|
-
* 移除值
|
|
784
|
-
* @param key - 键
|
|
785
|
-
*/
|
|
786
|
-
remove(key) {
|
|
787
|
-
if (typeof window === "undefined" || !window.sessionStorage) return;
|
|
788
|
-
try {
|
|
789
|
-
window.sessionStorage.removeItem(key);
|
|
790
|
-
} catch (error) {
|
|
791
|
-
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
792
|
-
console.error("sessionStorage.remove error");
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
},
|
|
796
|
-
/**
|
|
797
|
-
* 清空所有值
|
|
798
|
-
*/
|
|
799
|
-
clear() {
|
|
800
|
-
if (typeof window === "undefined" || !window.sessionStorage) return;
|
|
801
|
-
try {
|
|
802
|
-
window.sessionStorage.clear();
|
|
362
|
+
return keys;
|
|
803
363
|
} catch (error) {
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
}
|
|
364
|
+
console.error("[SecureStorage] \u83B7\u53D6\u952E\u540D\u5931\u8D25:", error);
|
|
365
|
+
return [];
|
|
807
366
|
}
|
|
808
367
|
}
|
|
809
|
-
};
|
|
810
|
-
var cookie = {
|
|
811
|
-
/**
|
|
812
|
-
* 设置Cookie
|
|
813
|
-
* @param key - 键
|
|
814
|
-
* @param value - 值
|
|
815
|
-
* @param options - Cookie选项
|
|
816
|
-
*/
|
|
817
|
-
set(key, value, options = {}) {
|
|
818
|
-
if (typeof document === "undefined") {
|
|
819
|
-
throw new Error("document is not available");
|
|
820
|
-
}
|
|
821
|
-
let cookieStr = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
|
822
|
-
if (options.expires) {
|
|
823
|
-
const expiresDate = options.expires instanceof Date ? options.expires : new Date(Date.now() + options.expires * 24 * 60 * 60 * 1e3);
|
|
824
|
-
cookieStr += `; expires=${expiresDate.toUTCString()}`;
|
|
825
|
-
}
|
|
826
|
-
if (options.path) cookieStr += `; path=${options.path}`;
|
|
827
|
-
if (options.domain) cookieStr += `; domain=${options.domain}`;
|
|
828
|
-
if (options.secure) cookieStr += "; secure";
|
|
829
|
-
if (options.sameSite) cookieStr += `; sameSite=${options.sameSite}`;
|
|
830
|
-
document.cookie = cookieStr;
|
|
831
|
-
},
|
|
832
|
-
/**
|
|
833
|
-
* 获取Cookie
|
|
834
|
-
* @param key - 键
|
|
835
|
-
* @returns Cookie值
|
|
836
|
-
*/
|
|
837
|
-
get(key) {
|
|
838
|
-
if (typeof document === "undefined") return void 0;
|
|
839
|
-
const name = encodeURIComponent(key);
|
|
840
|
-
const cookies = document.cookie.split(";");
|
|
841
|
-
for (const cookieStr of cookies) {
|
|
842
|
-
const trimmed = cookieStr.trim();
|
|
843
|
-
const eqIndex = trimmed.indexOf("=");
|
|
844
|
-
if (eqIndex === -1) continue;
|
|
845
|
-
const cookieKey = trimmed.substring(0, eqIndex).trim();
|
|
846
|
-
const cookieValue = trimmed.substring(eqIndex + 1).trim();
|
|
847
|
-
if (cookieKey === name) {
|
|
848
|
-
const decoded = decodeURIComponent(cookieValue);
|
|
849
|
-
return decoded === "" ? void 0 : decoded;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
return void 0;
|
|
853
|
-
},
|
|
854
|
-
/**
|
|
855
|
-
* 移除Cookie
|
|
856
|
-
* @param key - 键
|
|
857
|
-
* @param options - Cookie选项
|
|
858
|
-
*/
|
|
859
|
-
remove(key, options = {}) {
|
|
860
|
-
cookie.set(key, "", {
|
|
861
|
-
...options,
|
|
862
|
-
expires: /* @__PURE__ */ new Date(0)
|
|
863
|
-
});
|
|
864
|
-
},
|
|
865
368
|
/**
|
|
866
|
-
*
|
|
867
|
-
* @returns Cookie对象
|
|
369
|
+
* 获取数据条数
|
|
868
370
|
*/
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
const cookies = {};
|
|
872
|
-
document.cookie.split(";").forEach((cookieStr) => {
|
|
873
|
-
const trimmed = cookieStr.trim();
|
|
874
|
-
if (!trimmed) return;
|
|
875
|
-
const eqIndex = trimmed.indexOf("=");
|
|
876
|
-
if (eqIndex === -1) return;
|
|
877
|
-
const key = trimmed.substring(0, eqIndex).trim();
|
|
878
|
-
const value = trimmed.substring(eqIndex + 1).trim();
|
|
879
|
-
if (key && value) {
|
|
880
|
-
const decodedKey = decodeURIComponent(key);
|
|
881
|
-
const decodedValue = decodeURIComponent(value);
|
|
882
|
-
if (decodedValue !== "") {
|
|
883
|
-
cookies[decodedKey] = decodedValue;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
});
|
|
887
|
-
return cookies;
|
|
371
|
+
size() {
|
|
372
|
+
return this.keys().length;
|
|
888
373
|
}
|
|
889
|
-
};
|
|
890
|
-
var secureStorage = {
|
|
891
374
|
/**
|
|
892
|
-
*
|
|
893
|
-
* @param key - 键
|
|
894
|
-
* @param value - 值
|
|
895
|
-
* @param options - 选项(过期时间、公钥等)
|
|
896
|
-
* @returns Promise<void>
|
|
375
|
+
* 清理过期数据
|
|
897
376
|
*/
|
|
898
|
-
async
|
|
377
|
+
async clearExpired() {
|
|
899
378
|
try {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
379
|
+
let count = 0;
|
|
380
|
+
const keys = this.keys();
|
|
381
|
+
for (const key of keys) {
|
|
382
|
+
const fullKey = this._getFullKey(key);
|
|
383
|
+
try {
|
|
384
|
+
const storedData = this.storage.getItem(fullKey);
|
|
385
|
+
if (storedData) {
|
|
386
|
+
const finalData = JSON.parse(storedData);
|
|
387
|
+
const aesKey = await decryptWithEnvPrivateKey(finalData.key);
|
|
388
|
+
const storageData = decryptJsonWithAES(finalData.data, aesKey);
|
|
389
|
+
if (this._isExpired(storageData.expire)) {
|
|
390
|
+
this.remove(key);
|
|
391
|
+
count++;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
} catch (error) {
|
|
395
|
+
this.remove(key);
|
|
396
|
+
count++;
|
|
907
397
|
}
|
|
908
398
|
}
|
|
399
|
+
return count;
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.error("[SecureStorage] \u6E05\u7406\u8FC7\u671F\u6570\u636E\u5931\u8D25:", error);
|
|
402
|
+
return 0;
|
|
909
403
|
}
|
|
910
|
-
}
|
|
911
|
-
/**
|
|
912
|
-
* 获取值
|
|
913
|
-
* @param key - 键
|
|
914
|
-
* @param options - 选项(默认值、私钥等)
|
|
915
|
-
* @returns Promise<T | undefined> 值或默认值
|
|
916
|
-
*/
|
|
917
|
-
async get(key, options = {}) {
|
|
918
|
-
const localValue = await localStorage.get(key, options);
|
|
919
|
-
if (localValue !== void 0) return localValue;
|
|
920
|
-
const sessionValue = await sessionStorage.get(key, options);
|
|
921
|
-
return sessionValue !== void 0 ? sessionValue : options.defaultValue;
|
|
922
|
-
},
|
|
923
|
-
/**
|
|
924
|
-
* 移除值
|
|
925
|
-
* @param key - 键
|
|
926
|
-
*/
|
|
927
|
-
remove(key) {
|
|
928
|
-
localStorage.remove(key);
|
|
929
|
-
sessionStorage.remove(key);
|
|
930
|
-
},
|
|
404
|
+
}
|
|
931
405
|
/**
|
|
932
|
-
*
|
|
406
|
+
* 获取实例的唯一标识
|
|
933
407
|
*/
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
},
|
|
408
|
+
getInstanceKey() {
|
|
409
|
+
return this.instanceKey;
|
|
410
|
+
}
|
|
938
411
|
/**
|
|
939
|
-
*
|
|
940
|
-
* @returns 键名数组
|
|
412
|
+
* 检查是否为单例实例
|
|
941
413
|
*/
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
const sessionKeys = [];
|
|
945
|
-
if (typeof window !== "undefined" && window.sessionStorage) {
|
|
946
|
-
try {
|
|
947
|
-
for (let i = 0; i < window.sessionStorage.length; i++) {
|
|
948
|
-
const key = window.sessionStorage.key(i);
|
|
949
|
-
if (key) sessionKeys.push(key);
|
|
950
|
-
}
|
|
951
|
-
} catch (error) {
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
return Array.from(/* @__PURE__ */ new Set([...localKeys, ...sessionKeys]));
|
|
414
|
+
isSingleton() {
|
|
415
|
+
return _SecureStorage.instances.has(this.instanceKey);
|
|
955
416
|
}
|
|
956
417
|
};
|
|
418
|
+
var _defaultSecureStorage = null;
|
|
419
|
+
function _getDefaultSecureStorage() {
|
|
420
|
+
if (typeof window === "undefined") {
|
|
421
|
+
throw new Error("[SecureStorage] secureStorage is only available in browser environments.");
|
|
422
|
+
}
|
|
423
|
+
if (!_defaultSecureStorage) {
|
|
424
|
+
_defaultSecureStorage = new SecureStorage({});
|
|
425
|
+
}
|
|
426
|
+
return _defaultSecureStorage;
|
|
427
|
+
}
|
|
428
|
+
var secureStorage = new Proxy({}, {
|
|
429
|
+
get(_target, prop) {
|
|
430
|
+
const instance = _getDefaultSecureStorage();
|
|
431
|
+
const value = instance[prop];
|
|
432
|
+
if (typeof value === "function") {
|
|
433
|
+
return value.bind(instance);
|
|
434
|
+
}
|
|
435
|
+
return value;
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
var SecureStorage_default = SecureStorage;
|
|
957
439
|
|
|
958
|
-
exports.
|
|
959
|
-
exports.
|
|
960
|
-
exports.getKeyUsageCount = getKeyUsageCount;
|
|
961
|
-
exports.getStorageKeyPair = getStorageKeyPair;
|
|
962
|
-
exports.initializeStorageKeys = initializeStorageKeys;
|
|
963
|
-
exports.resetKeyUsageCount = resetKeyUsageCount;
|
|
440
|
+
exports.SecureStorage = SecureStorage;
|
|
441
|
+
exports.default = SecureStorage_default;
|
|
964
442
|
exports.secureStorage = secureStorage;
|
|
965
|
-
exports.setStorageKeyPair = setStorageKeyPair;
|