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