decane-connect-kit 2.15.0 → 2.17.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.js CHANGED
@@ -3776,14 +3776,35 @@ async function createDecaneConnect(config) {
3776
3776
  async function registerPasskeyForDevice(userId, jwt, deviceShare) {
3777
3777
  if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
3778
3778
  const prfSalt = generateRandomBytes(32);
3779
- const { credentialId, prfOutput } = await registerWebAuthnCredential({
3780
- api,
3781
- jwt,
3782
- rpId,
3783
- rpName,
3784
- prfSalt
3785
- });
3786
- if (!prfOutput) throw new WebAuthnNotSupportedError();
3779
+ let credentialId;
3780
+ let prfOutput;
3781
+ try {
3782
+ ({ credentialId, prfOutput } = await registerWebAuthnCredential({
3783
+ api,
3784
+ jwt,
3785
+ rpId,
3786
+ rpName,
3787
+ prfSalt
3788
+ }));
3789
+ } catch (e) {
3790
+ if (e?.name !== "InvalidStateError") throw e;
3791
+ return adoptExistingPasskey(userId, jwt, deviceShare);
3792
+ }
3793
+ let key = prfOutput;
3794
+ if (!key) {
3795
+ try {
3796
+ key = await getPrfOutput({ rpId, credentialId, prfSalt });
3797
+ } catch (e) {
3798
+ console.warn(
3799
+ "decane-connect-kit: the new credential did not return a PRF output; it cannot wrap the share.",
3800
+ e
3801
+ );
3802
+ }
3803
+ }
3804
+ if (!key) throw new WebAuthnNotSupportedError();
3805
+ await storeShareUnderPasskey(userId, jwt, deviceShare, credentialId, key, prfSalt);
3806
+ }
3807
+ async function storeShareUnderPasskey(userId, jwt, deviceShare, credentialId, prfOutput, prfSalt) {
3787
3808
  const wrappingKey = await importAesKey(prfOutput);
3788
3809
  const iv = generateRandomBytes(12);
3789
3810
  const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
@@ -3816,6 +3837,24 @@ async function createDecaneConnect(config) {
3816
3837
  console.warn("decane-connect-kit: wrapped share backup upload failed; user can retry from settings.", e);
3817
3838
  }
3818
3839
  }
3840
+ async function adoptExistingPasskey(userId, jwt, deviceShare) {
3841
+ const options = await api.getWebAuthnLoginOptions(userId, rpId);
3842
+ if (!options) throw new WebAuthnNotSupportedError();
3843
+ const prfSalt = generateRandomBytes(32);
3844
+ const { response, prfOutput } = await authenticateWithPasskey({
3845
+ optionsJSON: options,
3846
+ prfSalt
3847
+ });
3848
+ if (!prfOutput) throw new WebAuthnNotSupportedError();
3849
+ await storeShareUnderPasskey(
3850
+ userId,
3851
+ jwt,
3852
+ deviceShare,
3853
+ b64uToBuffer(response.id),
3854
+ prfOutput,
3855
+ prfSalt
3856
+ );
3857
+ }
3819
3858
  async function registerPasskeyAndBackup(userId, jwt, deviceShare) {
3820
3859
  if (!isPasskeySupported()) return;
3821
3860
  try {
@@ -4495,8 +4534,19 @@ async function createDecaneConnect(config) {
4495
4534
  if (!auth) throw new ReconnectRequiredError();
4496
4535
  if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
4497
4536
  const stored = await storage.getShare(auth.userId);
4498
- if (!stored) throw new NewDeviceError();
4499
- if (stored.unlockMethod === "passkey") return;
4537
+ if (stored?.unlockMethod === "passkey") return;
4538
+ if (!stored) {
4539
+ if (!serverAssistedRecovery) throw new NewDeviceError();
4540
+ const tee = new TeeClient(teeUrl);
4541
+ const provisioned = await tee.provisionDeviceShare(auth.jwt);
4542
+ if (!provisioned) throw new NewDeviceError();
4543
+ try {
4544
+ await registerPasskeyForDevice(auth.userId, auth.jwt, provisioned);
4545
+ } finally {
4546
+ zeroize(provisioned);
4547
+ }
4548
+ return;
4549
+ }
4500
4550
  let deviceShare;
4501
4551
  try {
4502
4552
  deviceShare = await aesGcmDecrypt(