decane-connect-kit 2.29.3 → 2.31.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 +170 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -17
- package/dist/index.d.ts +79 -17
- package/dist/index.js +169 -115
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ __export(errors_exports, {
|
|
|
20
20
|
InvalidApiKeyError: () => InvalidApiKeyError,
|
|
21
21
|
InvalidPinError: () => InvalidPinError,
|
|
22
22
|
InvalidRecoveryFileError: () => InvalidRecoveryFileError,
|
|
23
|
+
LoginInUseError: () => LoginInUseError,
|
|
23
24
|
NetworkError: () => NetworkError,
|
|
24
25
|
NewDeviceError: () => NewDeviceError,
|
|
25
26
|
OriginNotAllowedError: () => OriginNotAllowedError,
|
|
@@ -42,7 +43,7 @@ __export(errors_exports, {
|
|
|
42
43
|
WrongPasswordError: () => WrongPasswordError,
|
|
43
44
|
WrongUnlockPasswordError: () => WrongUnlockPasswordError
|
|
44
45
|
});
|
|
45
|
-
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, WalletExistsError, WalletEnrolmentError, ProvisioningRateLimitedError, EnclaveHttpError;
|
|
46
|
+
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, LoginInUseError, WalletExistsError, WalletEnrolmentError, ProvisioningRateLimitedError, EnclaveHttpError;
|
|
46
47
|
var init_errors = __esm({
|
|
47
48
|
"src/social/errors.ts"() {
|
|
48
49
|
"use strict";
|
|
@@ -191,6 +192,11 @@ var init_errors = __esm({
|
|
|
191
192
|
super("EXPORT_NOT_AUTHORIZED", "Export requires a fresh security check", true);
|
|
192
193
|
}
|
|
193
194
|
};
|
|
195
|
+
LoginInUseError = class extends DecaneConnectError {
|
|
196
|
+
constructor(message = "That login already opens another account. It was not linked.") {
|
|
197
|
+
super("LOGIN_IN_USE", message);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
194
200
|
WalletExistsError = class extends DecaneConnectError {
|
|
195
201
|
constructor() {
|
|
196
202
|
super("WALLET_EXISTS", "This account already has a wallet; opening it instead.", true);
|
|
@@ -2031,7 +2037,9 @@ var ERROR_MAP = {
|
|
|
2031
2037
|
// generic failure message.
|
|
2032
2038
|
UNAUTHORIZED: ReconnectRequiredError,
|
|
2033
2039
|
// A restorable wallet already exists: the client provisions instead of creating.
|
|
2034
|
-
WALLET_EXISTS: WalletExistsError
|
|
2040
|
+
WALLET_EXISTS: WalletExistsError,
|
|
2041
|
+
// linkWithToken(): the login already opens another account.
|
|
2042
|
+
LOGIN_IN_USE: LoginInUseError
|
|
2035
2043
|
};
|
|
2036
2044
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
2037
2045
|
var ApiClient = class {
|
|
@@ -2098,9 +2106,19 @@ var ApiClient = class {
|
|
|
2098
2106
|
return this._request("/auth/kingschat/config", { method: "GET" });
|
|
2099
2107
|
}
|
|
2100
2108
|
/**
|
|
2101
|
-
* Exchange a KingsChat
|
|
2102
|
-
*
|
|
2103
|
-
*
|
|
2109
|
+
* Exchange a KingsChat authorization code for a Decane session. The backend
|
|
2110
|
+
* trades it with KingsChat for an access token, verifies that token (it must
|
|
2111
|
+
* have been issued to this project's KingsChat app), and never stores it.
|
|
2112
|
+
*/
|
|
2113
|
+
async authKingsChatCode(code) {
|
|
2114
|
+
return this._request("/auth/kingschat/code", {
|
|
2115
|
+
method: "POST",
|
|
2116
|
+
body: JSON.stringify({ code })
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Exchange a KingsChat access token the app already holds for a Decane
|
|
2121
|
+
* session. Verified the same way as a code's token, and never stored.
|
|
2104
2122
|
*/
|
|
2105
2123
|
async authKingsChat(accessToken) {
|
|
2106
2124
|
return this._request("/auth/kingschat", {
|
|
@@ -2200,6 +2218,23 @@ var ApiClient = class {
|
|
|
2200
2218
|
body: JSON.stringify(providerId ? { token, providerId } : { token })
|
|
2201
2219
|
});
|
|
2202
2220
|
}
|
|
2221
|
+
/** Attach a login from the project's own provider to the signed-in account. */
|
|
2222
|
+
async linkExternal(jwt, token, providerId) {
|
|
2223
|
+
return this._request("/auth/link/external", {
|
|
2224
|
+
method: "POST",
|
|
2225
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
2226
|
+
body: JSON.stringify(providerId ? { token, providerId } : { token })
|
|
2227
|
+
});
|
|
2228
|
+
}
|
|
2229
|
+
async listLinkedLogins(jwt) {
|
|
2230
|
+
return this._request("/auth/link/external", { method: "GET", headers: { Authorization: `Bearer ${jwt}` } });
|
|
2231
|
+
}
|
|
2232
|
+
async unlinkLogin(jwt, id) {
|
|
2233
|
+
await this._request(`/auth/link/external/${encodeURIComponent(id)}`, {
|
|
2234
|
+
method: "DELETE",
|
|
2235
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2203
2238
|
/**
|
|
2204
2239
|
* Returns null when this user has no server-registered passkey, which is the
|
|
2205
2240
|
* expected answer for wallets created before passkey sign-in existed or for
|
|
@@ -3263,75 +3298,47 @@ async function verifyPhoneCode(api, phone, code) {
|
|
|
3263
3298
|
|
|
3264
3299
|
// src/social/auth/kingschat.ts
|
|
3265
3300
|
init_errors();
|
|
3266
|
-
var
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
const
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
}
|
|
3301
|
+
var KINGSCHAT_LOGIN_URL = "https://accounts.kingschat.online/log-in";
|
|
3302
|
+
var STATE_KEY = "decane:kingschat_state";
|
|
3303
|
+
function randomState() {
|
|
3304
|
+
const bytes = new Uint8Array(16);
|
|
3305
|
+
crypto.getRandomValues(bytes);
|
|
3306
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
3307
|
+
}
|
|
3308
|
+
function storage2() {
|
|
3309
|
+
try {
|
|
3310
|
+
return window.sessionStorage ?? null;
|
|
3311
|
+
} catch {
|
|
3312
|
+
return null;
|
|
3313
|
+
}
|
|
3279
3314
|
}
|
|
3280
|
-
function
|
|
3281
|
-
const
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
url
|
|
3315
|
+
function beginKingsChatLogin(clientId, opts = {}) {
|
|
3316
|
+
const store = storage2();
|
|
3317
|
+
if (!store) throw new Error("KingsChat sign-in needs sessionStorage, which this browser is blocking");
|
|
3318
|
+
const state = randomState();
|
|
3319
|
+
store.setItem(STATE_KEY, state);
|
|
3320
|
+
const url = new URL(KINGSCHAT_LOGIN_URL);
|
|
3321
|
+
url.searchParams.set("clientId", clientId);
|
|
3322
|
+
url.searchParams.set("origin", state);
|
|
3323
|
+
if (opts.forceLogin) url.searchParams.set("forceLogin", "true");
|
|
3286
3324
|
return url.toString();
|
|
3287
3325
|
}
|
|
3288
|
-
function
|
|
3289
|
-
const
|
|
3290
|
-
const
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3326
|
+
function readKingsChatReturn(search) {
|
|
3327
|
+
const params = new URLSearchParams(search);
|
|
3328
|
+
const code = params.get("code");
|
|
3329
|
+
const origin = params.get("origin");
|
|
3330
|
+
if (!code || !origin) return null;
|
|
3331
|
+
const expected = storage2()?.getItem(STATE_KEY);
|
|
3332
|
+
if (!expected || expected !== origin) return null;
|
|
3333
|
+
return { code };
|
|
3334
|
+
}
|
|
3335
|
+
function clearKingsChatState() {
|
|
3336
|
+
storage2()?.removeItem(STATE_KEY);
|
|
3337
|
+
}
|
|
3338
|
+
async function loginWithKingsChatPopup(_opts) {
|
|
3339
|
+
throw new CallSequenceError(
|
|
3340
|
+
"loginWithKingsChatPopup was removed in 2.31.0: KingsChat retired its popup login. Use connectWithKingsChat(), which redirects to KingsChat and finishes on your registered Redirect URL."
|
|
3294
3341
|
);
|
|
3295
|
-
if (!popup) {
|
|
3296
|
-
throw new CallSequenceError(
|
|
3297
|
-
"The KingsChat sign-in popup was blocked. Call connectWithKingsChat() directly from a click handler."
|
|
3298
|
-
);
|
|
3299
|
-
}
|
|
3300
|
-
return new Promise((resolve, reject) => {
|
|
3301
|
-
let settled = false;
|
|
3302
|
-
const finish = (fn) => {
|
|
3303
|
-
if (settled) return;
|
|
3304
|
-
settled = true;
|
|
3305
|
-
window.removeEventListener("message", onMessage);
|
|
3306
|
-
clearInterval(closedPoll);
|
|
3307
|
-
clearTimeout(timeout);
|
|
3308
|
-
try {
|
|
3309
|
-
popup.close();
|
|
3310
|
-
} catch {
|
|
3311
|
-
}
|
|
3312
|
-
fn();
|
|
3313
|
-
};
|
|
3314
|
-
const onMessage = (event) => {
|
|
3315
|
-
if (event.origin !== KINGSCHAT_AUTH_ORIGIN) return;
|
|
3316
|
-
const data = event.data;
|
|
3317
|
-
if (data && typeof data === "object" && data.error) {
|
|
3318
|
-
finish(() => reject(new CallSequenceError(`KingsChat sign-in failed: ${String(data.error)}`)));
|
|
3319
|
-
return;
|
|
3320
|
-
}
|
|
3321
|
-
const tokens2 = readTokens(event.data);
|
|
3322
|
-
if (tokens2) finish(() => resolve(tokens2));
|
|
3323
|
-
};
|
|
3324
|
-
window.addEventListener("message", onMessage);
|
|
3325
|
-
const closedPoll = setInterval(() => {
|
|
3326
|
-
if (popup.closed) finish(() => reject(new UserCancelledError()));
|
|
3327
|
-
}, 400);
|
|
3328
|
-
const timeout = setTimeout(
|
|
3329
|
-
() => finish(
|
|
3330
|
-
() => reject(new CallSequenceError("KingsChat sign-in timed out before returning a token."))
|
|
3331
|
-
),
|
|
3332
|
-
opts.timeoutMs ?? 5 * 6e4
|
|
3333
|
-
);
|
|
3334
|
-
});
|
|
3335
3342
|
}
|
|
3336
3343
|
|
|
3337
3344
|
// src/social/signers/evm.ts
|
|
@@ -3936,7 +3943,7 @@ async function createDecaneConnect(config) {
|
|
|
3936
3943
|
const teeUrl = config.teeUrl ?? DEFAULT_TEE_URL;
|
|
3937
3944
|
const events = new EventEmitter();
|
|
3938
3945
|
const api = new ApiClient(baseUrl, config.apiKey, config.appId);
|
|
3939
|
-
const
|
|
3946
|
+
const storage3 = new Storage();
|
|
3940
3947
|
const session = new SessionManager(
|
|
3941
3948
|
events,
|
|
3942
3949
|
durationMs,
|
|
@@ -4126,7 +4133,7 @@ async function createDecaneConnect(config) {
|
|
|
4126
4133
|
}
|
|
4127
4134
|
async function attachUnlockToken(userId, jwt, tokenKey) {
|
|
4128
4135
|
try {
|
|
4129
|
-
const stored = await
|
|
4136
|
+
const stored = await storage3.getShare(userId);
|
|
4130
4137
|
if (!stored) return;
|
|
4131
4138
|
const { token } = await api.issueUnlockToken(jwt);
|
|
4132
4139
|
const unlockTokenIv = generateRandomBytes(12);
|
|
@@ -4135,13 +4142,13 @@ async function createDecaneConnect(config) {
|
|
|
4135
4142
|
new TextEncoder().encode(token),
|
|
4136
4143
|
unlockTokenIv
|
|
4137
4144
|
);
|
|
4138
|
-
await
|
|
4145
|
+
await storage3.putShare({ ...stored, wrappedUnlockToken, unlockTokenIv });
|
|
4139
4146
|
} catch (e) {
|
|
4140
4147
|
console.warn("decane-connect-kit: could not store an unlock token for this device.", e);
|
|
4141
4148
|
}
|
|
4142
4149
|
}
|
|
4143
4150
|
async function persistRotatedDeviceShare(userId, deviceShare) {
|
|
4144
|
-
const stored = await
|
|
4151
|
+
const stored = await storage3.getShare(userId);
|
|
4145
4152
|
if (!stored) {
|
|
4146
4153
|
throw new CallSequenceError(
|
|
4147
4154
|
"Share set was rotated but this device has no stored share to update. Recover the wallet on this device before rotating."
|
|
@@ -4150,21 +4157,21 @@ async function createDecaneConnect(config) {
|
|
|
4150
4157
|
const wrappingKey = await getWrappingKey(stored);
|
|
4151
4158
|
const iv = generateRandomBytes(12);
|
|
4152
4159
|
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
4153
|
-
await
|
|
4160
|
+
await storage3.putShare({ ...stored, wrappedDeviceShare, iv });
|
|
4154
4161
|
}
|
|
4155
4162
|
function hasPasswordUnlock(stored) {
|
|
4156
4163
|
return !!stored && stored.unlockMethod === "password" && !!stored.passwordSalt && !!stored.wrappedUnlockToken && !!stored.unlockTokenIv;
|
|
4157
4164
|
}
|
|
4158
4165
|
async function passwordDeviceCandidate() {
|
|
4159
4166
|
if (persisted?.userId) {
|
|
4160
|
-
const stored = await
|
|
4167
|
+
const stored = await storage3.getShare(persisted.userId);
|
|
4161
4168
|
return hasPasswordUnlock(stored) ? stored : null;
|
|
4162
4169
|
}
|
|
4163
4170
|
if (lastUserId) {
|
|
4164
|
-
const stored = await
|
|
4171
|
+
const stored = await storage3.getShare(lastUserId);
|
|
4165
4172
|
return hasPasswordUnlock(stored) ? stored : null;
|
|
4166
4173
|
}
|
|
4167
|
-
const candidates = (await
|
|
4174
|
+
const candidates = (await storage3.listShares()).filter(hasPasswordUnlock);
|
|
4168
4175
|
return candidates.length === 1 ? candidates[0] : null;
|
|
4169
4176
|
}
|
|
4170
4177
|
async function getWrappingKey(stored) {
|
|
@@ -4192,7 +4199,7 @@ async function createDecaneConnect(config) {
|
|
|
4192
4199
|
let credentialId;
|
|
4193
4200
|
let prfOutput;
|
|
4194
4201
|
try {
|
|
4195
|
-
const label = (await
|
|
4202
|
+
const label = (await storage3.getShare(userId))?.profile?.email;
|
|
4196
4203
|
({ credentialId, prfOutput } = await registerWebAuthnCredential({
|
|
4197
4204
|
api,
|
|
4198
4205
|
jwt,
|
|
@@ -4224,8 +4231,8 @@ async function createDecaneConnect(config) {
|
|
|
4224
4231
|
const wrappingKey = await importAesKey(prfOutput);
|
|
4225
4232
|
const iv = generateRandomBytes(12);
|
|
4226
4233
|
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
4227
|
-
const existing = await
|
|
4228
|
-
await
|
|
4234
|
+
const existing = await storage3.getShare(userId);
|
|
4235
|
+
await storage3.putShare({
|
|
4229
4236
|
...existing,
|
|
4230
4237
|
userId,
|
|
4231
4238
|
wrappedDeviceShare,
|
|
@@ -4309,7 +4316,7 @@ async function createDecaneConnect(config) {
|
|
|
4309
4316
|
const { wrappingKey, tokenKey, meta } = await passwordSetup();
|
|
4310
4317
|
const iv = generateRandomBytes(12);
|
|
4311
4318
|
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
4312
|
-
await
|
|
4319
|
+
await storage3.putShare({
|
|
4313
4320
|
userId,
|
|
4314
4321
|
wrappedDeviceShare,
|
|
4315
4322
|
iv,
|
|
@@ -4383,7 +4390,7 @@ async function createDecaneConnect(config) {
|
|
|
4383
4390
|
shareSet.device,
|
|
4384
4391
|
iv
|
|
4385
4392
|
);
|
|
4386
|
-
await
|
|
4393
|
+
await storage3.putShare({
|
|
4387
4394
|
userId,
|
|
4388
4395
|
wrappedDeviceShare,
|
|
4389
4396
|
iv,
|
|
@@ -4423,7 +4430,7 @@ async function createDecaneConnect(config) {
|
|
|
4423
4430
|
zeroize(shareSet.server);
|
|
4424
4431
|
zeroize(shareSet.device);
|
|
4425
4432
|
zeroize(shareSet.recovery);
|
|
4426
|
-
if (wrappingKey) await
|
|
4433
|
+
if (wrappingKey) await storage3.deleteShare(userId).catch(() => {
|
|
4427
4434
|
});
|
|
4428
4435
|
events.emit("wallet-unlocking");
|
|
4429
4436
|
return returningUserUnlock(userId, jwt);
|
|
@@ -4436,7 +4443,7 @@ async function createDecaneConnect(config) {
|
|
|
4436
4443
|
} catch (e) {
|
|
4437
4444
|
zeroize(shareSet.device);
|
|
4438
4445
|
zeroize(shareSet.recovery);
|
|
4439
|
-
if (wrappingKey) await
|
|
4446
|
+
if (wrappingKey) await storage3.deleteShare(userId).catch(() => {
|
|
4440
4447
|
});
|
|
4441
4448
|
throw e;
|
|
4442
4449
|
}
|
|
@@ -4512,7 +4519,7 @@ async function createDecaneConnect(config) {
|
|
|
4512
4519
|
async function returningUserUnlock(userId, jwt) {
|
|
4513
4520
|
const rawAddresses = await api.getAddresses(jwt);
|
|
4514
4521
|
const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana, tron: rawAddresses.tron ?? evmToTronAddress2(rawAddresses.evm) } : null;
|
|
4515
|
-
const stored = await
|
|
4522
|
+
const stored = await storage3.getShare(userId);
|
|
4516
4523
|
let deviceShare;
|
|
4517
4524
|
let joinedViaPasskeySync = false;
|
|
4518
4525
|
let migrating = false;
|
|
@@ -4564,7 +4571,7 @@ async function createDecaneConnect(config) {
|
|
|
4564
4571
|
rpId,
|
|
4565
4572
|
teeUrl,
|
|
4566
4573
|
api,
|
|
4567
|
-
storage:
|
|
4574
|
+
storage: storage3,
|
|
4568
4575
|
onProgress: config.onRecoveryProgress,
|
|
4569
4576
|
promptForRecoveryFile: config.promptForRecoveryFile,
|
|
4570
4577
|
onNewRecoveryShare: async (recoveryShare, shareSetVersion) => {
|
|
@@ -4578,7 +4585,7 @@ async function createDecaneConnect(config) {
|
|
|
4578
4585
|
});
|
|
4579
4586
|
deviceShare = recovered.deviceShare;
|
|
4580
4587
|
joinedViaPasskeySync = recovered.flow === "passkey-sync";
|
|
4581
|
-
if (!identityTier && !await
|
|
4588
|
+
if (!identityTier && !await storage3.getShare(userId) && isPasskeySupported()) {
|
|
4582
4589
|
await registerPasskeyAndBackup(userId, jwt, deviceShare);
|
|
4583
4590
|
}
|
|
4584
4591
|
} else {
|
|
@@ -4607,7 +4614,7 @@ async function createDecaneConnect(config) {
|
|
|
4607
4614
|
} catch (e) {
|
|
4608
4615
|
if (e instanceof StaleDeviceShareError) {
|
|
4609
4616
|
zeroize(deviceShare);
|
|
4610
|
-
await
|
|
4617
|
+
await storage3.deleteShare(userId);
|
|
4611
4618
|
if (stored) return returningUserUnlock(userId, jwt);
|
|
4612
4619
|
} else {
|
|
4613
4620
|
zeroize(deviceShare);
|
|
@@ -4635,7 +4642,7 @@ async function createDecaneConnect(config) {
|
|
|
4635
4642
|
const result = await tee.rotateShares(jwt);
|
|
4636
4643
|
try {
|
|
4637
4644
|
await persistRotatedDeviceShare(userId, result.deviceShare);
|
|
4638
|
-
const refreshed = await
|
|
4645
|
+
const refreshed = await storage3.getShare(userId);
|
|
4639
4646
|
if (refreshed?.unlockMethod === "passkey" && refreshed.credentialId && refreshed.prfSalt) {
|
|
4640
4647
|
await api.putRecoveryBackup(jwt, {
|
|
4641
4648
|
wrappedDeviceShare: refreshed.wrappedDeviceShare,
|
|
@@ -4668,13 +4675,13 @@ async function createDecaneConnect(config) {
|
|
|
4668
4675
|
}
|
|
4669
4676
|
async function passkeyLoginCandidate() {
|
|
4670
4677
|
if (!persisted?.userId || !isPasskeySupported()) return null;
|
|
4671
|
-
return usablePasskeyShare(await
|
|
4678
|
+
return usablePasskeyShare(await storage3.getShare(persisted.userId));
|
|
4672
4679
|
}
|
|
4673
4680
|
async function passkeyDeviceCandidate() {
|
|
4674
4681
|
if (!isPasskeySupported()) return null;
|
|
4675
4682
|
if (persisted?.userId) return passkeyLoginCandidate();
|
|
4676
|
-
if (lastUserId) return usablePasskeyShare(await
|
|
4677
|
-
const candidates = (await
|
|
4683
|
+
if (lastUserId) return usablePasskeyShare(await storage3.getShare(lastUserId));
|
|
4684
|
+
const candidates = (await storage3.listShares()).map(usablePasskeyShare).filter(Boolean);
|
|
4678
4685
|
return candidates.length === 1 ? candidates[0] : null;
|
|
4679
4686
|
}
|
|
4680
4687
|
async function canOfferPasskey() {
|
|
@@ -4682,15 +4689,15 @@ async function createDecaneConnect(config) {
|
|
|
4682
4689
|
if (await passkeyDeviceCandidate()) return true;
|
|
4683
4690
|
const known = persisted?.userId ?? lastUserId;
|
|
4684
4691
|
if (!known) return false;
|
|
4685
|
-
return await
|
|
4692
|
+
return await storage3.getShare(known) == null;
|
|
4686
4693
|
}
|
|
4687
4694
|
async function rememberProfile(userId, profile) {
|
|
4688
4695
|
const incoming = Object.fromEntries(Object.entries(profile).filter(([, v]) => v));
|
|
4689
4696
|
if (!Object.keys(incoming).length) return;
|
|
4690
4697
|
try {
|
|
4691
|
-
const stored = await
|
|
4698
|
+
const stored = await storage3.getShare(userId);
|
|
4692
4699
|
if (!stored) return;
|
|
4693
|
-
await
|
|
4700
|
+
await storage3.putShare({ ...stored, profile: { ...stored.profile, ...incoming } });
|
|
4694
4701
|
} catch {
|
|
4695
4702
|
}
|
|
4696
4703
|
}
|
|
@@ -4714,7 +4721,7 @@ async function createDecaneConnect(config) {
|
|
|
4714
4721
|
savePersisted(PERSIST_KEY, persisted);
|
|
4715
4722
|
events.emit("connected");
|
|
4716
4723
|
persistLiveSession();
|
|
4717
|
-
const stored = await
|
|
4724
|
+
const stored = await storage3.getShare(userId);
|
|
4718
4725
|
return {
|
|
4719
4726
|
addresses,
|
|
4720
4727
|
isNewUser: false,
|
|
@@ -4774,6 +4781,11 @@ async function createDecaneConnect(config) {
|
|
|
4774
4781
|
return new Promise(() => {
|
|
4775
4782
|
});
|
|
4776
4783
|
}
|
|
4784
|
+
async function finishKingsChatReturn(code) {
|
|
4785
|
+
clearKingsChatState();
|
|
4786
|
+
window.history.replaceState({}, "", window.location.pathname);
|
|
4787
|
+
return handleAuthResult(await api.authKingsChatCode(code));
|
|
4788
|
+
}
|
|
4777
4789
|
async function handleAuthResult(result) {
|
|
4778
4790
|
let addresses;
|
|
4779
4791
|
if (!result.hasShare) {
|
|
@@ -4836,6 +4848,8 @@ async function createDecaneConnect(config) {
|
|
|
4836
4848
|
* this never starts a flow.
|
|
4837
4849
|
*/
|
|
4838
4850
|
async completeRedirectSignIn() {
|
|
4851
|
+
const pending = readKingsChatReturn(window.location.search);
|
|
4852
|
+
if (pending) return finishKingsChatReturn(pending.code);
|
|
4839
4853
|
const outcome = readRedirectOutcome(window.location.search);
|
|
4840
4854
|
if (outcome.kind === "none") {
|
|
4841
4855
|
throw new CallSequenceError("No sign-in result in the URL to complete");
|
|
@@ -4854,12 +4868,12 @@ async function createDecaneConnect(config) {
|
|
|
4854
4868
|
return handleAuthResult(await api.authGoogleToken(idToken));
|
|
4855
4869
|
},
|
|
4856
4870
|
async connectWithKingsChat() {
|
|
4871
|
+
const pending = readKingsChatReturn(window.location.search);
|
|
4872
|
+
if (pending) return finishKingsChatReturn(pending.code);
|
|
4857
4873
|
const { clientId } = await api.getKingsChatConfig();
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
scopes: config.kingsChatScopes
|
|
4874
|
+
window.location.href = beginKingsChatLogin(clientId);
|
|
4875
|
+
return new Promise(() => {
|
|
4861
4876
|
});
|
|
4862
|
-
return client.connectWithKingsChatToken(tokens2.accessToken);
|
|
4863
4877
|
},
|
|
4864
4878
|
async connectWithKingsChatToken(accessToken) {
|
|
4865
4879
|
if (!accessToken) {
|
|
@@ -4873,6 +4887,20 @@ async function createDecaneConnect(config) {
|
|
|
4873
4887
|
const result = await api.authExternal(token, opts?.providerId);
|
|
4874
4888
|
return handleAuthResult(result);
|
|
4875
4889
|
},
|
|
4890
|
+
async linkWithToken(token, opts) {
|
|
4891
|
+
if (!token) throw new CallSequenceError("linkWithToken requires a token");
|
|
4892
|
+
if (!auth) throw new ReconnectRequiredError();
|
|
4893
|
+
const result = await api.linkExternal(auth.jwt, token, opts?.providerId);
|
|
4894
|
+
return result.id ? { alreadyLinked: result.alreadyLinked, id: result.id } : { alreadyLinked: result.alreadyLinked };
|
|
4895
|
+
},
|
|
4896
|
+
async listLinkedLogins() {
|
|
4897
|
+
if (!auth) throw new ReconnectRequiredError();
|
|
4898
|
+
return (await api.listLinkedLogins(auth.jwt)).logins;
|
|
4899
|
+
},
|
|
4900
|
+
async unlinkLogin(id) {
|
|
4901
|
+
if (!auth) throw new ReconnectRequiredError();
|
|
4902
|
+
await api.unlinkLogin(auth.jwt, id);
|
|
4903
|
+
},
|
|
4876
4904
|
async connectWithAccessToken(token) {
|
|
4877
4905
|
const claims = decodeAccessToken(token);
|
|
4878
4906
|
if (!claims) throw new InvalidAccessTokenError("not a Decane access token");
|
|
@@ -4887,7 +4915,7 @@ async function createDecaneConnect(config) {
|
|
|
4887
4915
|
events.emit("connected");
|
|
4888
4916
|
persistLiveSession();
|
|
4889
4917
|
scheduleAccessTokenExpiry(token, claims.expiresAt);
|
|
4890
|
-
const stored = await
|
|
4918
|
+
const stored = await storage3.getShare(claims.userId);
|
|
4891
4919
|
return {
|
|
4892
4920
|
addresses,
|
|
4893
4921
|
isNewUser: false,
|
|
@@ -4923,7 +4951,7 @@ async function createDecaneConnect(config) {
|
|
|
4923
4951
|
const candidate = await passkeyDeviceCandidate();
|
|
4924
4952
|
if (candidate) return passkeyLogin(candidate);
|
|
4925
4953
|
const known = persisted?.userId ?? lastUserId;
|
|
4926
|
-
if (isPasskeySupported() && known && await
|
|
4954
|
+
if (isPasskeySupported() && known && await storage3.getShare(known) == null) {
|
|
4927
4955
|
return passkeyLoginWithoutShare(known);
|
|
4928
4956
|
}
|
|
4929
4957
|
throw new NewDeviceError();
|
|
@@ -4942,7 +4970,7 @@ async function createDecaneConnect(config) {
|
|
|
4942
4970
|
async setUnlockPassword(password) {
|
|
4943
4971
|
if (!auth) throw new ReconnectRequiredError();
|
|
4944
4972
|
validateUnlockPassword(password);
|
|
4945
|
-
const stored = await
|
|
4973
|
+
const stored = await storage3.getShare(auth.userId);
|
|
4946
4974
|
if (!stored) throw new NewDeviceError();
|
|
4947
4975
|
const deviceShare = await aesGcmDecrypt(
|
|
4948
4976
|
await getWrappingKey(stored),
|
|
@@ -4961,7 +4989,7 @@ async function createDecaneConnect(config) {
|
|
|
4961
4989
|
);
|
|
4962
4990
|
const iv = generateRandomBytes(12);
|
|
4963
4991
|
const wrappedDeviceShare = await aesGcmEncrypt(shareKey, deviceShare, iv);
|
|
4964
|
-
await
|
|
4992
|
+
await storage3.putShare({
|
|
4965
4993
|
...stored,
|
|
4966
4994
|
unlockMethod: "password",
|
|
4967
4995
|
wrappedDeviceShare,
|
|
@@ -5041,9 +5069,9 @@ async function createDecaneConnect(config) {
|
|
|
5041
5069
|
async removeUnlockPassword() {
|
|
5042
5070
|
if (!auth) throw new ReconnectRequiredError();
|
|
5043
5071
|
await api.revokeUnlockTokens(auth.jwt);
|
|
5044
|
-
const stored = await
|
|
5072
|
+
const stored = await storage3.getShare(auth.userId);
|
|
5045
5073
|
if (!stored || stored.unlockMethod !== "password") return;
|
|
5046
|
-
await
|
|
5074
|
+
await storage3.putShare({
|
|
5047
5075
|
...stored,
|
|
5048
5076
|
wrappedUnlockToken: void 0,
|
|
5049
5077
|
unlockTokenIv: void 0
|
|
@@ -5051,7 +5079,7 @@ async function createDecaneConnect(config) {
|
|
|
5051
5079
|
},
|
|
5052
5080
|
async hasPasskey() {
|
|
5053
5081
|
if (!auth) return false;
|
|
5054
|
-
const stored = await
|
|
5082
|
+
const stored = await storage3.getShare(auth.userId);
|
|
5055
5083
|
return stored?.unlockMethod === "passkey";
|
|
5056
5084
|
},
|
|
5057
5085
|
async addPasskey() {
|
|
@@ -5062,7 +5090,7 @@ async function createDecaneConnect(config) {
|
|
|
5062
5090
|
}
|
|
5063
5091
|
if (!auth) throw new ReconnectRequiredError();
|
|
5064
5092
|
if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
|
|
5065
|
-
const stored = await
|
|
5093
|
+
const stored = await storage3.getShare(auth.userId);
|
|
5066
5094
|
if (stored?.unlockMethod === "passkey") return;
|
|
5067
5095
|
if (!stored) {
|
|
5068
5096
|
if (!serverAssistedRecovery) throw new NewDeviceError();
|
|
@@ -5095,12 +5123,12 @@ async function createDecaneConnect(config) {
|
|
|
5095
5123
|
},
|
|
5096
5124
|
async deviceProtected() {
|
|
5097
5125
|
if (!auth) return false;
|
|
5098
|
-
return await
|
|
5126
|
+
return await storage3.getShare(auth.userId) !== null;
|
|
5099
5127
|
},
|
|
5100
5128
|
async protectDevice() {
|
|
5101
5129
|
if (identityTier) return false;
|
|
5102
5130
|
if (!auth) throw new ReconnectRequiredError();
|
|
5103
|
-
if (await
|
|
5131
|
+
if (await storage3.getShare(auth.userId)) return true;
|
|
5104
5132
|
if (!serverAssistedRecovery) throw new NewDeviceError();
|
|
5105
5133
|
const tee = new TeeClient(teeUrl);
|
|
5106
5134
|
const provisioned = await tee.provisionDeviceShare(auth.jwt);
|
|
@@ -5110,7 +5138,7 @@ async function createDecaneConnect(config) {
|
|
|
5110
5138
|
} finally {
|
|
5111
5139
|
zeroize(provisioned);
|
|
5112
5140
|
}
|
|
5113
|
-
return await
|
|
5141
|
+
return await storage3.getShare(auth.userId) !== null;
|
|
5114
5142
|
},
|
|
5115
5143
|
async unlock() {
|
|
5116
5144
|
if (!auth) {
|
|
@@ -6073,7 +6101,6 @@ function SignInView({
|
|
|
6073
6101
|
await onKingsChatSignIn();
|
|
6074
6102
|
} catch (e) {
|
|
6075
6103
|
setError(e.message);
|
|
6076
|
-
} finally {
|
|
6077
6104
|
setKingsChatLoading(false);
|
|
6078
6105
|
}
|
|
6079
6106
|
}
|
|
@@ -6209,7 +6236,7 @@ function SignInView({
|
|
|
6209
6236
|
value: phone,
|
|
6210
6237
|
onChange: (e) => setPhone(e.target.value)
|
|
6211
6238
|
}
|
|
6212
|
-
), /* @__PURE__ */ React2.createElement("button", { type: "submit", className: "dcm-submit", disabled: busy || !phone.trim() }, phoneLoading ? "Sending\u2026" : "Submit")), authMethods.includes("x") && onXSignIn && /* @__PURE__ */ React2.createElement("button", { type: "button", className: "dcm-method", onClick: handleX, disabled: busy }, /* @__PURE__ */ React2.createElement(XLogo, { size: 16 }), /* @__PURE__ */ React2.createElement("span", null, xLoading ? "Redirecting to X\u2026" : "X")), authMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ React2.createElement("button", { type: "button", className: "dcm-method", onClick: handleKingsChat, disabled: busy }, /* @__PURE__ */ React2.createElement(ChatIcon, { size: 18 }), /* @__PURE__ */ React2.createElement("span", null, kingsChatLoading ? "
|
|
6239
|
+
), /* @__PURE__ */ React2.createElement("button", { type: "submit", className: "dcm-submit", disabled: busy || !phone.trim() }, phoneLoading ? "Sending\u2026" : "Submit")), authMethods.includes("x") && onXSignIn && /* @__PURE__ */ React2.createElement("button", { type: "button", className: "dcm-method", onClick: handleX, disabled: busy }, /* @__PURE__ */ React2.createElement(XLogo, { size: 16 }), /* @__PURE__ */ React2.createElement("span", null, xLoading ? "Redirecting to X\u2026" : "X")), authMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ React2.createElement("button", { type: "button", className: "dcm-method", onClick: handleKingsChat, disabled: busy }, /* @__PURE__ */ React2.createElement(ChatIcon, { size: 18 }), /* @__PURE__ */ React2.createElement("span", null, kingsChatLoading ? "Redirecting to KingsChat\u2026" : "KingsChat")), customAuthLabel && onCustomAuthSignIn && /* @__PURE__ */ React2.createElement("button", { type: "button", className: "dcm-method", onClick: handleCustomAuth, disabled: busy }, /* @__PURE__ */ React2.createElement(UserIcon, { size: 18 }), /* @__PURE__ */ React2.createElement("span", null, customLoading ? "Connecting\u2026" : customAuthLabel))), extra, errorLine, canUsePasskey && onPasskeySignIn && /* @__PURE__ */ React2.createElement("button", { type: "button", className: "dcm-link", onClick: () => {
|
|
6213
6240
|
setError("");
|
|
6214
6241
|
setShowOtherMethods(false);
|
|
6215
6242
|
} }, "Use my passkey instead"), /* @__PURE__ */ React2.createElement(ProtectedBy, null));
|
|
@@ -6873,7 +6900,7 @@ function DecaneKit({ config, children }) {
|
|
|
6873
6900
|
sdk.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {
|
|
6874
6901
|
});
|
|
6875
6902
|
const params = new URLSearchParams(window.location.search);
|
|
6876
|
-
if (params.has("decane_jwt") || params.has("decane_error")) {
|
|
6903
|
+
if (params.has("decane_jwt") || params.has("decane_error") || readKingsChatReturn(window.location.search)) {
|
|
6877
6904
|
sdk.completeRedirectSignIn().then(applyResult).catch((e) => {
|
|
6878
6905
|
setSocialAuth((p) => p.phase ? { ...p, phase: null } : p);
|
|
6879
6906
|
console.error(e);
|
|
@@ -6961,6 +6988,21 @@ function DecaneKit({ config, children }) {
|
|
|
6961
6988
|
throw e;
|
|
6962
6989
|
}
|
|
6963
6990
|
}, [clearPhase]);
|
|
6991
|
+
const linkWithToken = useCallback3(async (token, opts) => {
|
|
6992
|
+
const sdk = socialRef.current;
|
|
6993
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
6994
|
+
return sdk.linkWithToken(token, opts);
|
|
6995
|
+
}, []);
|
|
6996
|
+
const listLinkedLogins = useCallback3(async () => {
|
|
6997
|
+
const sdk = socialRef.current;
|
|
6998
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
6999
|
+
return sdk.listLinkedLogins();
|
|
7000
|
+
}, []);
|
|
7001
|
+
const unlinkLogin = useCallback3(async (id) => {
|
|
7002
|
+
const sdk = socialRef.current;
|
|
7003
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
7004
|
+
await sdk.unlinkLogin(id);
|
|
7005
|
+
}, []);
|
|
6964
7006
|
const connectWithToken = useCallback3(async (token, opts) => {
|
|
6965
7007
|
const sdk = socialRef.current;
|
|
6966
7008
|
if (!sdk) throw new Error("Social sign-in not configured");
|
|
@@ -7178,6 +7220,9 @@ function DecaneKit({ config, children }) {
|
|
|
7178
7220
|
verifyPhoneCode: verifyPhoneCode2,
|
|
7179
7221
|
connectWithToken,
|
|
7180
7222
|
connectWithAccessToken,
|
|
7223
|
+
linkWithToken,
|
|
7224
|
+
listLinkedLogins,
|
|
7225
|
+
unlinkLogin,
|
|
7181
7226
|
customAuthLabel: customAuthConfig?.label,
|
|
7182
7227
|
connectWithCustomAuth: customAuthConfig ? connectWithCustomAuth : void 0,
|
|
7183
7228
|
socialUnlock,
|
|
@@ -7294,6 +7339,9 @@ function useSocialAuth() {
|
|
|
7294
7339
|
verifyPhoneCode: verifyPhoneCode2,
|
|
7295
7340
|
connectWithToken,
|
|
7296
7341
|
connectWithAccessToken,
|
|
7342
|
+
linkWithToken,
|
|
7343
|
+
listLinkedLogins,
|
|
7344
|
+
unlinkLogin,
|
|
7297
7345
|
customAuthLabel,
|
|
7298
7346
|
connectWithCustomAuth,
|
|
7299
7347
|
signInWithPasskey: ctxSignInWithPasskey,
|
|
@@ -7497,6 +7545,11 @@ function useSocialAuth() {
|
|
|
7497
7545
|
confirmPhoneCode,
|
|
7498
7546
|
signInWithToken,
|
|
7499
7547
|
signInWithAccessToken,
|
|
7548
|
+
// Linked logins: while signed in, attach a login from your own auth (a
|
|
7549
|
+
// phone number your backend verified, say) so it opens this same wallet.
|
|
7550
|
+
linkWithToken,
|
|
7551
|
+
listLinkedLogins,
|
|
7552
|
+
unlinkLogin,
|
|
7500
7553
|
signInWithCustomAuth: connectWithCustomAuth ? signInWithCustomAuth : void 0,
|
|
7501
7554
|
signInWithPasskey,
|
|
7502
7555
|
unlock,
|
|
@@ -8050,6 +8103,7 @@ export {
|
|
|
8050
8103
|
InvalidApiKeyError,
|
|
8051
8104
|
InvalidPinError,
|
|
8052
8105
|
InvalidRecoveryFileError,
|
|
8106
|
+
LoginInUseError,
|
|
8053
8107
|
MIN_PASSWORD_LENGTH,
|
|
8054
8108
|
NetworkError,
|
|
8055
8109
|
NewDeviceError,
|