decane-connect-kit 2.8.1 → 2.9.1
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 +81 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +81 -17
- 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;
|
|
@@ -1284,6 +1305,8 @@ declare function useSocialWallet(): {
|
|
|
1284
1305
|
chainId?: number;
|
|
1285
1306
|
}) => Eip1193Provider;
|
|
1286
1307
|
getAccessToken: () => string | null;
|
|
1308
|
+
hasPasskey: () => Promise<boolean>;
|
|
1309
|
+
addPasskey: () => Promise<void>;
|
|
1287
1310
|
rotateShares: (opts: {
|
|
1288
1311
|
password: string;
|
|
1289
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;
|
|
@@ -1284,6 +1305,8 @@ declare function useSocialWallet(): {
|
|
|
1284
1305
|
chainId?: number;
|
|
1285
1306
|
}) => Eip1193Provider;
|
|
1286
1307
|
getAccessToken: () => string | null;
|
|
1308
|
+
hasPasskey: () => Promise<boolean>;
|
|
1309
|
+
addPasskey: () => Promise<void>;
|
|
1287
1310
|
rotateShares: (opts: {
|
|
1288
1311
|
password: string;
|
|
1289
1312
|
passwordHint?: string;
|
package/dist/index.js
CHANGED
|
@@ -2215,14 +2215,15 @@ import { getCollateralAndVerify } from "@phala/dcap-qvl";
|
|
|
2215
2215
|
|
|
2216
2216
|
// ../../infra/tee-measurement.json
|
|
2217
2217
|
var tee_measurement_default = {
|
|
2218
|
-
signerVersion: "0.
|
|
2218
|
+
signerVersion: "0.2.10",
|
|
2219
2219
|
acceptedMrtd: [
|
|
2220
|
+
'["fb14dc139f33d6fcf474bc8332cac001259fb31cfbcb6b34d4ceeb552a2c4466884a0cbde45ad98a05c5c060c23ad65a","07e6f51aa763abfe75c3ddfbf4f425fe3f0ceff66d807a75e049303dce9addf68e7218729bd419638af63a370f65878c","a2a58c9a959a4fa44bd6da0c97a2270c051faf12084cfe91ae900e4fdff6cdd4f69a82005e04ee920f231497894d677f","a76a81147b7f733cb400914c8791ec149bb3a31f3727b1c1e12ec22e9c1a183b6e617e78636dca37075f5a695afd46c2"]',
|
|
2220
2221
|
'["68102e7b524af310f7b7d426ce75481e36c40f5d513a9009c046e9d37e31551f0134d954b496a3357fd61d03f07ffe96","07e6f51aa763abfe75c3ddfbf4f425fe3f0ceff66d807a75e049303dce9addf68e7218729bd419638af63a370f65878c","a2a58c9a959a4fa44bd6da0c97a2270c051faf12084cfe91ae900e4fdff6cdd4f69a82005e04ee920f231497894d677f","a76a81147b7f733cb400914c8791ec149bb3a31f3727b1c1e12ec22e9c1a183b6e617e78636dca37075f5a695afd46c2"]'
|
|
2221
2222
|
],
|
|
2222
|
-
primaryMrtd: '["
|
|
2223
|
-
capturedAt: "2026-
|
|
2223
|
+
primaryMrtd: '["fb14dc139f33d6fcf474bc8332cac001259fb31cfbcb6b34d4ceeb552a2c4466884a0cbde45ad98a05c5c060c23ad65a","07e6f51aa763abfe75c3ddfbf4f425fe3f0ceff66d807a75e049303dce9addf68e7218729bd419638af63a370f65878c","a2a58c9a959a4fa44bd6da0c97a2270c051faf12084cfe91ae900e4fdff6cdd4f69a82005e04ee920f231497894d677f","a76a81147b7f733cb400914c8791ec149bb3a31f3727b1c1e12ec22e9c1a183b6e617e78636dca37075f5a695afd46c2"]',
|
|
2224
|
+
capturedAt: "2026-09-07T07:19:30.544601Z",
|
|
2224
2225
|
tcbStatus: "UpToDate",
|
|
2225
|
-
_note: "Captured from
|
|
2226
|
+
_note: "Captured from the live CVM 786e714d-5042-4bf1-8060-0bc358f6a72f (app_4405742df08d\u2026) after the 2026-09-07 credential recovery. RTMR0 changed; RTMR1-3 did not \u2014 RTMR3 is the app-compose measurement, so the app's identity is unchanged and this is a platform/firmware move underneath the CVM (Phala node v0.6.0), not a change to what we deploy. Sealing was verified intact across it (/signer/sealing-status: sealingAvailable, roundTripOk, ready), so no auth_shares were lost. The previous measurement is retained above so a rollback still verifies. measurement is the JSON.stringify(replayRtmrs()) output, compared as an opaque string (not parsed per-RTMR), matching social/attestation.ts."
|
|
2226
2227
|
};
|
|
2227
2228
|
|
|
2228
2229
|
// src/social/attestation.ts
|
|
@@ -3618,20 +3619,17 @@ async function createDecaneConnect(config) {
|
|
|
3618
3619
|
const pin = await config.promptPin();
|
|
3619
3620
|
return deriveKeyFromPin(pin, stored.pinSalt);
|
|
3620
3621
|
}
|
|
3621
|
-
async function
|
|
3622
|
-
if (!isPasskeySupported())
|
|
3622
|
+
async function registerPasskeyForDevice(userId, jwt, deviceShare) {
|
|
3623
|
+
if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
|
|
3623
3624
|
const prfSalt = generateRandomBytes(32);
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
}
|
|
3631
|
-
|
|
3632
|
-
return;
|
|
3633
|
-
}
|
|
3634
|
-
if (!prfOutput) return;
|
|
3625
|
+
const { credentialId, prfOutput } = await registerWebAuthnCredential({
|
|
3626
|
+
api,
|
|
3627
|
+
jwt,
|
|
3628
|
+
rpId,
|
|
3629
|
+
rpName,
|
|
3630
|
+
prfSalt
|
|
3631
|
+
});
|
|
3632
|
+
if (!prfOutput) throw new WebAuthnNotSupportedError();
|
|
3635
3633
|
const wrappingKey = await importAesKey(prfOutput);
|
|
3636
3634
|
const iv = generateRandomBytes(12);
|
|
3637
3635
|
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
@@ -3656,6 +3654,17 @@ async function createDecaneConnect(config) {
|
|
|
3656
3654
|
console.warn("decane-connect-kit: wrapped share backup upload failed; user can retry from settings.", e);
|
|
3657
3655
|
}
|
|
3658
3656
|
}
|
|
3657
|
+
async function registerPasskeyAndBackup(userId, jwt, deviceShare) {
|
|
3658
|
+
if (!isPasskeySupported()) return;
|
|
3659
|
+
try {
|
|
3660
|
+
await registerPasskeyForDevice(userId, jwt, deviceShare);
|
|
3661
|
+
} catch (e) {
|
|
3662
|
+
console.warn(
|
|
3663
|
+
"decane-connect-kit: passkey provisioning failed; the user can set one up later with addPasskey().",
|
|
3664
|
+
e
|
|
3665
|
+
);
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3659
3668
|
async function firstTimeSetup(userId, jwt) {
|
|
3660
3669
|
prewarmAttestation(teeUrl);
|
|
3661
3670
|
let unlockMethod;
|
|
@@ -4105,6 +4114,34 @@ async function createDecaneConnect(config) {
|
|
|
4105
4114
|
if (!candidate) throw new NewDeviceError();
|
|
4106
4115
|
return passkeyLogin(candidate);
|
|
4107
4116
|
},
|
|
4117
|
+
async hasPasskey() {
|
|
4118
|
+
if (!auth) return false;
|
|
4119
|
+
const stored = await storage.getShare(auth.userId);
|
|
4120
|
+
return stored?.unlockMethod === "passkey";
|
|
4121
|
+
},
|
|
4122
|
+
async addPasskey() {
|
|
4123
|
+
if (!auth) throw new ReconnectRequiredError();
|
|
4124
|
+
if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
|
|
4125
|
+
const stored = await storage.getShare(auth.userId);
|
|
4126
|
+
if (!stored) throw new NewDeviceError();
|
|
4127
|
+
if (stored.unlockMethod === "passkey") return;
|
|
4128
|
+
let deviceShare;
|
|
4129
|
+
try {
|
|
4130
|
+
deviceShare = await aesGcmDecrypt(
|
|
4131
|
+
await getWrappingKey(stored),
|
|
4132
|
+
stored.wrappedDeviceShare,
|
|
4133
|
+
stored.iv
|
|
4134
|
+
);
|
|
4135
|
+
} catch (e) {
|
|
4136
|
+
if (e instanceof DecaneConnectError) throw e;
|
|
4137
|
+
throw new ShareCorruptedError();
|
|
4138
|
+
}
|
|
4139
|
+
try {
|
|
4140
|
+
await registerPasskeyForDevice(auth.userId, auth.jwt, deviceShare);
|
|
4141
|
+
} finally {
|
|
4142
|
+
zeroize(deviceShare);
|
|
4143
|
+
}
|
|
4144
|
+
},
|
|
4108
4145
|
async unlock() {
|
|
4109
4146
|
if (!auth) {
|
|
4110
4147
|
const candidate = await passkeyLoginCandidate();
|
|
@@ -6871,6 +6908,16 @@ function DecaneKit({ config, children }) {
|
|
|
6871
6908
|
if (!sdk) throw new Error("Social wallet not connected");
|
|
6872
6909
|
return sdk.exportPortableBackup(opts);
|
|
6873
6910
|
}, []);
|
|
6911
|
+
const socialHasPasskey = useCallback3(async () => {
|
|
6912
|
+
const sdk = socialRef.current;
|
|
6913
|
+
if (!sdk) throw new Error("Social wallet not connected");
|
|
6914
|
+
return sdk.hasPasskey();
|
|
6915
|
+
}, []);
|
|
6916
|
+
const socialAddPasskey = useCallback3(async () => {
|
|
6917
|
+
const sdk = socialRef.current;
|
|
6918
|
+
if (!sdk) throw new Error("Social wallet not connected");
|
|
6919
|
+
return sdk.addPasskey();
|
|
6920
|
+
}, []);
|
|
6874
6921
|
const socialHasRecoveryShare = useCallback3(async () => {
|
|
6875
6922
|
const sdk = socialRef.current;
|
|
6876
6923
|
if (!sdk) throw new Error("Social wallet not connected");
|
|
@@ -6932,6 +6979,8 @@ function DecaneKit({ config, children }) {
|
|
|
6932
6979
|
socialRotateShares,
|
|
6933
6980
|
socialExportPortableBackup,
|
|
6934
6981
|
socialHasRecoveryShare,
|
|
6982
|
+
socialHasPasskey,
|
|
6983
|
+
socialAddPasskey,
|
|
6935
6984
|
socialShouldPromptRecovery,
|
|
6936
6985
|
socialDismissRecoveryPrompt,
|
|
6937
6986
|
socialModalOpen,
|
|
@@ -7211,6 +7260,8 @@ function useSocialWallet() {
|
|
|
7211
7260
|
socialRotateShares,
|
|
7212
7261
|
socialExportPortableBackup,
|
|
7213
7262
|
socialHasRecoveryShare,
|
|
7263
|
+
socialHasPasskey,
|
|
7264
|
+
socialAddPasskey,
|
|
7214
7265
|
socialShouldPromptRecovery,
|
|
7215
7266
|
socialDismissRecoveryPrompt,
|
|
7216
7267
|
openSocialModal,
|
|
@@ -7298,6 +7349,16 @@ function useSocialWallet() {
|
|
|
7298
7349
|
(opts) => run(() => socialExportPortableBackup(opts)),
|
|
7299
7350
|
[socialExportPortableBackup]
|
|
7300
7351
|
);
|
|
7352
|
+
const hasPasskey = useCallback4(() => run(() => socialHasPasskey()), [socialHasPasskey]);
|
|
7353
|
+
const addPasskey = useCallback4(async () => {
|
|
7354
|
+
setError(null);
|
|
7355
|
+
try {
|
|
7356
|
+
await socialAddPasskey();
|
|
7357
|
+
} catch (e) {
|
|
7358
|
+
setError(e.message);
|
|
7359
|
+
throw e;
|
|
7360
|
+
}
|
|
7361
|
+
}, [socialAddPasskey]);
|
|
7301
7362
|
const hasRecoveryShare = useCallback4(
|
|
7302
7363
|
() => run(() => socialHasRecoveryShare()),
|
|
7303
7364
|
[socialHasRecoveryShare]
|
|
@@ -7351,6 +7412,9 @@ function useSocialWallet() {
|
|
|
7351
7412
|
// The Decane JWT — authenticate the user to your own backend (bundler proxy,
|
|
7352
7413
|
// etc.); verify server-side against backend.decane.app/.well-known/jwks.json.
|
|
7353
7414
|
getAccessToken: socialGetAccessToken,
|
|
7415
|
+
// Passkey management for THIS device
|
|
7416
|
+
hasPasskey,
|
|
7417
|
+
addPasskey,
|
|
7354
7418
|
// Recovery (2-of-3 Shamir share set)
|
|
7355
7419
|
rotateShares,
|
|
7356
7420
|
exportPortableBackup,
|