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.cjs CHANGED
@@ -3858,14 +3858,35 @@ async function createDecaneConnect(config) {
3858
3858
  async function registerPasskeyForDevice(userId, jwt, deviceShare) {
3859
3859
  if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
3860
3860
  const prfSalt = generateRandomBytes(32);
3861
- const { credentialId, prfOutput } = await registerWebAuthnCredential({
3862
- api,
3863
- jwt,
3864
- rpId,
3865
- rpName,
3866
- prfSalt
3867
- });
3868
- if (!prfOutput) throw new WebAuthnNotSupportedError();
3861
+ let credentialId;
3862
+ let prfOutput;
3863
+ try {
3864
+ ({ credentialId, prfOutput } = await registerWebAuthnCredential({
3865
+ api,
3866
+ jwt,
3867
+ rpId,
3868
+ rpName,
3869
+ prfSalt
3870
+ }));
3871
+ } catch (e) {
3872
+ if (e?.name !== "InvalidStateError") throw e;
3873
+ return adoptExistingPasskey(userId, jwt, deviceShare);
3874
+ }
3875
+ let key = prfOutput;
3876
+ if (!key) {
3877
+ try {
3878
+ key = await getPrfOutput({ rpId, credentialId, prfSalt });
3879
+ } catch (e) {
3880
+ console.warn(
3881
+ "decane-connect-kit: the new credential did not return a PRF output; it cannot wrap the share.",
3882
+ e
3883
+ );
3884
+ }
3885
+ }
3886
+ if (!key) throw new WebAuthnNotSupportedError();
3887
+ await storeShareUnderPasskey(userId, jwt, deviceShare, credentialId, key, prfSalt);
3888
+ }
3889
+ async function storeShareUnderPasskey(userId, jwt, deviceShare, credentialId, prfOutput, prfSalt) {
3869
3890
  const wrappingKey = await importAesKey(prfOutput);
3870
3891
  const iv = generateRandomBytes(12);
3871
3892
  const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
@@ -3898,6 +3919,24 @@ async function createDecaneConnect(config) {
3898
3919
  console.warn("decane-connect-kit: wrapped share backup upload failed; user can retry from settings.", e);
3899
3920
  }
3900
3921
  }
3922
+ async function adoptExistingPasskey(userId, jwt, deviceShare) {
3923
+ const options = await api.getWebAuthnLoginOptions(userId, rpId);
3924
+ if (!options) throw new WebAuthnNotSupportedError();
3925
+ const prfSalt = generateRandomBytes(32);
3926
+ const { response, prfOutput } = await authenticateWithPasskey({
3927
+ optionsJSON: options,
3928
+ prfSalt
3929
+ });
3930
+ if (!prfOutput) throw new WebAuthnNotSupportedError();
3931
+ await storeShareUnderPasskey(
3932
+ userId,
3933
+ jwt,
3934
+ deviceShare,
3935
+ b64uToBuffer(response.id),
3936
+ prfOutput,
3937
+ prfSalt
3938
+ );
3939
+ }
3901
3940
  async function registerPasskeyAndBackup(userId, jwt, deviceShare) {
3902
3941
  if (!isPasskeySupported()) return;
3903
3942
  try {
@@ -4577,8 +4616,19 @@ async function createDecaneConnect(config) {
4577
4616
  if (!auth) throw new ReconnectRequiredError();
4578
4617
  if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
4579
4618
  const stored = await storage.getShare(auth.userId);
4580
- if (!stored) throw new NewDeviceError();
4581
- if (stored.unlockMethod === "passkey") return;
4619
+ if (stored?.unlockMethod === "passkey") return;
4620
+ if (!stored) {
4621
+ if (!serverAssistedRecovery) throw new NewDeviceError();
4622
+ const tee = new TeeClient(teeUrl);
4623
+ const provisioned = await tee.provisionDeviceShare(auth.jwt);
4624
+ if (!provisioned) throw new NewDeviceError();
4625
+ try {
4626
+ await registerPasskeyForDevice(auth.userId, auth.jwt, provisioned);
4627
+ } finally {
4628
+ zeroize(provisioned);
4629
+ }
4630
+ return;
4631
+ }
4582
4632
  let deviceShare;
4583
4633
  try {
4584
4634
  deviceShare = await aesGcmDecrypt(