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