decane-connect-kit 2.13.0 → 2.14.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
@@ -3787,13 +3787,21 @@ async function createDecaneConnect(config) {
3787
3787
  const wrappingKey = await importAesKey(prfOutput);
3788
3788
  const iv = generateRandomBytes(12);
3789
3789
  const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
3790
+ const existing = await storage.getShare(userId);
3790
3791
  await storage.putShare({
3792
+ ...existing,
3791
3793
  userId,
3792
3794
  wrappedDeviceShare,
3793
3795
  iv,
3794
3796
  unlockMethod: "passkey",
3795
3797
  credentialId,
3796
3798
  prfSalt,
3799
+ pinSalt: void 0,
3800
+ pinIterations: void 0,
3801
+ passwordSalt: void 0,
3802
+ passwordIterations: void 0,
3803
+ wrappedUnlockToken: void 0,
3804
+ unlockTokenIv: void 0,
3797
3805
  version: 1
3798
3806
  });
3799
3807
  try {
@@ -3819,6 +3827,38 @@ async function createDecaneConnect(config) {
3819
3827
  );
3820
3828
  }
3821
3829
  }
3830
+ async function persistProvisionedShare(userId, jwt, deviceShare) {
3831
+ if (isPasskeySupported()) {
3832
+ try {
3833
+ await registerPasskeyForDevice(userId, jwt, deviceShare);
3834
+ return;
3835
+ } catch (e) {
3836
+ console.warn(
3837
+ "decane-connect-kit: passkey provisioning failed; falling back to an unlock password so the share is still stored.",
3838
+ e
3839
+ );
3840
+ }
3841
+ }
3842
+ if (!config.promptUnlockPassword) {
3843
+ console.error(
3844
+ "decane-connect-kit: this device could not store its share \u2014 no passkey and no promptUnlockPassword. The session works, but every visit will re-provision and no unlock will be offered. Configure promptUnlockPassword."
3845
+ );
3846
+ return;
3847
+ }
3848
+ const { wrappingKey, tokenKey, meta } = await passwordSetup();
3849
+ const iv = generateRandomBytes(12);
3850
+ const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
3851
+ await storage.putShare({
3852
+ userId,
3853
+ wrappedDeviceShare,
3854
+ iv,
3855
+ unlockMethod: "password",
3856
+ passwordSalt: meta.passwordSalt ? b64uToBuffer(meta.passwordSalt) : void 0,
3857
+ passwordIterations: meta.passwordIterations,
3858
+ version: 1
3859
+ });
3860
+ await attachUnlockToken(userId, jwt, tokenKey);
3861
+ }
3822
3862
  async function firstTimeSetup(userId, jwt) {
3823
3863
  prewarmAttestation(teeUrl);
3824
3864
  let unlockMethod;
@@ -3983,8 +4023,7 @@ async function createDecaneConnect(config) {
3983
4023
  const tee = new TeeClient(teeUrl);
3984
4024
  const provisioned = await tee.provisionDeviceShare(jwt);
3985
4025
  if (provisioned) {
3986
- await registerPasskeyAndBackup(userId, jwt, provisioned).catch(() => {
3987
- });
4026
+ await persistProvisionedShare(userId, jwt, provisioned);
3988
4027
  let addrs;
3989
4028
  try {
3990
4029
  addrs = await session.start(provisioned, jwt, recordedAddresses);
@@ -4114,6 +4153,12 @@ async function createDecaneConnect(config) {
4114
4153
  const candidates = (await storage.listShares()).map(usablePasskeyShare).filter(Boolean);
4115
4154
  return candidates.length === 1 ? candidates[0] : null;
4116
4155
  }
4156
+ async function canOfferPasskey() {
4157
+ if (!isPasskeySupported()) return false;
4158
+ if (await passkeyDeviceCandidate()) return true;
4159
+ if (!persisted?.userId) return false;
4160
+ return await storage.getShare(persisted.userId) == null;
4161
+ }
4117
4162
  async function rememberProfile(userId, profile) {
4118
4163
  const incoming = Object.fromEntries(Object.entries(profile).filter(([, v]) => v));
4119
4164
  if (!Object.keys(incoming).length) return;
@@ -4129,6 +4174,28 @@ async function createDecaneConnect(config) {
4129
4174
  if (!stored.credentialId || !stored.prfSalt) return null;
4130
4175
  return stored;
4131
4176
  }
4177
+ async function passkeyLoginWithoutShare(userId) {
4178
+ events.emit("wallet-unlocking");
4179
+ const options = await api.getWebAuthnLoginOptions(userId, rpId);
4180
+ if (!options) throw new NewDeviceError();
4181
+ const { response } = await authenticateWithPasskey({
4182
+ optionsJSON: options,
4183
+ prfSalt: generateRandomBytes(32)
4184
+ });
4185
+ const result = await api.postWebAuthnLogin(userId, response, window.location.origin);
4186
+ const addresses = await returningUserUnlock(userId, result.jwt);
4187
+ auth = { jwt: result.jwt, userId, addresses };
4188
+ persisted = { userId, addresses };
4189
+ savePersisted(PERSIST_KEY, persisted);
4190
+ events.emit("connected");
4191
+ persistLiveSession();
4192
+ const stored = await storage.getShare(userId);
4193
+ return {
4194
+ addresses,
4195
+ isNewUser: false,
4196
+ ...stored?.profile ? { profile: stored.profile } : {}
4197
+ };
4198
+ }
4132
4199
  async function passkeyLogin(stored) {
4133
4200
  const userId = stored.userId;
4134
4201
  events.emit("wallet-unlocking");
@@ -4288,12 +4355,15 @@ async function createDecaneConnect(config) {
4288
4355
  return handleAuthResult(result);
4289
4356
  },
4290
4357
  async canSignInWithPasskey() {
4291
- return await passkeyDeviceCandidate() !== null;
4358
+ return canOfferPasskey();
4292
4359
  },
4293
4360
  async signInWithPasskey() {
4294
4361
  const candidate = await passkeyDeviceCandidate();
4295
- if (!candidate) throw new NewDeviceError();
4296
- return passkeyLogin(candidate);
4362
+ if (candidate) return passkeyLogin(candidate);
4363
+ if (isPasskeySupported() && persisted?.userId && await storage.getShare(persisted.userId) == null) {
4364
+ return passkeyLoginWithoutShare(persisted.userId);
4365
+ }
4366
+ throw new NewDeviceError();
4297
4367
  },
4298
4368
  /**
4299
4369
  * Turn on "unlock with your password" for this device.