decane-connect-kit 2.17.0 → 2.18.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
@@ -3053,6 +3053,7 @@ function bufFromBytes(u) {
3053
3053
  }
3054
3054
  async function registerWebAuthnCredential(opts) {
3055
3055
  const baseOptions = await opts.api.getWebAuthnRegisterOptions(opts.jwt, opts.rpId, opts.rpName);
3056
+ if (opts.allowDuplicate) delete baseOptions.excludeCredentials;
3056
3057
  const optionsJSON = {
3057
3058
  ...baseOptions,
3058
3059
  extensions: {
@@ -3061,11 +3062,7 @@ async function registerWebAuthnCredential(opts) {
3061
3062
  }
3062
3063
  };
3063
3064
  const { credentialId, prfOutput, response } = await registerPasskey({ optionsJSON });
3064
- try {
3065
- await opts.api.postWebAuthnRegister(opts.jwt, response, window.location.origin);
3066
- } catch (e) {
3067
- console.warn("decane-connect-kit: WebAuthn server-side registration failed; PRF-based unlock still works, but rotateShares()/exportPortableBackup() will not until this is retried.", e);
3068
- }
3065
+ await opts.api.postWebAuthnRegister(opts.jwt, response, window.location.origin);
3069
3066
  return { credentialId, prfOutput };
3070
3067
  }
3071
3068
 
@@ -3855,7 +3852,7 @@ async function createDecaneConnect(config) {
3855
3852
  const pin = await config.promptPin();
3856
3853
  return deriveKeyFromPin(pin, stored.pinSalt);
3857
3854
  }
3858
- async function registerPasskeyForDevice(userId, jwt, deviceShare) {
3855
+ async function registerPasskeyForDevice(userId, jwt, deviceShare, opts = {}) {
3859
3856
  if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
3860
3857
  const prfSalt = generateRandomBytes(32);
3861
3858
  let credentialId;
@@ -3866,10 +3863,11 @@ async function createDecaneConnect(config) {
3866
3863
  jwt,
3867
3864
  rpId,
3868
3865
  rpName,
3869
- prfSalt
3866
+ prfSalt,
3867
+ ...opts.allowDuplicate ? { allowDuplicate: true } : {}
3870
3868
  }));
3871
3869
  } catch (e) {
3872
- if (e?.name !== "InvalidStateError") throw e;
3870
+ if (e?.name !== "InvalidStateError" || opts.allowDuplicate) throw e;
3873
3871
  return adoptExistingPasskey(userId, jwt, deviceShare);
3874
3872
  }
3875
3873
  let key = prfOutput;
@@ -3921,7 +3919,12 @@ async function createDecaneConnect(config) {
3921
3919
  }
3922
3920
  async function adoptExistingPasskey(userId, jwt, deviceShare) {
3923
3921
  const options = await api.getWebAuthnLoginOptions(userId, rpId);
3924
- if (!options) throw new WebAuthnNotSupportedError();
3922
+ if (!options) {
3923
+ console.warn(
3924
+ "decane-connect-kit: this authenticator holds a credential the server does not know; registering a fresh one."
3925
+ );
3926
+ return registerPasskeyForDevice(userId, jwt, deviceShare, { allowDuplicate: true });
3927
+ }
3925
3928
  const prfSalt = generateRandomBytes(32);
3926
3929
  const { response, prfOutput } = await authenticateWithPasskey({
3927
3930
  optionsJSON: options,
@@ -3966,19 +3969,26 @@ async function createDecaneConnect(config) {
3966
3969
  );
3967
3970
  return;
3968
3971
  }
3969
- const { wrappingKey, tokenKey, meta } = await passwordSetup();
3970
- const iv = generateRandomBytes(12);
3971
- const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
3972
- await storage.putShare({
3973
- userId,
3974
- wrappedDeviceShare,
3975
- iv,
3976
- unlockMethod: "password",
3977
- passwordSalt: meta.passwordSalt ? b64uToBuffer(meta.passwordSalt) : void 0,
3978
- passwordIterations: meta.passwordIterations,
3979
- version: 1
3980
- });
3981
- await attachUnlockToken(userId, jwt, tokenKey);
3972
+ try {
3973
+ const { wrappingKey, tokenKey, meta } = await passwordSetup();
3974
+ const iv = generateRandomBytes(12);
3975
+ const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
3976
+ await storage.putShare({
3977
+ userId,
3978
+ wrappedDeviceShare,
3979
+ iv,
3980
+ unlockMethod: "password",
3981
+ passwordSalt: meta.passwordSalt ? b64uToBuffer(meta.passwordSalt) : void 0,
3982
+ passwordIterations: meta.passwordIterations,
3983
+ version: 1
3984
+ });
3985
+ await attachUnlockToken(userId, jwt, tokenKey);
3986
+ } catch (e) {
3987
+ console.warn(
3988
+ "decane-connect-kit: no unlock password was set, so this device stored no share. The session works; the next visit will provision again.",
3989
+ e
3990
+ );
3991
+ }
3982
3992
  }
3983
3993
  async function firstTimeSetup(userId, jwt) {
3984
3994
  prewarmAttestation(teeUrl);