decane-connect-kit 2.26.1 → 2.27.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 +182 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -6
- package/dist/index.d.ts +100 -6
- package/dist/index.js +179 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(errors_exports, {
|
|
|
36
36
|
AttestationError: () => AttestationError,
|
|
37
37
|
CallSequenceError: () => CallSequenceError,
|
|
38
38
|
DecaneConnectError: () => DecaneConnectError,
|
|
39
|
+
EnclaveHttpError: () => EnclaveHttpError,
|
|
39
40
|
ExportNotAuthorizedError: () => ExportNotAuthorizedError,
|
|
40
41
|
InvalidAccessTokenError: () => InvalidAccessTokenError,
|
|
41
42
|
InvalidApiKeyError: () => InvalidApiKeyError,
|
|
@@ -46,6 +47,7 @@ __export(errors_exports, {
|
|
|
46
47
|
OriginNotAllowedError: () => OriginNotAllowedError,
|
|
47
48
|
PromptPasswordNotConfiguredError: () => PromptPasswordNotConfiguredError,
|
|
48
49
|
PromptPinNotConfiguredError: () => PromptPinNotConfiguredError,
|
|
50
|
+
ProvisioningRateLimitedError: () => ProvisioningRateLimitedError,
|
|
49
51
|
ProvisioningTimeoutError: () => ProvisioningTimeoutError,
|
|
50
52
|
ReconnectRequiredError: () => ReconnectRequiredError,
|
|
51
53
|
RecoveryFailedError: () => RecoveryFailedError,
|
|
@@ -56,11 +58,12 @@ __export(errors_exports, {
|
|
|
56
58
|
UnlockRequiredError: () => UnlockRequiredError,
|
|
57
59
|
UnlockTokenRejectedError: () => UnlockTokenRejectedError,
|
|
58
60
|
UserCancelledError: () => UserCancelledError,
|
|
61
|
+
WalletEnrolmentError: () => WalletEnrolmentError,
|
|
59
62
|
WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
|
|
60
63
|
WrongPasswordError: () => WrongPasswordError,
|
|
61
64
|
WrongUnlockPasswordError: () => WrongUnlockPasswordError
|
|
62
65
|
});
|
|
63
|
-
var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, ProvisioningTimeoutError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, PromptPasswordNotConfiguredError, WrongUnlockPasswordError, InvalidAccessTokenError, UnlockTokenRejectedError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
|
|
66
|
+
var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, ProvisioningTimeoutError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, PromptPasswordNotConfiguredError, WrongUnlockPasswordError, InvalidAccessTokenError, UnlockTokenRejectedError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError, WalletEnrolmentError, ProvisioningRateLimitedError, EnclaveHttpError;
|
|
64
67
|
var init_errors = __esm({
|
|
65
68
|
"src/social/errors.ts"() {
|
|
66
69
|
"use strict";
|
|
@@ -209,6 +212,31 @@ var init_errors = __esm({
|
|
|
209
212
|
super("EXPORT_NOT_AUTHORIZED", "Export requires a fresh security check", true);
|
|
210
213
|
}
|
|
211
214
|
};
|
|
215
|
+
WalletEnrolmentError = class extends DecaneConnectError {
|
|
216
|
+
constructor(cause) {
|
|
217
|
+
super(
|
|
218
|
+
"WALLET_ENROLMENT_FAILED",
|
|
219
|
+
"Your wallet could not be set up: the signing enclave did not accept it. Nothing was created \u2014 sign in again to try again.",
|
|
220
|
+
true
|
|
221
|
+
);
|
|
222
|
+
if (cause !== void 0) this.cause = cause;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
ProvisioningRateLimitedError = class extends DecaneConnectError {
|
|
226
|
+
constructor() {
|
|
227
|
+
super(
|
|
228
|
+
"PROVISIONING_RATE_LIMITED",
|
|
229
|
+
"This wallet has been opened on too many devices or tabs in the last hour. Wait a little and sign in again.",
|
|
230
|
+
true
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
EnclaveHttpError = class extends DecaneConnectError {
|
|
235
|
+
constructor(status, message) {
|
|
236
|
+
super("ENCLAVE_HTTP", message, status >= 500);
|
|
237
|
+
this.status = status;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
212
240
|
}
|
|
213
241
|
});
|
|
214
242
|
|
|
@@ -226,6 +254,7 @@ __export(index_exports, {
|
|
|
226
254
|
DecaneLogo: () => DecaneLogo,
|
|
227
255
|
DecaneMark: () => DecaneMark,
|
|
228
256
|
DecaneWordmark: () => DecaneWordmark,
|
|
257
|
+
EnclaveHttpError: () => EnclaveHttpError,
|
|
229
258
|
ExportNotAuthorizedError: () => ExportNotAuthorizedError,
|
|
230
259
|
InvalidAccessTokenError: () => InvalidAccessTokenError,
|
|
231
260
|
InvalidApiKeyError: () => InvalidApiKeyError,
|
|
@@ -237,6 +266,7 @@ __export(index_exports, {
|
|
|
237
266
|
OriginNotAllowedError: () => OriginNotAllowedError,
|
|
238
267
|
PromptPasswordNotConfiguredError: () => PromptPasswordNotConfiguredError,
|
|
239
268
|
PromptPinNotConfiguredError: () => PromptPinNotConfiguredError,
|
|
269
|
+
ProvisioningRateLimitedError: () => ProvisioningRateLimitedError,
|
|
240
270
|
ProvisioningTimeoutError: () => ProvisioningTimeoutError,
|
|
241
271
|
ReconnectRequiredError: () => ReconnectRequiredError,
|
|
242
272
|
RecoveryFailedError: () => RecoveryFailedError,
|
|
@@ -248,6 +278,7 @@ __export(index_exports, {
|
|
|
248
278
|
UnlockRequiredError: () => UnlockRequiredError,
|
|
249
279
|
UnlockTokenRejectedError: () => UnlockTokenRejectedError,
|
|
250
280
|
UserCancelledError: () => UserCancelledError,
|
|
281
|
+
WalletEnrolmentError: () => WalletEnrolmentError,
|
|
251
282
|
WalletSelector: () => WalletSelector,
|
|
252
283
|
WeakPasswordError: () => WeakPasswordError,
|
|
253
284
|
WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
|
|
@@ -2197,7 +2228,8 @@ var ApiClient = class {
|
|
|
2197
2228
|
wrappedShareMeta: btoa(JSON.stringify(share.wrappedShareMeta)),
|
|
2198
2229
|
evmAddress: share.evmAddress,
|
|
2199
2230
|
solanaAddress: share.solanaAddress,
|
|
2200
|
-
tronAddress: share.tronAddress
|
|
2231
|
+
tronAddress: share.tronAddress,
|
|
2232
|
+
...share.pendingEnrolment === void 0 ? {} : { pendingEnrolment: share.pendingEnrolment }
|
|
2201
2233
|
})
|
|
2202
2234
|
});
|
|
2203
2235
|
}
|
|
@@ -2776,7 +2808,8 @@ var TeeClient = class {
|
|
|
2776
2808
|
});
|
|
2777
2809
|
if (!res.ok) {
|
|
2778
2810
|
const detail = await res.text().catch(() => "");
|
|
2779
|
-
throw new
|
|
2811
|
+
throw new EnclaveHttpError(
|
|
2812
|
+
res.status,
|
|
2780
2813
|
`auth share enrolment failed (HTTP ${res.status})${detail ? `: ${detail.slice(0, 200)}` : ""}`
|
|
2781
2814
|
);
|
|
2782
2815
|
}
|
|
@@ -2797,9 +2830,11 @@ var TeeClient = class {
|
|
|
2797
2830
|
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" }
|
|
2798
2831
|
});
|
|
2799
2832
|
if (res.status === 409) return null;
|
|
2833
|
+
if (res.status === 429) throw new ProvisioningRateLimitedError();
|
|
2800
2834
|
if (!res.ok) {
|
|
2801
2835
|
const detail = await res.text().catch(() => "");
|
|
2802
|
-
throw new
|
|
2836
|
+
throw new EnclaveHttpError(
|
|
2837
|
+
res.status,
|
|
2803
2838
|
`device share provisioning failed (HTTP ${res.status})${detail ? `: ${detail.slice(0, 200)}` : ""}`
|
|
2804
2839
|
);
|
|
2805
2840
|
}
|
|
@@ -3014,6 +3049,35 @@ var SessionManager = class {
|
|
|
3014
3049
|
}
|
|
3015
3050
|
};
|
|
3016
3051
|
|
|
3052
|
+
// src/social/identityTier.ts
|
|
3053
|
+
function resolveProtectionTier(config) {
|
|
3054
|
+
if (config.protection) return config.protection;
|
|
3055
|
+
return config.deferDeviceProtection === true ? "identity" : "device";
|
|
3056
|
+
}
|
|
3057
|
+
function resumeAcrossTabsDefault(config, identityTier) {
|
|
3058
|
+
return config.resumeSessionAcrossTabs ?? identityTier;
|
|
3059
|
+
}
|
|
3060
|
+
var ENROL_BACKOFF_MS = [0, 1e3, 3e3];
|
|
3061
|
+
function enrolmentRetryable(status) {
|
|
3062
|
+
return status !== 429 && status !== 403;
|
|
3063
|
+
}
|
|
3064
|
+
function shouldTryProvisionFirst(identityTier, hasStoredShare) {
|
|
3065
|
+
return identityTier || !hasStoredShare;
|
|
3066
|
+
}
|
|
3067
|
+
function routeAfterProvision(input) {
|
|
3068
|
+
switch (input.outcome) {
|
|
3069
|
+
case "share":
|
|
3070
|
+
return "session";
|
|
3071
|
+
case "rate-limited":
|
|
3072
|
+
return "fail";
|
|
3073
|
+
case "not-enrolled":
|
|
3074
|
+
if (!input.identityTier) return "recover";
|
|
3075
|
+
return input.hasStoredShare ? "migrate" : "new-device";
|
|
3076
|
+
case "error":
|
|
3077
|
+
return input.identityTier ? "fail" : "recover";
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3017
3081
|
// src/social/crypto/shares.ts
|
|
3018
3082
|
var import_secp256k1 = require("@noble/curves/secp256k1");
|
|
3019
3083
|
var import_shamir_secret_sharing = require("shamir-secret-sharing");
|
|
@@ -3924,7 +3988,8 @@ function clearSessionPersist(key) {
|
|
|
3924
3988
|
async function createDecaneConnect(config) {
|
|
3925
3989
|
if (!config.appId) throw new Error("decane-connect-kit: appId is required");
|
|
3926
3990
|
if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
|
|
3927
|
-
|
|
3991
|
+
const identityTier = resolveProtectionTier(config) === "identity";
|
|
3992
|
+
useCrossTabSessionStore(resumeAcrossTabsDefault(config, identityTier));
|
|
3928
3993
|
if (!config.chains?.length)
|
|
3929
3994
|
throw new Error("decane-connect-kit: at least one chain is required");
|
|
3930
3995
|
if (config.rotateOnNewDeviceRecovery && !config.onRecoveryRotated) {
|
|
@@ -3962,7 +4027,6 @@ async function createDecaneConnect(config) {
|
|
|
3962
4027
|
let lastUserId = loadLastUser(LAST_USER_KEY) ?? (LEGACY_LAST_USER_KEY !== LAST_USER_KEY ? loadLastUser(LEGACY_LAST_USER_KEY) : null);
|
|
3963
4028
|
let ethProvider = null;
|
|
3964
4029
|
const persistSession = config.persistSession !== false;
|
|
3965
|
-
const deferDeviceProtection = config.deferDeviceProtection === true;
|
|
3966
4030
|
const serverAssistedRecovery = true;
|
|
3967
4031
|
const RECOVERY_OFFER_TIMEOUT_MS = 5 * 6e4;
|
|
3968
4032
|
const selfHealAttempted = /* @__PURE__ */ new Set();
|
|
@@ -3983,22 +4047,27 @@ async function createDecaneConnect(config) {
|
|
|
3983
4047
|
zeroize(share);
|
|
3984
4048
|
}
|
|
3985
4049
|
}
|
|
3986
|
-
async function
|
|
4050
|
+
async function enrolBlocking(jwt, share, budgetMs) {
|
|
4051
|
+
const startedAt = Date.now();
|
|
3987
4052
|
try {
|
|
3988
|
-
|
|
4053
|
+
let lastError;
|
|
4054
|
+
for (const wait of ENROL_BACKOFF_MS) {
|
|
4055
|
+
if (wait) await new Promise((r) => setTimeout(r, wait));
|
|
3989
4056
|
try {
|
|
3990
|
-
await
|
|
4057
|
+
await withDeadline(
|
|
4058
|
+
new TeeClient(teeUrl).enrolAuthShare(jwt, share),
|
|
4059
|
+
budgetMs - (Date.now() - startedAt),
|
|
4060
|
+
"enrolling your wallet"
|
|
4061
|
+
);
|
|
3991
4062
|
return;
|
|
3992
4063
|
} catch (e) {
|
|
3993
|
-
if (
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
"decane-connect-kit: could not enrol this wallet for server-assisted recovery. The wallet is fine and fully usable; it will be re-enrolled automatically on the next unlock. Until then a brand-new device would need a passkey backup or a recovery file.",
|
|
3997
|
-
e
|
|
3998
|
-
);
|
|
3999
|
-
}
|
|
4064
|
+
if (e instanceof AttestationError || e instanceof ProvisioningTimeoutError) throw e;
|
|
4065
|
+
lastError = e;
|
|
4066
|
+
if (!enrolmentRetryable(e.status)) break;
|
|
4000
4067
|
}
|
|
4001
4068
|
}
|
|
4069
|
+
events.emit("enrolment-failed");
|
|
4070
|
+
throw new WalletEnrolmentError(lastError);
|
|
4002
4071
|
} finally {
|
|
4003
4072
|
zeroize(share);
|
|
4004
4073
|
}
|
|
@@ -4094,6 +4163,7 @@ async function createDecaneConnect(config) {
|
|
|
4094
4163
|
else scheduleAccessTokenExpiry(saved.jwt, saved.expiresAt);
|
|
4095
4164
|
auth = { jwt: saved.jwt, userId: saved.userId, addresses: saved.addresses };
|
|
4096
4165
|
persisted = { userId: saved.userId, addresses: saved.addresses };
|
|
4166
|
+
if (saved.handle) armTokenRefresh(saved.jwt);
|
|
4097
4167
|
} catch {
|
|
4098
4168
|
dropLiveSession();
|
|
4099
4169
|
}
|
|
@@ -4103,6 +4173,13 @@ async function createDecaneConnect(config) {
|
|
|
4103
4173
|
if (LEGACY_SESSION_KEY !== SESSION_KEY) clearSessionPersist(LEGACY_SESSION_KEY);
|
|
4104
4174
|
}
|
|
4105
4175
|
events.on("session-expired", () => dropLiveSession());
|
|
4176
|
+
function armTokenRefresh(jwt) {
|
|
4177
|
+
const expiresAt = decodeAccessToken(jwt)?.expiresAt;
|
|
4178
|
+
if (expiresAt) scheduleTokenRefresh(jwt, expiresAt);
|
|
4179
|
+
}
|
|
4180
|
+
events.on("connected", () => {
|
|
4181
|
+
if (auth) armTokenRefresh(auth.jwt);
|
|
4182
|
+
});
|
|
4106
4183
|
async function passwordSetup() {
|
|
4107
4184
|
if (!config.promptUnlockPassword) throw new PromptPasswordNotConfiguredError();
|
|
4108
4185
|
const password = await config.promptUnlockPassword({ kind: "set" });
|
|
@@ -4327,7 +4404,7 @@ async function createDecaneConnect(config) {
|
|
|
4327
4404
|
let wrappingKey = null;
|
|
4328
4405
|
let meta = {};
|
|
4329
4406
|
let unlockTokenKey = null;
|
|
4330
|
-
if (
|
|
4407
|
+
if (identityTier) {
|
|
4331
4408
|
} else if (isPasskeySupported()) {
|
|
4332
4409
|
const prfSalt = generateRandomBytes(32);
|
|
4333
4410
|
try {
|
|
@@ -4399,14 +4476,29 @@ async function createDecaneConnect(config) {
|
|
|
4399
4476
|
iv: serverIv,
|
|
4400
4477
|
wrappedShareMeta: {
|
|
4401
4478
|
// The server records this on the wallet_created event and nothing else.
|
|
4402
|
-
unlockMethod:
|
|
4479
|
+
unlockMethod: identityTier ? "identity" : unlockMethod
|
|
4403
4480
|
// credential IDs, prfSalt, pinSalt stay on-device — never sent to server
|
|
4404
4481
|
},
|
|
4405
4482
|
evmAddress,
|
|
4406
4483
|
solanaAddress,
|
|
4407
|
-
tronAddress
|
|
4484
|
+
tronAddress,
|
|
4485
|
+
// Provisional until the enclave has sealed the auth share, just below. A
|
|
4486
|
+
// backend that knows the flag keeps this row invisible to hasShare until
|
|
4487
|
+
// then, so a creation that fails past this point leaves no wallet behind
|
|
4488
|
+
// and the next sign-in simply creates again — with a new key, which is
|
|
4489
|
+
// why the backend also replaces the recorded addresses outright.
|
|
4490
|
+
pendingEnrolment: true
|
|
4408
4491
|
}), remainingBudget(), "storing your wallet");
|
|
4409
4492
|
zeroize(shareSet.server);
|
|
4493
|
+
try {
|
|
4494
|
+
await enrolBlocking(jwt, new Uint8Array(shareSet.recovery), remainingBudget());
|
|
4495
|
+
} catch (e) {
|
|
4496
|
+
zeroize(shareSet.device);
|
|
4497
|
+
zeroize(shareSet.recovery);
|
|
4498
|
+
if (wrappingKey) await storage2.deleteShare(userId).catch(() => {
|
|
4499
|
+
});
|
|
4500
|
+
throw e;
|
|
4501
|
+
}
|
|
4410
4502
|
const addresses = { evm: evmAddress, solana: solanaAddress, tron: tronAddress };
|
|
4411
4503
|
if (unlockMethod === "passkey" && meta.credentialId && meta.prfSalt && wrappedDeviceShare && iv) {
|
|
4412
4504
|
try {
|
|
@@ -4435,10 +4527,7 @@ async function createDecaneConnect(config) {
|
|
|
4435
4527
|
addresses.evm = evmAddress = confirmed.evm;
|
|
4436
4528
|
addresses.solana = solanaAddress = confirmed.solana;
|
|
4437
4529
|
addresses.tron = tronAddress = confirmed.tron ?? evmToTronAddress2(confirmed.evm);
|
|
4438
|
-
if (
|
|
4439
|
-
void enrolInBackground(jwt, new Uint8Array(shareSet.recovery));
|
|
4440
|
-
}
|
|
4441
|
-
if (config.offerRecoveryAtSignup === true && config.onRecoveryShareOffer) {
|
|
4530
|
+
if (!identityTier && config.offerRecoveryAtSignup === true && config.onRecoveryShareOffer) {
|
|
4442
4531
|
void offerRecoveryShare(
|
|
4443
4532
|
jwt,
|
|
4444
4533
|
new Uint8Array(shareSet.recovery),
|
|
@@ -4485,31 +4574,44 @@ async function createDecaneConnect(config) {
|
|
|
4485
4574
|
const stored = await storage2.getShare(userId);
|
|
4486
4575
|
let deviceShare;
|
|
4487
4576
|
let joinedViaPasskeySync = false;
|
|
4488
|
-
|
|
4489
|
-
|
|
4577
|
+
let migrating = false;
|
|
4578
|
+
if (serverAssistedRecovery && shouldTryProvisionFirst(identityTier, !!stored)) {
|
|
4579
|
+
let provisioned = null;
|
|
4580
|
+
let failure;
|
|
4581
|
+
let outcome;
|
|
4582
|
+
try {
|
|
4583
|
+
provisioned = await new TeeClient(teeUrl).provisionDeviceShare(jwt);
|
|
4584
|
+
outcome = provisioned ? "share" : "not-enrolled";
|
|
4585
|
+
} catch (e) {
|
|
4586
|
+
failure = e;
|
|
4587
|
+
outcome = e instanceof ProvisioningRateLimitedError ? "rate-limited" : "error";
|
|
4588
|
+
}
|
|
4589
|
+
const route = routeAfterProvision({ identityTier, hasStoredShare: !!stored, outcome });
|
|
4590
|
+
if (route === "session" && provisioned) {
|
|
4591
|
+
if (!identityTier) await persistProvisionedShare(userId, jwt, provisioned);
|
|
4592
|
+
let addrs;
|
|
4490
4593
|
try {
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
if (!deferDeviceProtection) await persistProvisionedShare(userId, jwt, provisioned);
|
|
4495
|
-
let addrs;
|
|
4496
|
-
try {
|
|
4497
|
-
addrs = await session.start(provisioned, jwt, recordedAddresses);
|
|
4498
|
-
} finally {
|
|
4499
|
-
zeroize(provisioned);
|
|
4500
|
-
}
|
|
4501
|
-
return addrs;
|
|
4502
|
-
}
|
|
4503
|
-
console.warn(
|
|
4504
|
-
"decane-connect-kit: this wallet is not enrolled for server-assisted provisioning (/signer/provision returned 409 not_enrolled), so a device share cannot be issued from the JWT alone. Falling back to passkey sync, then the recovery file."
|
|
4505
|
-
);
|
|
4506
|
-
} catch (e) {
|
|
4507
|
-
console.warn(
|
|
4508
|
-
"decane-connect-kit: server-assisted provisioning unavailable; falling back to passkey/recovery-file recovery.",
|
|
4509
|
-
e
|
|
4510
|
-
);
|
|
4594
|
+
addrs = await session.start(provisioned, jwt, recordedAddresses);
|
|
4595
|
+
} finally {
|
|
4596
|
+
zeroize(provisioned);
|
|
4511
4597
|
}
|
|
4598
|
+
return addrs;
|
|
4599
|
+
}
|
|
4600
|
+
if (route === "fail") throw failure;
|
|
4601
|
+
if (route === "new-device") throw new NewDeviceError();
|
|
4602
|
+
if (route === "migrate") migrating = true;
|
|
4603
|
+
if (route === "recover" && outcome === "not-enrolled") {
|
|
4604
|
+
console.warn(
|
|
4605
|
+
"decane-connect-kit: this wallet is not enrolled for server-assisted provisioning (/signer/provision returned 409 not_enrolled), so a device share cannot be issued from the JWT alone. Falling back to passkey sync, then the recovery file."
|
|
4606
|
+
);
|
|
4607
|
+
} else if (route === "recover") {
|
|
4608
|
+
console.warn(
|
|
4609
|
+
"decane-connect-kit: server-assisted provisioning unavailable; falling back to passkey/recovery-file recovery.",
|
|
4610
|
+
failure
|
|
4611
|
+
);
|
|
4512
4612
|
}
|
|
4613
|
+
}
|
|
4614
|
+
if (!stored) {
|
|
4513
4615
|
if (!config.onRecoveryRotated) {
|
|
4514
4616
|
throw new CallSequenceError(
|
|
4515
4617
|
"Recovery rotates the share set and issues a new recovery file, but no onRecoveryRotated callback is configured to save it. Configure onRecoveryRotated before allowing recovery."
|
|
@@ -4535,7 +4637,7 @@ async function createDecaneConnect(config) {
|
|
|
4535
4637
|
});
|
|
4536
4638
|
deviceShare = recovered.deviceShare;
|
|
4537
4639
|
joinedViaPasskeySync = recovered.flow === "passkey-sync";
|
|
4538
|
-
if (!
|
|
4640
|
+
if (!identityTier && !await storage2.getShare(userId) && isPasskeySupported()) {
|
|
4539
4641
|
await registerPasskeyAndBackup(userId, jwt, deviceShare);
|
|
4540
4642
|
}
|
|
4541
4643
|
} else {
|
|
@@ -4549,6 +4651,14 @@ async function createDecaneConnect(config) {
|
|
|
4549
4651
|
} catch {
|
|
4550
4652
|
throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
|
|
4551
4653
|
}
|
|
4654
|
+
if (migrating) {
|
|
4655
|
+
try {
|
|
4656
|
+
await enrolBlocking(jwt, new Uint8Array(deviceShare), PROVISIONING_BUDGET_MS);
|
|
4657
|
+
} catch (e) {
|
|
4658
|
+
zeroize(deviceShare);
|
|
4659
|
+
throw e;
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4552
4662
|
}
|
|
4553
4663
|
let addresses;
|
|
4554
4664
|
try {
|
|
@@ -4563,7 +4673,11 @@ async function createDecaneConnect(config) {
|
|
|
4563
4673
|
}
|
|
4564
4674
|
throw e;
|
|
4565
4675
|
}
|
|
4566
|
-
|
|
4676
|
+
if (migrating) {
|
|
4677
|
+
events.emit("migrated-to-identity");
|
|
4678
|
+
} else if (!identityTier) {
|
|
4679
|
+
void selfHealEnrolment(userId, jwt, new Uint8Array(deviceShare));
|
|
4680
|
+
}
|
|
4567
4681
|
zeroize(deviceShare);
|
|
4568
4682
|
if (recordedAddresses && recordedAddresses.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
|
|
4569
4683
|
console.warn(
|
|
@@ -4746,7 +4860,7 @@ async function createDecaneConnect(config) {
|
|
|
4746
4860
|
if (auth && session.isActive()) {
|
|
4747
4861
|
return { addresses: auth.addresses, isNewUser: false };
|
|
4748
4862
|
}
|
|
4749
|
-
const candidate = await passkeyLoginCandidate();
|
|
4863
|
+
const candidate = identityTier ? null : await passkeyLoginCandidate();
|
|
4750
4864
|
if (candidate) {
|
|
4751
4865
|
try {
|
|
4752
4866
|
return await passkeyLogin(candidate);
|
|
@@ -4856,9 +4970,15 @@ async function createDecaneConnect(config) {
|
|
|
4856
4970
|
return handleAuthResult(result);
|
|
4857
4971
|
},
|
|
4858
4972
|
async canSignInWithPasskey() {
|
|
4973
|
+
if (identityTier) return false;
|
|
4859
4974
|
return canOfferPasskey();
|
|
4860
4975
|
},
|
|
4861
4976
|
async signInWithPasskey() {
|
|
4977
|
+
if (identityTier) {
|
|
4978
|
+
throw new CallSequenceError(
|
|
4979
|
+
"signInWithPasskey() is not available in the identity tier (protection: 'identity'); use connect()."
|
|
4980
|
+
);
|
|
4981
|
+
}
|
|
4862
4982
|
const candidate = await passkeyDeviceCandidate();
|
|
4863
4983
|
if (candidate) return passkeyLogin(candidate);
|
|
4864
4984
|
const known = persisted?.userId ?? lastUserId;
|
|
@@ -4994,6 +5114,11 @@ async function createDecaneConnect(config) {
|
|
|
4994
5114
|
return stored?.unlockMethod === "passkey";
|
|
4995
5115
|
},
|
|
4996
5116
|
async addPasskey() {
|
|
5117
|
+
if (identityTier) {
|
|
5118
|
+
throw new CallSequenceError(
|
|
5119
|
+
"addPasskey() is not available in the identity tier (protection: 'identity'): nothing is stored on the device for a passkey to protect."
|
|
5120
|
+
);
|
|
5121
|
+
}
|
|
4997
5122
|
if (!auth) throw new ReconnectRequiredError();
|
|
4998
5123
|
if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
|
|
4999
5124
|
const stored = await storage2.getShare(auth.userId);
|
|
@@ -5032,6 +5157,7 @@ async function createDecaneConnect(config) {
|
|
|
5032
5157
|
return await storage2.getShare(auth.userId) !== null;
|
|
5033
5158
|
},
|
|
5034
5159
|
async protectDevice() {
|
|
5160
|
+
if (identityTier) return false;
|
|
5035
5161
|
if (!auth) throw new ReconnectRequiredError();
|
|
5036
5162
|
if (await storage2.getShare(auth.userId)) return true;
|
|
5037
5163
|
if (!serverAssistedRecovery) throw new NewDeviceError();
|
|
@@ -5047,7 +5173,7 @@ async function createDecaneConnect(config) {
|
|
|
5047
5173
|
},
|
|
5048
5174
|
async unlock() {
|
|
5049
5175
|
if (!auth) {
|
|
5050
|
-
const candidate = await passkeyLoginCandidate();
|
|
5176
|
+
const candidate = identityTier ? null : await passkeyLoginCandidate();
|
|
5051
5177
|
if (candidate) {
|
|
5052
5178
|
await passkeyLogin(candidate);
|
|
5053
5179
|
return;
|
|
@@ -5216,6 +5342,8 @@ async function createDecaneConnect(config) {
|
|
|
5216
5342
|
},
|
|
5217
5343
|
async disconnect() {
|
|
5218
5344
|
session.end();
|
|
5345
|
+
if (tokenRefresh) clearTimeout(tokenRefresh);
|
|
5346
|
+
tokenRefresh = null;
|
|
5219
5347
|
if (auth?.jwt) {
|
|
5220
5348
|
await api.revoke(auth.jwt).catch(() => {
|
|
5221
5349
|
});
|
|
@@ -7666,6 +7794,7 @@ function DecaneKit({ config, children }) {
|
|
|
7666
7794
|
chains,
|
|
7667
7795
|
rpcUrls,
|
|
7668
7796
|
requireAssertionPerSignature,
|
|
7797
|
+
protection,
|
|
7669
7798
|
deferDeviceProtection,
|
|
7670
7799
|
resumeSessionAcrossTabs,
|
|
7671
7800
|
// Recovery callbacks. Forwarding these is what makes recovery
|
|
@@ -7688,6 +7817,7 @@ function DecaneKit({ config, children }) {
|
|
|
7688
7817
|
promptPin,
|
|
7689
7818
|
promptUnlockPassword,
|
|
7690
7819
|
requireAssertionPerSignature,
|
|
7820
|
+
protection,
|
|
7691
7821
|
deferDeviceProtection,
|
|
7692
7822
|
resumeSessionAcrossTabs,
|
|
7693
7823
|
offerRecoveryAtSignup,
|
|
@@ -8897,6 +9027,7 @@ function useSignTypedData() {
|
|
|
8897
9027
|
DecaneLogo,
|
|
8898
9028
|
DecaneMark,
|
|
8899
9029
|
DecaneWordmark,
|
|
9030
|
+
EnclaveHttpError,
|
|
8900
9031
|
ExportNotAuthorizedError,
|
|
8901
9032
|
InvalidAccessTokenError,
|
|
8902
9033
|
InvalidApiKeyError,
|
|
@@ -8908,6 +9039,7 @@ function useSignTypedData() {
|
|
|
8908
9039
|
OriginNotAllowedError,
|
|
8909
9040
|
PromptPasswordNotConfiguredError,
|
|
8910
9041
|
PromptPinNotConfiguredError,
|
|
9042
|
+
ProvisioningRateLimitedError,
|
|
8911
9043
|
ProvisioningTimeoutError,
|
|
8912
9044
|
ReconnectRequiredError,
|
|
8913
9045
|
RecoveryFailedError,
|
|
@@ -8919,6 +9051,7 @@ function useSignTypedData() {
|
|
|
8919
9051
|
UnlockRequiredError,
|
|
8920
9052
|
UnlockTokenRejectedError,
|
|
8921
9053
|
UserCancelledError,
|
|
9054
|
+
WalletEnrolmentError,
|
|
8922
9055
|
WalletSelector,
|
|
8923
9056
|
WeakPasswordError,
|
|
8924
9057
|
WebAuthnNotSupportedError,
|