decane-connect-kit 2.24.1 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +160 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +166 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2416,6 +2416,54 @@ var import_browser = require("@simplewebauthn/browser");
|
|
|
2416
2416
|
|
|
2417
2417
|
// src/social/attestation.ts
|
|
2418
2418
|
var import_dcap_qvl = require("@phala/dcap-qvl");
|
|
2419
|
+
|
|
2420
|
+
// src/social/collateralCache.ts
|
|
2421
|
+
var CACHE_PREFIX = "decane:collateral:v1:";
|
|
2422
|
+
var COLLATERAL_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
2423
|
+
function storage() {
|
|
2424
|
+
try {
|
|
2425
|
+
return typeof localStorage !== "undefined" ? localStorage : null;
|
|
2426
|
+
} catch {
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
function keyFor(teeUrl) {
|
|
2431
|
+
return `${CACHE_PREFIX}${teeUrl}`;
|
|
2432
|
+
}
|
|
2433
|
+
function readCollateral(teeUrl) {
|
|
2434
|
+
const store = storage();
|
|
2435
|
+
if (!store) return null;
|
|
2436
|
+
try {
|
|
2437
|
+
const raw = store.getItem(keyFor(teeUrl));
|
|
2438
|
+
if (!raw) return null;
|
|
2439
|
+
const entry = JSON.parse(raw);
|
|
2440
|
+
if (!entry?.at || Date.now() - entry.at > COLLATERAL_TTL_MS) {
|
|
2441
|
+
store.removeItem(keyFor(teeUrl));
|
|
2442
|
+
return null;
|
|
2443
|
+
}
|
|
2444
|
+
return entry.collateral ?? null;
|
|
2445
|
+
} catch {
|
|
2446
|
+
return null;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
function writeCollateral(teeUrl, collateral) {
|
|
2450
|
+
const store = storage();
|
|
2451
|
+
if (!store) return;
|
|
2452
|
+
try {
|
|
2453
|
+
store.setItem(keyFor(teeUrl), JSON.stringify({ at: Date.now(), collateral }));
|
|
2454
|
+
} catch {
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
function dropCollateral(teeUrl) {
|
|
2458
|
+
const store = storage();
|
|
2459
|
+
if (!store) return;
|
|
2460
|
+
try {
|
|
2461
|
+
store.removeItem(keyFor(teeUrl));
|
|
2462
|
+
} catch {
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
// src/social/attestation.ts
|
|
2419
2467
|
init_errors();
|
|
2420
2468
|
|
|
2421
2469
|
// ../../infra/tee-measurement.json
|
|
@@ -2443,6 +2491,9 @@ function hexToBytes(hex) {
|
|
|
2443
2491
|
function bytesToHex(bytes) {
|
|
2444
2492
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2445
2493
|
}
|
|
2494
|
+
function nowSeconds() {
|
|
2495
|
+
return Math.floor(Date.now() / 1e3);
|
|
2496
|
+
}
|
|
2446
2497
|
async function verifyTeeAttestation(teeUrl, nonce) {
|
|
2447
2498
|
const res = await fetch(`${teeUrl}/attestation`, {
|
|
2448
2499
|
method: "POST",
|
|
@@ -2452,9 +2503,22 @@ async function verifyTeeAttestation(teeUrl, nonce) {
|
|
|
2452
2503
|
if (!res.ok) throw new AttestationError("quote fetch failed");
|
|
2453
2504
|
const { quote, measurement } = await res.json();
|
|
2454
2505
|
let verified;
|
|
2506
|
+
const cached = readCollateral(teeUrl);
|
|
2455
2507
|
try {
|
|
2456
|
-
|
|
2457
|
-
|
|
2508
|
+
if (cached) {
|
|
2509
|
+
try {
|
|
2510
|
+
verified = await (0, import_dcap_qvl.verify)(hexToBytes(quote), cached, nowSeconds());
|
|
2511
|
+
} catch {
|
|
2512
|
+
dropCollateral(teeUrl);
|
|
2513
|
+
verified = await (0, import_dcap_qvl.getCollateralAndVerify)(hexToBytes(quote));
|
|
2514
|
+
}
|
|
2515
|
+
} else {
|
|
2516
|
+
const collateral = await (0, import_dcap_qvl.getCollateral)(import_dcap_qvl.PHALA_PCCS_URL, hexToBytes(quote));
|
|
2517
|
+
verified = await (0, import_dcap_qvl.verify)(hexToBytes(quote), collateral, nowSeconds());
|
|
2518
|
+
writeCollateral(teeUrl, collateral);
|
|
2519
|
+
}
|
|
2520
|
+
} catch (e) {
|
|
2521
|
+
if (e instanceof AttestationError) throw e;
|
|
2458
2522
|
throw new AttestationError("quote signature invalid");
|
|
2459
2523
|
}
|
|
2460
2524
|
if (verified.status === "Revoked") {
|
|
@@ -2496,14 +2560,51 @@ async function teeFetch(url, init) {
|
|
|
2496
2560
|
}
|
|
2497
2561
|
var ATTESTATION_TTL_MS = 10 * 6e4;
|
|
2498
2562
|
var attestationCache = /* @__PURE__ */ new Map();
|
|
2563
|
+
var ATTESTATION_PERSIST_PREFIX = "decane:attested:v1:";
|
|
2564
|
+
function persistedAt(teeUrl) {
|
|
2565
|
+
try {
|
|
2566
|
+
if (typeof sessionStorage === "undefined") return null;
|
|
2567
|
+
const raw = sessionStorage.getItem(ATTESTATION_PERSIST_PREFIX + teeUrl);
|
|
2568
|
+
const at = raw ? Number(raw) : NaN;
|
|
2569
|
+
return Number.isFinite(at) ? at : null;
|
|
2570
|
+
} catch {
|
|
2571
|
+
return null;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
function rememberAttested(teeUrl, at) {
|
|
2575
|
+
try {
|
|
2576
|
+
if (typeof sessionStorage !== "undefined") {
|
|
2577
|
+
sessionStorage.setItem(ATTESTATION_PERSIST_PREFIX + teeUrl, String(at));
|
|
2578
|
+
}
|
|
2579
|
+
} catch {
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
function forgetAttested(teeUrl) {
|
|
2583
|
+
try {
|
|
2584
|
+
if (typeof sessionStorage !== "undefined") {
|
|
2585
|
+
sessionStorage.removeItem(ATTESTATION_PERSIST_PREFIX + teeUrl);
|
|
2586
|
+
}
|
|
2587
|
+
} catch {
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2499
2590
|
function attestOnce(teeUrl, verify) {
|
|
2500
2591
|
const hit = attestationCache.get(teeUrl);
|
|
2501
2592
|
if (hit && Date.now() - hit.at < ATTESTATION_TTL_MS) return hit.promise;
|
|
2502
|
-
const
|
|
2593
|
+
const at = persistedAt(teeUrl);
|
|
2594
|
+
if (at && Date.now() - at < ATTESTATION_TTL_MS) {
|
|
2595
|
+
const resolved = Promise.resolve();
|
|
2596
|
+
attestationCache.set(teeUrl, { at, promise: resolved });
|
|
2597
|
+
return resolved;
|
|
2598
|
+
}
|
|
2599
|
+
const startedAt = Date.now();
|
|
2600
|
+
const promise = verify().then(() => {
|
|
2601
|
+
rememberAttested(teeUrl, startedAt);
|
|
2602
|
+
}).catch((e) => {
|
|
2503
2603
|
attestationCache.delete(teeUrl);
|
|
2604
|
+
forgetAttested(teeUrl);
|
|
2504
2605
|
throw e;
|
|
2505
2606
|
});
|
|
2506
|
-
attestationCache.set(teeUrl, { at:
|
|
2607
|
+
attestationCache.set(teeUrl, { at: startedAt, promise });
|
|
2507
2608
|
return promise;
|
|
2508
2609
|
}
|
|
2509
2610
|
function prewarmAttestation(teeUrl) {
|
|
@@ -3775,29 +3876,44 @@ function clearLastUser(key) {
|
|
|
3775
3876
|
function sessionPersistKey(apiKey) {
|
|
3776
3877
|
return `decane:sess:${apiKey.slice(0, 20)}`;
|
|
3777
3878
|
}
|
|
3879
|
+
var sessionStore = () => typeof sessionStorage !== "undefined" ? sessionStorage : null;
|
|
3880
|
+
function useCrossTabSessionStore(enabled) {
|
|
3881
|
+
sessionStore = enabled ? () => typeof localStorage !== "undefined" ? localStorage : null : () => typeof sessionStorage !== "undefined" ? sessionStorage : null;
|
|
3882
|
+
}
|
|
3778
3883
|
function loadSession(key) {
|
|
3779
3884
|
try {
|
|
3780
|
-
const raw = typeof sessionStorage !== "undefined" ? sessionStorage.getItem(key) : null;
|
|
3781
|
-
|
|
3885
|
+
const raw = sessionStore()?.getItem(key) ?? (typeof sessionStorage !== "undefined" ? sessionStorage.getItem(key) : null) ?? (typeof localStorage !== "undefined" ? localStorage.getItem(key) : null);
|
|
3886
|
+
if (!raw) return null;
|
|
3887
|
+
const parsed = JSON.parse(raw);
|
|
3888
|
+
if (parsed?.expiresAt && parsed.expiresAt <= Date.now()) {
|
|
3889
|
+
clearSessionPersist(key);
|
|
3890
|
+
return null;
|
|
3891
|
+
}
|
|
3892
|
+
return parsed;
|
|
3782
3893
|
} catch {
|
|
3783
3894
|
return null;
|
|
3784
3895
|
}
|
|
3785
3896
|
}
|
|
3786
3897
|
function saveSession(key, s) {
|
|
3787
3898
|
try {
|
|
3788
|
-
|
|
3899
|
+
sessionStore()?.setItem(key, JSON.stringify(s));
|
|
3789
3900
|
} catch {
|
|
3790
3901
|
}
|
|
3791
3902
|
}
|
|
3792
3903
|
function clearSessionPersist(key) {
|
|
3793
3904
|
try {
|
|
3794
|
-
sessionStorage.removeItem(key);
|
|
3905
|
+
if (typeof sessionStorage !== "undefined") sessionStorage.removeItem(key);
|
|
3906
|
+
} catch {
|
|
3907
|
+
}
|
|
3908
|
+
try {
|
|
3909
|
+
if (typeof localStorage !== "undefined") localStorage.removeItem(key);
|
|
3795
3910
|
} catch {
|
|
3796
3911
|
}
|
|
3797
3912
|
}
|
|
3798
3913
|
async function createDecaneConnect(config) {
|
|
3799
3914
|
if (!config.appId) throw new Error("decane-connect-kit: appId is required");
|
|
3800
3915
|
if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
|
|
3916
|
+
useCrossTabSessionStore(config.resumeSessionAcrossTabs === true);
|
|
3801
3917
|
if (!config.chains?.length)
|
|
3802
3918
|
throw new Error("decane-connect-kit: at least one chain is required");
|
|
3803
3919
|
if (config.rotateOnNewDeviceRecovery && !config.onRecoveryRotated) {
|
|
@@ -3816,7 +3932,7 @@ async function createDecaneConnect(config) {
|
|
|
3816
3932
|
const teeUrl = config.teeUrl ?? DEFAULT_TEE_URL;
|
|
3817
3933
|
const events = new EventEmitter();
|
|
3818
3934
|
const api = new ApiClient(baseUrl, config.apiKey, config.appId);
|
|
3819
|
-
const
|
|
3935
|
+
const storage2 = new Storage();
|
|
3820
3936
|
const session = new SessionManager(
|
|
3821
3937
|
events,
|
|
3822
3938
|
durationMs,
|
|
@@ -3959,7 +4075,7 @@ async function createDecaneConnect(config) {
|
|
|
3959
4075
|
}
|
|
3960
4076
|
async function attachUnlockToken(userId, jwt, tokenKey) {
|
|
3961
4077
|
try {
|
|
3962
|
-
const stored = await
|
|
4078
|
+
const stored = await storage2.getShare(userId);
|
|
3963
4079
|
if (!stored) return;
|
|
3964
4080
|
const { token } = await api.issueUnlockToken(jwt);
|
|
3965
4081
|
const unlockTokenIv = generateRandomBytes(12);
|
|
@@ -3968,13 +4084,13 @@ async function createDecaneConnect(config) {
|
|
|
3968
4084
|
new TextEncoder().encode(token),
|
|
3969
4085
|
unlockTokenIv
|
|
3970
4086
|
);
|
|
3971
|
-
await
|
|
4087
|
+
await storage2.putShare({ ...stored, wrappedUnlockToken, unlockTokenIv });
|
|
3972
4088
|
} catch (e) {
|
|
3973
4089
|
console.warn("decane-connect-kit: could not store an unlock token for this device.", e);
|
|
3974
4090
|
}
|
|
3975
4091
|
}
|
|
3976
4092
|
async function persistRotatedDeviceShare(userId, deviceShare) {
|
|
3977
|
-
const stored = await
|
|
4093
|
+
const stored = await storage2.getShare(userId);
|
|
3978
4094
|
if (!stored) {
|
|
3979
4095
|
throw new CallSequenceError(
|
|
3980
4096
|
"Share set was rotated but this device has no stored share to update. Recover the wallet on this device before rotating."
|
|
@@ -3983,21 +4099,21 @@ async function createDecaneConnect(config) {
|
|
|
3983
4099
|
const wrappingKey = await getWrappingKey(stored);
|
|
3984
4100
|
const iv = generateRandomBytes(12);
|
|
3985
4101
|
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
3986
|
-
await
|
|
4102
|
+
await storage2.putShare({ ...stored, wrappedDeviceShare, iv });
|
|
3987
4103
|
}
|
|
3988
4104
|
function hasPasswordUnlock(stored) {
|
|
3989
4105
|
return !!stored && stored.unlockMethod === "password" && !!stored.passwordSalt && !!stored.wrappedUnlockToken && !!stored.unlockTokenIv;
|
|
3990
4106
|
}
|
|
3991
4107
|
async function passwordDeviceCandidate() {
|
|
3992
4108
|
if (persisted?.userId) {
|
|
3993
|
-
const stored = await
|
|
4109
|
+
const stored = await storage2.getShare(persisted.userId);
|
|
3994
4110
|
return hasPasswordUnlock(stored) ? stored : null;
|
|
3995
4111
|
}
|
|
3996
4112
|
if (lastUserId) {
|
|
3997
|
-
const stored = await
|
|
4113
|
+
const stored = await storage2.getShare(lastUserId);
|
|
3998
4114
|
return hasPasswordUnlock(stored) ? stored : null;
|
|
3999
4115
|
}
|
|
4000
|
-
const candidates = (await
|
|
4116
|
+
const candidates = (await storage2.listShares()).filter(hasPasswordUnlock);
|
|
4001
4117
|
return candidates.length === 1 ? candidates[0] : null;
|
|
4002
4118
|
}
|
|
4003
4119
|
async function getWrappingKey(stored) {
|
|
@@ -4025,7 +4141,7 @@ async function createDecaneConnect(config) {
|
|
|
4025
4141
|
let credentialId;
|
|
4026
4142
|
let prfOutput;
|
|
4027
4143
|
try {
|
|
4028
|
-
const label = (await
|
|
4144
|
+
const label = (await storage2.getShare(userId))?.profile?.email;
|
|
4029
4145
|
({ credentialId, prfOutput } = await registerWebAuthnCredential({
|
|
4030
4146
|
api,
|
|
4031
4147
|
jwt,
|
|
@@ -4057,8 +4173,8 @@ async function createDecaneConnect(config) {
|
|
|
4057
4173
|
const wrappingKey = await importAesKey(prfOutput);
|
|
4058
4174
|
const iv = generateRandomBytes(12);
|
|
4059
4175
|
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
4060
|
-
const existing = await
|
|
4061
|
-
await
|
|
4176
|
+
const existing = await storage2.getShare(userId);
|
|
4177
|
+
await storage2.putShare({
|
|
4062
4178
|
...existing,
|
|
4063
4179
|
userId,
|
|
4064
4180
|
wrappedDeviceShare,
|
|
@@ -4142,7 +4258,7 @@ async function createDecaneConnect(config) {
|
|
|
4142
4258
|
const { wrappingKey, tokenKey, meta } = await passwordSetup();
|
|
4143
4259
|
const iv = generateRandomBytes(12);
|
|
4144
4260
|
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
4145
|
-
await
|
|
4261
|
+
await storage2.putShare({
|
|
4146
4262
|
userId,
|
|
4147
4263
|
wrappedDeviceShare,
|
|
4148
4264
|
iv,
|
|
@@ -4216,7 +4332,7 @@ async function createDecaneConnect(config) {
|
|
|
4216
4332
|
shareSet.device,
|
|
4217
4333
|
iv
|
|
4218
4334
|
);
|
|
4219
|
-
await
|
|
4335
|
+
await storage2.putShare({
|
|
4220
4336
|
userId,
|
|
4221
4337
|
wrappedDeviceShare,
|
|
4222
4338
|
iv,
|
|
@@ -4320,7 +4436,7 @@ async function createDecaneConnect(config) {
|
|
|
4320
4436
|
async function returningUserUnlock(userId, jwt) {
|
|
4321
4437
|
const rawAddresses = await api.getAddresses(jwt);
|
|
4322
4438
|
const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana, tron: rawAddresses.tron ?? evmToTronAddress2(rawAddresses.evm) } : null;
|
|
4323
|
-
const stored = await
|
|
4439
|
+
const stored = await storage2.getShare(userId);
|
|
4324
4440
|
let deviceShare;
|
|
4325
4441
|
let joinedViaPasskeySync = false;
|
|
4326
4442
|
if (!stored) {
|
|
@@ -4359,7 +4475,7 @@ async function createDecaneConnect(config) {
|
|
|
4359
4475
|
rpId,
|
|
4360
4476
|
teeUrl,
|
|
4361
4477
|
api,
|
|
4362
|
-
storage,
|
|
4478
|
+
storage: storage2,
|
|
4363
4479
|
onProgress: config.onRecoveryProgress,
|
|
4364
4480
|
promptForRecoveryFile: config.promptForRecoveryFile,
|
|
4365
4481
|
onNewRecoveryShare: async (recoveryShare, shareSetVersion) => {
|
|
@@ -4373,7 +4489,7 @@ async function createDecaneConnect(config) {
|
|
|
4373
4489
|
});
|
|
4374
4490
|
deviceShare = recovered.deviceShare;
|
|
4375
4491
|
joinedViaPasskeySync = recovered.flow === "passkey-sync";
|
|
4376
|
-
if (!deferDeviceProtection && !await
|
|
4492
|
+
if (!deferDeviceProtection && !await storage2.getShare(userId) && isPasskeySupported()) {
|
|
4377
4493
|
await registerPasskeyAndBackup(userId, jwt, deviceShare);
|
|
4378
4494
|
}
|
|
4379
4495
|
} else {
|
|
@@ -4394,7 +4510,7 @@ async function createDecaneConnect(config) {
|
|
|
4394
4510
|
} catch (e) {
|
|
4395
4511
|
if (e instanceof StaleDeviceShareError) {
|
|
4396
4512
|
zeroize(deviceShare);
|
|
4397
|
-
await
|
|
4513
|
+
await storage2.deleteShare(userId);
|
|
4398
4514
|
if (stored) return returningUserUnlock(userId, jwt);
|
|
4399
4515
|
} else {
|
|
4400
4516
|
zeroize(deviceShare);
|
|
@@ -4418,7 +4534,7 @@ async function createDecaneConnect(config) {
|
|
|
4418
4534
|
const result = await tee.rotateShares(jwt);
|
|
4419
4535
|
try {
|
|
4420
4536
|
await persistRotatedDeviceShare(userId, result.deviceShare);
|
|
4421
|
-
const refreshed = await
|
|
4537
|
+
const refreshed = await storage2.getShare(userId);
|
|
4422
4538
|
if (refreshed?.unlockMethod === "passkey" && refreshed.credentialId && refreshed.prfSalt) {
|
|
4423
4539
|
await api.putRecoveryBackup(jwt, {
|
|
4424
4540
|
wrappedDeviceShare: refreshed.wrappedDeviceShare,
|
|
@@ -4451,13 +4567,13 @@ async function createDecaneConnect(config) {
|
|
|
4451
4567
|
}
|
|
4452
4568
|
async function passkeyLoginCandidate() {
|
|
4453
4569
|
if (!persisted?.userId || !isPasskeySupported()) return null;
|
|
4454
|
-
return usablePasskeyShare(await
|
|
4570
|
+
return usablePasskeyShare(await storage2.getShare(persisted.userId));
|
|
4455
4571
|
}
|
|
4456
4572
|
async function passkeyDeviceCandidate() {
|
|
4457
4573
|
if (!isPasskeySupported()) return null;
|
|
4458
4574
|
if (persisted?.userId) return passkeyLoginCandidate();
|
|
4459
|
-
if (lastUserId) return usablePasskeyShare(await
|
|
4460
|
-
const candidates = (await
|
|
4575
|
+
if (lastUserId) return usablePasskeyShare(await storage2.getShare(lastUserId));
|
|
4576
|
+
const candidates = (await storage2.listShares()).map(usablePasskeyShare).filter(Boolean);
|
|
4461
4577
|
return candidates.length === 1 ? candidates[0] : null;
|
|
4462
4578
|
}
|
|
4463
4579
|
async function canOfferPasskey() {
|
|
@@ -4465,15 +4581,15 @@ async function createDecaneConnect(config) {
|
|
|
4465
4581
|
if (await passkeyDeviceCandidate()) return true;
|
|
4466
4582
|
const known = persisted?.userId ?? lastUserId;
|
|
4467
4583
|
if (!known) return false;
|
|
4468
|
-
return await
|
|
4584
|
+
return await storage2.getShare(known) == null;
|
|
4469
4585
|
}
|
|
4470
4586
|
async function rememberProfile(userId, profile) {
|
|
4471
4587
|
const incoming = Object.fromEntries(Object.entries(profile).filter(([, v]) => v));
|
|
4472
4588
|
if (!Object.keys(incoming).length) return;
|
|
4473
4589
|
try {
|
|
4474
|
-
const stored = await
|
|
4590
|
+
const stored = await storage2.getShare(userId);
|
|
4475
4591
|
if (!stored) return;
|
|
4476
|
-
await
|
|
4592
|
+
await storage2.putShare({ ...stored, profile: { ...stored.profile, ...incoming } });
|
|
4477
4593
|
} catch {
|
|
4478
4594
|
}
|
|
4479
4595
|
}
|
|
@@ -4497,7 +4613,7 @@ async function createDecaneConnect(config) {
|
|
|
4497
4613
|
savePersisted(PERSIST_KEY, persisted);
|
|
4498
4614
|
events.emit("connected");
|
|
4499
4615
|
persistLiveSession();
|
|
4500
|
-
const stored = await
|
|
4616
|
+
const stored = await storage2.getShare(userId);
|
|
4501
4617
|
return {
|
|
4502
4618
|
addresses,
|
|
4503
4619
|
isNewUser: false,
|
|
@@ -4670,7 +4786,7 @@ async function createDecaneConnect(config) {
|
|
|
4670
4786
|
events.emit("connected");
|
|
4671
4787
|
persistLiveSession();
|
|
4672
4788
|
scheduleAccessTokenExpiry(token, claims.expiresAt);
|
|
4673
|
-
const stored = await
|
|
4789
|
+
const stored = await storage2.getShare(claims.userId);
|
|
4674
4790
|
return {
|
|
4675
4791
|
addresses,
|
|
4676
4792
|
isNewUser: false,
|
|
@@ -4700,7 +4816,7 @@ async function createDecaneConnect(config) {
|
|
|
4700
4816
|
const candidate = await passkeyDeviceCandidate();
|
|
4701
4817
|
if (candidate) return passkeyLogin(candidate);
|
|
4702
4818
|
const known = persisted?.userId ?? lastUserId;
|
|
4703
|
-
if (isPasskeySupported() && known && await
|
|
4819
|
+
if (isPasskeySupported() && known && await storage2.getShare(known) == null) {
|
|
4704
4820
|
return passkeyLoginWithoutShare(known);
|
|
4705
4821
|
}
|
|
4706
4822
|
throw new NewDeviceError();
|
|
@@ -4719,7 +4835,7 @@ async function createDecaneConnect(config) {
|
|
|
4719
4835
|
async setUnlockPassword(password) {
|
|
4720
4836
|
if (!auth) throw new ReconnectRequiredError();
|
|
4721
4837
|
validateUnlockPassword(password);
|
|
4722
|
-
const stored = await
|
|
4838
|
+
const stored = await storage2.getShare(auth.userId);
|
|
4723
4839
|
if (!stored) throw new NewDeviceError();
|
|
4724
4840
|
const deviceShare = await aesGcmDecrypt(
|
|
4725
4841
|
await getWrappingKey(stored),
|
|
@@ -4738,7 +4854,7 @@ async function createDecaneConnect(config) {
|
|
|
4738
4854
|
);
|
|
4739
4855
|
const iv = generateRandomBytes(12);
|
|
4740
4856
|
const wrappedDeviceShare = await aesGcmEncrypt(shareKey, deviceShare, iv);
|
|
4741
|
-
await
|
|
4857
|
+
await storage2.putShare({
|
|
4742
4858
|
...stored,
|
|
4743
4859
|
unlockMethod: "password",
|
|
4744
4860
|
wrappedDeviceShare,
|
|
@@ -4818,9 +4934,9 @@ async function createDecaneConnect(config) {
|
|
|
4818
4934
|
async removeUnlockPassword() {
|
|
4819
4935
|
if (!auth) throw new ReconnectRequiredError();
|
|
4820
4936
|
await api.revokeUnlockTokens(auth.jwt);
|
|
4821
|
-
const stored = await
|
|
4937
|
+
const stored = await storage2.getShare(auth.userId);
|
|
4822
4938
|
if (!stored || stored.unlockMethod !== "password") return;
|
|
4823
|
-
await
|
|
4939
|
+
await storage2.putShare({
|
|
4824
4940
|
...stored,
|
|
4825
4941
|
wrappedUnlockToken: void 0,
|
|
4826
4942
|
unlockTokenIv: void 0
|
|
@@ -4828,13 +4944,13 @@ async function createDecaneConnect(config) {
|
|
|
4828
4944
|
},
|
|
4829
4945
|
async hasPasskey() {
|
|
4830
4946
|
if (!auth) return false;
|
|
4831
|
-
const stored = await
|
|
4947
|
+
const stored = await storage2.getShare(auth.userId);
|
|
4832
4948
|
return stored?.unlockMethod === "passkey";
|
|
4833
4949
|
},
|
|
4834
4950
|
async addPasskey() {
|
|
4835
4951
|
if (!auth) throw new ReconnectRequiredError();
|
|
4836
4952
|
if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
|
|
4837
|
-
const stored = await
|
|
4953
|
+
const stored = await storage2.getShare(auth.userId);
|
|
4838
4954
|
if (stored?.unlockMethod === "passkey") return;
|
|
4839
4955
|
if (!stored) {
|
|
4840
4956
|
if (!serverAssistedRecovery) throw new NewDeviceError();
|
|
@@ -4867,11 +4983,11 @@ async function createDecaneConnect(config) {
|
|
|
4867
4983
|
},
|
|
4868
4984
|
async deviceProtected() {
|
|
4869
4985
|
if (!auth) return false;
|
|
4870
|
-
return await
|
|
4986
|
+
return await storage2.getShare(auth.userId) !== null;
|
|
4871
4987
|
},
|
|
4872
4988
|
async protectDevice() {
|
|
4873
4989
|
if (!auth) throw new ReconnectRequiredError();
|
|
4874
|
-
if (await
|
|
4990
|
+
if (await storage2.getShare(auth.userId)) return true;
|
|
4875
4991
|
if (!serverAssistedRecovery) throw new NewDeviceError();
|
|
4876
4992
|
const tee = new TeeClient(teeUrl);
|
|
4877
4993
|
const provisioned = await tee.provisionDeviceShare(auth.jwt);
|
|
@@ -4881,7 +4997,7 @@ async function createDecaneConnect(config) {
|
|
|
4881
4997
|
} finally {
|
|
4882
4998
|
zeroize(provisioned);
|
|
4883
4999
|
}
|
|
4884
|
-
return await
|
|
5000
|
+
return await storage2.getShare(auth.userId) !== null;
|
|
4885
5001
|
},
|
|
4886
5002
|
async unlock() {
|
|
4887
5003
|
if (!auth) {
|