decane-connect-kit 2.8.0 → 2.9.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 +92 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +92 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -357,6 +357,26 @@ interface DecaneConnect {
|
|
|
357
357
|
* yourself. Throws NewDeviceError when this device isn't eligible.
|
|
358
358
|
*/
|
|
359
359
|
signInWithPasskey(): Promise<ConnectResult>;
|
|
360
|
+
/** Whether THIS device's share is wrapped by a passkey rather than a PIN. */
|
|
361
|
+
hasPasskey(): Promise<boolean>;
|
|
362
|
+
/**
|
|
363
|
+
* Add a passkey to this device after the fact, re-wrapping its device share
|
|
364
|
+
* under it.
|
|
365
|
+
*
|
|
366
|
+
* For the device that fell back to a PIN because no passkey was reachable at
|
|
367
|
+
* the time — a password manager that was not signed in, a browser without
|
|
368
|
+
* WebAuthn PRF, a prompt the user dismissed. Without this the fallback is
|
|
369
|
+
* permanent: once a PIN-wrapped share exists, nothing in the normal unlock
|
|
370
|
+
* path ever offers a passkey again, however available they later become.
|
|
371
|
+
*
|
|
372
|
+
* Prompts for the current PIN (through `promptPin`) to unwrap the share, then
|
|
373
|
+
* asks for the new credential. Idempotent when a passkey is already in place.
|
|
374
|
+
* Requires a live session; throws `WebAuthnNotSupportedError` where passkeys
|
|
375
|
+
* are unavailable, and `NewDeviceError` when this device holds no share yet
|
|
376
|
+
* (that case wants `connect()`, which provisions one and registers a passkey
|
|
377
|
+
* on the way).
|
|
378
|
+
*/
|
|
379
|
+
addPasskey(): Promise<void>;
|
|
360
380
|
sessionExpiresAt(): number | null;
|
|
361
381
|
signMessage(req: SignMessageRequest): Promise<string>;
|
|
362
382
|
sendTransaction(req: SendTransactionRequest): Promise<string>;
|
|
@@ -477,7 +497,8 @@ interface WrappedShareMeta {
|
|
|
477
497
|
* after a page reload.
|
|
478
498
|
*
|
|
479
499
|
* Every field is optional because providers differ in what they release, and a
|
|
480
|
-
* user can withhold most of it. Email sign-in returns
|
|
500
|
+
* user can withhold most of it. Email sign-in returns a profile carrying just
|
|
501
|
+
* the `email` the user signed in with; phone sign-in returns no profile.
|
|
481
502
|
*/
|
|
482
503
|
interface AuthProfile {
|
|
483
504
|
name?: string;
|
|
@@ -1205,6 +1226,7 @@ declare function useSocialAuth(): {
|
|
|
1205
1226
|
isCreatingWallet: boolean;
|
|
1206
1227
|
needsReconnect: boolean;
|
|
1207
1228
|
googleLoading: boolean;
|
|
1229
|
+
kingschatLoading: boolean;
|
|
1208
1230
|
emailLoading: boolean;
|
|
1209
1231
|
phoneLoading: boolean;
|
|
1210
1232
|
tokenLoading: boolean;
|
|
@@ -1213,6 +1235,7 @@ declare function useSocialAuth(): {
|
|
|
1213
1235
|
error: string | null;
|
|
1214
1236
|
clearError: () => void;
|
|
1215
1237
|
signInWithGoogle: () => Promise<void>;
|
|
1238
|
+
signInWithKingsChat: () => Promise<void>;
|
|
1216
1239
|
sendEmailCode: (email: string) => Promise<{
|
|
1217
1240
|
codeRequired: true;
|
|
1218
1241
|
}>;
|
|
@@ -1282,6 +1305,8 @@ declare function useSocialWallet(): {
|
|
|
1282
1305
|
chainId?: number;
|
|
1283
1306
|
}) => Eip1193Provider;
|
|
1284
1307
|
getAccessToken: () => string | null;
|
|
1308
|
+
hasPasskey: () => Promise<boolean>;
|
|
1309
|
+
addPasskey: () => Promise<void>;
|
|
1285
1310
|
rotateShares: (opts: {
|
|
1286
1311
|
password: string;
|
|
1287
1312
|
passwordHint?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -357,6 +357,26 @@ interface DecaneConnect {
|
|
|
357
357
|
* yourself. Throws NewDeviceError when this device isn't eligible.
|
|
358
358
|
*/
|
|
359
359
|
signInWithPasskey(): Promise<ConnectResult>;
|
|
360
|
+
/** Whether THIS device's share is wrapped by a passkey rather than a PIN. */
|
|
361
|
+
hasPasskey(): Promise<boolean>;
|
|
362
|
+
/**
|
|
363
|
+
* Add a passkey to this device after the fact, re-wrapping its device share
|
|
364
|
+
* under it.
|
|
365
|
+
*
|
|
366
|
+
* For the device that fell back to a PIN because no passkey was reachable at
|
|
367
|
+
* the time — a password manager that was not signed in, a browser without
|
|
368
|
+
* WebAuthn PRF, a prompt the user dismissed. Without this the fallback is
|
|
369
|
+
* permanent: once a PIN-wrapped share exists, nothing in the normal unlock
|
|
370
|
+
* path ever offers a passkey again, however available they later become.
|
|
371
|
+
*
|
|
372
|
+
* Prompts for the current PIN (through `promptPin`) to unwrap the share, then
|
|
373
|
+
* asks for the new credential. Idempotent when a passkey is already in place.
|
|
374
|
+
* Requires a live session; throws `WebAuthnNotSupportedError` where passkeys
|
|
375
|
+
* are unavailable, and `NewDeviceError` when this device holds no share yet
|
|
376
|
+
* (that case wants `connect()`, which provisions one and registers a passkey
|
|
377
|
+
* on the way).
|
|
378
|
+
*/
|
|
379
|
+
addPasskey(): Promise<void>;
|
|
360
380
|
sessionExpiresAt(): number | null;
|
|
361
381
|
signMessage(req: SignMessageRequest): Promise<string>;
|
|
362
382
|
sendTransaction(req: SendTransactionRequest): Promise<string>;
|
|
@@ -477,7 +497,8 @@ interface WrappedShareMeta {
|
|
|
477
497
|
* after a page reload.
|
|
478
498
|
*
|
|
479
499
|
* Every field is optional because providers differ in what they release, and a
|
|
480
|
-
* user can withhold most of it. Email sign-in returns
|
|
500
|
+
* user can withhold most of it. Email sign-in returns a profile carrying just
|
|
501
|
+
* the `email` the user signed in with; phone sign-in returns no profile.
|
|
481
502
|
*/
|
|
482
503
|
interface AuthProfile {
|
|
483
504
|
name?: string;
|
|
@@ -1205,6 +1226,7 @@ declare function useSocialAuth(): {
|
|
|
1205
1226
|
isCreatingWallet: boolean;
|
|
1206
1227
|
needsReconnect: boolean;
|
|
1207
1228
|
googleLoading: boolean;
|
|
1229
|
+
kingschatLoading: boolean;
|
|
1208
1230
|
emailLoading: boolean;
|
|
1209
1231
|
phoneLoading: boolean;
|
|
1210
1232
|
tokenLoading: boolean;
|
|
@@ -1213,6 +1235,7 @@ declare function useSocialAuth(): {
|
|
|
1213
1235
|
error: string | null;
|
|
1214
1236
|
clearError: () => void;
|
|
1215
1237
|
signInWithGoogle: () => Promise<void>;
|
|
1238
|
+
signInWithKingsChat: () => Promise<void>;
|
|
1216
1239
|
sendEmailCode: (email: string) => Promise<{
|
|
1217
1240
|
codeRequired: true;
|
|
1218
1241
|
}>;
|
|
@@ -1282,6 +1305,8 @@ declare function useSocialWallet(): {
|
|
|
1282
1305
|
chainId?: number;
|
|
1283
1306
|
}) => Eip1193Provider;
|
|
1284
1307
|
getAccessToken: () => string | null;
|
|
1308
|
+
hasPasskey: () => Promise<boolean>;
|
|
1309
|
+
addPasskey: () => Promise<void>;
|
|
1285
1310
|
rotateShares: (opts: {
|
|
1286
1311
|
password: string;
|
|
1287
1312
|
passwordHint?: string;
|
package/dist/index.js
CHANGED
|
@@ -3618,20 +3618,17 @@ async function createDecaneConnect(config) {
|
|
|
3618
3618
|
const pin = await config.promptPin();
|
|
3619
3619
|
return deriveKeyFromPin(pin, stored.pinSalt);
|
|
3620
3620
|
}
|
|
3621
|
-
async function
|
|
3622
|
-
if (!isPasskeySupported())
|
|
3621
|
+
async function registerPasskeyForDevice(userId, jwt, deviceShare) {
|
|
3622
|
+
if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
|
|
3623
3623
|
const prfSalt = generateRandomBytes(32);
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
}
|
|
3631
|
-
|
|
3632
|
-
return;
|
|
3633
|
-
}
|
|
3634
|
-
if (!prfOutput) return;
|
|
3624
|
+
const { credentialId, prfOutput } = await registerWebAuthnCredential({
|
|
3625
|
+
api,
|
|
3626
|
+
jwt,
|
|
3627
|
+
rpId,
|
|
3628
|
+
rpName,
|
|
3629
|
+
prfSalt
|
|
3630
|
+
});
|
|
3631
|
+
if (!prfOutput) throw new WebAuthnNotSupportedError();
|
|
3635
3632
|
const wrappingKey = await importAesKey(prfOutput);
|
|
3636
3633
|
const iv = generateRandomBytes(12);
|
|
3637
3634
|
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
@@ -3656,6 +3653,17 @@ async function createDecaneConnect(config) {
|
|
|
3656
3653
|
console.warn("decane-connect-kit: wrapped share backup upload failed; user can retry from settings.", e);
|
|
3657
3654
|
}
|
|
3658
3655
|
}
|
|
3656
|
+
async function registerPasskeyAndBackup(userId, jwt, deviceShare) {
|
|
3657
|
+
if (!isPasskeySupported()) return;
|
|
3658
|
+
try {
|
|
3659
|
+
await registerPasskeyForDevice(userId, jwt, deviceShare);
|
|
3660
|
+
} catch (e) {
|
|
3661
|
+
console.warn(
|
|
3662
|
+
"decane-connect-kit: passkey provisioning failed; the user can set one up later with addPasskey().",
|
|
3663
|
+
e
|
|
3664
|
+
);
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3659
3667
|
async function firstTimeSetup(userId, jwt) {
|
|
3660
3668
|
prewarmAttestation(teeUrl);
|
|
3661
3669
|
let unlockMethod;
|
|
@@ -4105,6 +4113,34 @@ async function createDecaneConnect(config) {
|
|
|
4105
4113
|
if (!candidate) throw new NewDeviceError();
|
|
4106
4114
|
return passkeyLogin(candidate);
|
|
4107
4115
|
},
|
|
4116
|
+
async hasPasskey() {
|
|
4117
|
+
if (!auth) return false;
|
|
4118
|
+
const stored = await storage.getShare(auth.userId);
|
|
4119
|
+
return stored?.unlockMethod === "passkey";
|
|
4120
|
+
},
|
|
4121
|
+
async addPasskey() {
|
|
4122
|
+
if (!auth) throw new ReconnectRequiredError();
|
|
4123
|
+
if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
|
|
4124
|
+
const stored = await storage.getShare(auth.userId);
|
|
4125
|
+
if (!stored) throw new NewDeviceError();
|
|
4126
|
+
if (stored.unlockMethod === "passkey") return;
|
|
4127
|
+
let deviceShare;
|
|
4128
|
+
try {
|
|
4129
|
+
deviceShare = await aesGcmDecrypt(
|
|
4130
|
+
await getWrappingKey(stored),
|
|
4131
|
+
stored.wrappedDeviceShare,
|
|
4132
|
+
stored.iv
|
|
4133
|
+
);
|
|
4134
|
+
} catch (e) {
|
|
4135
|
+
if (e instanceof DecaneConnectError) throw e;
|
|
4136
|
+
throw new ShareCorruptedError();
|
|
4137
|
+
}
|
|
4138
|
+
try {
|
|
4139
|
+
await registerPasskeyForDevice(auth.userId, auth.jwt, deviceShare);
|
|
4140
|
+
} finally {
|
|
4141
|
+
zeroize(deviceShare);
|
|
4142
|
+
}
|
|
4143
|
+
},
|
|
4108
4144
|
async unlock() {
|
|
4109
4145
|
if (!auth) {
|
|
4110
4146
|
const candidate = await passkeyLoginCandidate();
|
|
@@ -6871,6 +6907,16 @@ function DecaneKit({ config, children }) {
|
|
|
6871
6907
|
if (!sdk) throw new Error("Social wallet not connected");
|
|
6872
6908
|
return sdk.exportPortableBackup(opts);
|
|
6873
6909
|
}, []);
|
|
6910
|
+
const socialHasPasskey = useCallback3(async () => {
|
|
6911
|
+
const sdk = socialRef.current;
|
|
6912
|
+
if (!sdk) throw new Error("Social wallet not connected");
|
|
6913
|
+
return sdk.hasPasskey();
|
|
6914
|
+
}, []);
|
|
6915
|
+
const socialAddPasskey = useCallback3(async () => {
|
|
6916
|
+
const sdk = socialRef.current;
|
|
6917
|
+
if (!sdk) throw new Error("Social wallet not connected");
|
|
6918
|
+
return sdk.addPasskey();
|
|
6919
|
+
}, []);
|
|
6874
6920
|
const socialHasRecoveryShare = useCallback3(async () => {
|
|
6875
6921
|
const sdk = socialRef.current;
|
|
6876
6922
|
if (!sdk) throw new Error("Social wallet not connected");
|
|
@@ -6932,6 +6978,8 @@ function DecaneKit({ config, children }) {
|
|
|
6932
6978
|
socialRotateShares,
|
|
6933
6979
|
socialExportPortableBackup,
|
|
6934
6980
|
socialHasRecoveryShare,
|
|
6981
|
+
socialHasPasskey,
|
|
6982
|
+
socialAddPasskey,
|
|
6935
6983
|
socialShouldPromptRecovery,
|
|
6936
6984
|
socialDismissRecoveryPrompt,
|
|
6937
6985
|
socialModalOpen,
|
|
@@ -7010,6 +7058,7 @@ function useSocialAuth() {
|
|
|
7010
7058
|
const {
|
|
7011
7059
|
socialAuth,
|
|
7012
7060
|
connectWithGoogle,
|
|
7061
|
+
connectWithKingsChat,
|
|
7013
7062
|
connectWithEmail,
|
|
7014
7063
|
verifyEmailCode: verifyEmailCode2,
|
|
7015
7064
|
connectWithPhone,
|
|
@@ -7022,6 +7071,7 @@ function useSocialAuth() {
|
|
|
7022
7071
|
socialDisconnect
|
|
7023
7072
|
} = useDecaneContext();
|
|
7024
7073
|
const [googleLoading, setGoogleLoading] = useState4(false);
|
|
7074
|
+
const [kingschatLoading, setKingschatLoading] = useState4(false);
|
|
7025
7075
|
const [emailLoading, setEmailLoading] = useState4(false);
|
|
7026
7076
|
const [phoneLoading, setPhoneLoading] = useState4(false);
|
|
7027
7077
|
const [tokenLoading, setTokenLoading] = useState4(false);
|
|
@@ -7037,6 +7087,18 @@ function useSocialAuth() {
|
|
|
7037
7087
|
setGoogleLoading(false);
|
|
7038
7088
|
}
|
|
7039
7089
|
}, [connectWithGoogle]);
|
|
7090
|
+
const signInWithKingsChat = useCallback4(async () => {
|
|
7091
|
+
setKingschatLoading(true);
|
|
7092
|
+
setError(null);
|
|
7093
|
+
try {
|
|
7094
|
+
await connectWithKingsChat();
|
|
7095
|
+
} catch (e) {
|
|
7096
|
+
setError(e.message);
|
|
7097
|
+
throw e;
|
|
7098
|
+
} finally {
|
|
7099
|
+
setKingschatLoading(false);
|
|
7100
|
+
}
|
|
7101
|
+
}, [connectWithKingsChat]);
|
|
7040
7102
|
const sendEmailCode = useCallback4(async (email) => {
|
|
7041
7103
|
setEmailLoading(true);
|
|
7042
7104
|
setError(null);
|
|
@@ -7156,6 +7218,7 @@ function useSocialAuth() {
|
|
|
7156
7218
|
// this into a real reconnect (signInWithGoogle / sendEmailCode), not unlock().
|
|
7157
7219
|
needsReconnect: socialAuth.needsReconnect,
|
|
7158
7220
|
googleLoading,
|
|
7221
|
+
kingschatLoading,
|
|
7159
7222
|
emailLoading,
|
|
7160
7223
|
phoneLoading,
|
|
7161
7224
|
tokenLoading,
|
|
@@ -7164,6 +7227,7 @@ function useSocialAuth() {
|
|
|
7164
7227
|
error,
|
|
7165
7228
|
clearError: () => setError(null),
|
|
7166
7229
|
signInWithGoogle,
|
|
7230
|
+
signInWithKingsChat,
|
|
7167
7231
|
sendEmailCode,
|
|
7168
7232
|
confirmEmailCode,
|
|
7169
7233
|
sendPhoneCode,
|
|
@@ -7195,6 +7259,8 @@ function useSocialWallet() {
|
|
|
7195
7259
|
socialRotateShares,
|
|
7196
7260
|
socialExportPortableBackup,
|
|
7197
7261
|
socialHasRecoveryShare,
|
|
7262
|
+
socialHasPasskey,
|
|
7263
|
+
socialAddPasskey,
|
|
7198
7264
|
socialShouldPromptRecovery,
|
|
7199
7265
|
socialDismissRecoveryPrompt,
|
|
7200
7266
|
openSocialModal,
|
|
@@ -7282,6 +7348,16 @@ function useSocialWallet() {
|
|
|
7282
7348
|
(opts) => run(() => socialExportPortableBackup(opts)),
|
|
7283
7349
|
[socialExportPortableBackup]
|
|
7284
7350
|
);
|
|
7351
|
+
const hasPasskey = useCallback4(() => run(() => socialHasPasskey()), [socialHasPasskey]);
|
|
7352
|
+
const addPasskey = useCallback4(async () => {
|
|
7353
|
+
setError(null);
|
|
7354
|
+
try {
|
|
7355
|
+
await socialAddPasskey();
|
|
7356
|
+
} catch (e) {
|
|
7357
|
+
setError(e.message);
|
|
7358
|
+
throw e;
|
|
7359
|
+
}
|
|
7360
|
+
}, [socialAddPasskey]);
|
|
7285
7361
|
const hasRecoveryShare = useCallback4(
|
|
7286
7362
|
() => run(() => socialHasRecoveryShare()),
|
|
7287
7363
|
[socialHasRecoveryShare]
|
|
@@ -7335,6 +7411,9 @@ function useSocialWallet() {
|
|
|
7335
7411
|
// The Decane JWT — authenticate the user to your own backend (bundler proxy,
|
|
7336
7412
|
// etc.); verify server-side against backend.decane.app/.well-known/jwks.json.
|
|
7337
7413
|
getAccessToken: socialGetAccessToken,
|
|
7414
|
+
// Passkey management for THIS device
|
|
7415
|
+
hasPasskey,
|
|
7416
|
+
addPasskey,
|
|
7338
7417
|
// Recovery (2-of-3 Shamir share set)
|
|
7339
7418
|
rotateShares,
|
|
7340
7419
|
exportPortableBackup,
|