@learncard/sss-key-manager 0.1.14 → 0.1.16

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/README.md CHANGED
@@ -8,19 +8,19 @@ This package provides a secure, self-hosted alternative to Web3Auth Single Facto
8
8
 
9
9
  ## Features
10
10
 
11
- - **Key Splitting**: Split ed25519 private keys into 3 shares with 2-of-3 threshold
12
- - **Device Storage**: Encrypted local storage using AES-GCM with IndexedDB
13
- - **Server Storage**: Encrypted auth share stored on server with envelope encryption
14
- - **Recovery Methods**:
15
- - Password-based (Argon2id KDF)
16
- - Passkey/WebAuthn PRF (coming soon)
17
- - Backup file export/import
18
- - **Migration**: Seamless migration from Web3Auth SFA
11
+ - **Key Splitting**: Split ed25519 private keys into 3 shares with 2-of-3 threshold
12
+ - **Device Storage**: Encrypted local storage using AES-GCM with IndexedDB
13
+ - **Server Storage**: Encrypted auth share stored on server with envelope encryption
14
+ - **Recovery Methods**:
15
+ - Password-based (Argon2id KDF)
16
+ - Passkey/WebAuthn PRF (coming soon)
17
+ - Backup file export/import
18
+ - **Migration**: Seamless migration from Web3Auth SFA
19
19
 
20
20
  ## Installation
21
21
 
22
22
  ```bash
23
- pnpm add @learncard/sss-key-manager
23
+ bun add @learncard/sss-key-manager
24
24
  ```
25
25
 
26
26
  ## Usage
@@ -115,19 +115,19 @@ const privateKey = await keyManager.recover({
115
115
 
116
116
  ## Security Model
117
117
 
118
- - **Device Share**: Encrypted with non-extractable AES-GCM key stored in IndexedDB
119
- - **Auth Share**: Server-side envelope encryption (DEK + KMS-encrypted DEK)
120
- - **Recovery Share**: Password-based uses Argon2id KDF with secure parameters
121
- - **Threshold**: Any 2 of 3 shares can reconstruct the key
118
+ - **Device Share**: Encrypted with non-extractable AES-GCM key stored in IndexedDB
119
+ - **Auth Share**: Server-side envelope encryption (DEK + KMS-encrypted DEK)
120
+ - **Recovery Share**: Password-based uses Argon2id KDF with secure parameters
121
+ - **Threshold**: Any 2 of 3 shares can reconstruct the key
122
122
 
123
123
  ## Auth Provider Support
124
124
 
125
125
  The package is designed to work with any authentication provider:
126
126
 
127
- - Firebase Authentication (default for production)
128
- - SuperTokens (recommended for self-hosting/local dev)
129
- - Keycloak (enterprise SSO)
130
- - Any OIDC-compliant provider
127
+ - Firebase Authentication (default for production)
128
+ - SuperTokens (recommended for self-hosting/local dev)
129
+ - Keycloak (enterprise SSO)
130
+ - Any OIDC-compliant provider
131
131
 
132
132
  ## API Reference
133
133
 
@@ -7,11 +7,7 @@ var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
9
  var __commonJS = (cb, mod) => function __require() {
10
- try {
11
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
- } catch (e) {
13
- throw mod = 0, e;
14
- }
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
15
11
  };
16
12
  var __export = (target, all) => {
17
13
  for (var name in all)
@@ -17356,7 +17352,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17356
17352
  includePersonalData: external_exports.boolean().optional().default(false),
17357
17353
  format: external_exports.enum(["prompt", "structured"]).optional().default("prompt"),
17358
17354
  instructions: external_exports.string().optional(),
17359
- detailLevel: external_exports.enum(["compact", "expanded"]).optional().default("compact")
17355
+ detailLevel: external_exports.enum(["compact", "expanded"]).optional().default("compact"),
17356
+ waitForSync: external_exports.boolean().optional().default(false)
17360
17357
  });
17361
17358
  var SummaryCredentialKeywordValidator = external_exports.object({
17362
17359
  occupations: external_exports.array(external_exports.string()).nullable(),
@@ -19100,7 +19097,15 @@ function createSSSStrategy(config) {
19100
19097
  };
19101
19098
  },
19102
19099
  async storeAuthShare(token, providerType, authShare, primaryDid, didAuthVp) {
19103
- const { shareVersion } = await putAuthShare(serverUrl, token, providerType, authShare, primaryDid, didAuthVp, tenantId);
19100
+ const { shareVersion } = await putAuthShare(
19101
+ serverUrl,
19102
+ token,
19103
+ providerType,
19104
+ authShare,
19105
+ primaryDid,
19106
+ didAuthVp,
19107
+ tenantId
19108
+ );
19104
19109
  await storage.storeShareVersion(shareVersion, activeStorageId);
19105
19110
  lastShareVersion = shareVersion;
19106
19111
  },
@@ -19193,7 +19198,14 @@ function createSSSStrategy(config) {
19193
19198
  throw new Error("No auth share found on server");
19194
19199
  }
19195
19200
  const authShareStr = typeof serverData.authShare === "object" ? serverData.authShare.encryptedData : serverData.authShare;
19196
- const privateKey = await reconstructFromShares([recoveryShare, authShareStr]);
19201
+ let privateKey;
19202
+ try {
19203
+ privateKey = await reconstructFromShares([recoveryShare, authShareStr]);
19204
+ } catch {
19205
+ throw new Error(
19206
+ "Recovery produced an incorrect key. The recovery key may be outdated. Please try a different recovery method."
19207
+ );
19208
+ }
19197
19209
  const primaryDid = serverData.primaryDid || "";
19198
19210
  if (primaryDid && didFromPrivateKey) {
19199
19211
  const derivedDid = await didFromPrivateKey(privateKey);
@@ -19224,13 +19236,44 @@ function createSSSStrategy(config) {
19224
19236
  const { shares } = await splitAndVerify(privateKey);
19225
19237
  lastEmailShare = shares.emailShare;
19226
19238
  await storage.storeDeviceShare(shares.deviceShare, activeStorageId);
19227
- const serverData = await fetchAuthShareRaw(serverUrl, token, providerType, void 0, tenantId);
19239
+ const serverData = await fetchAuthShareRaw(
19240
+ serverUrl,
19241
+ token,
19242
+ providerType,
19243
+ void 0,
19244
+ tenantId
19245
+ );
19228
19246
  const primaryDid = serverData?.primaryDid || "";
19229
- const { shareVersion } = await putAuthShare(serverUrl, token, providerType, shares.authShare, primaryDid, vpJwt, tenantId);
19247
+ const { shareVersion } = await putAuthShare(
19248
+ serverUrl,
19249
+ token,
19250
+ providerType,
19251
+ shares.authShare,
19252
+ primaryDid,
19253
+ vpJwt,
19254
+ tenantId
19255
+ );
19230
19256
  await storage.storeShareVersion(shareVersion, activeStorageId);
19231
19257
  if (enableEmailBackupShare && input.method !== "email" && lastEmailShare) {
19232
- const resend = hasRecoveryEmail ? sendEmailShareToRecoveryEmail(serverUrl, token, providerType, lastEmailShare, shareVersion, tenantId) : authUser?.email ? sendEmailBackupShare(serverUrl, token, providerType, lastEmailShare, authUser.email, shareVersion, tenantId) : Promise.resolve();
19233
- resend.catch((e) => console.warn("Email backup share re-send failed (non-fatal):", e));
19258
+ const resend = hasRecoveryEmail ? sendEmailShareToRecoveryEmail(
19259
+ serverUrl,
19260
+ token,
19261
+ providerType,
19262
+ lastEmailShare,
19263
+ shareVersion,
19264
+ tenantId
19265
+ ) : authUser?.email ? sendEmailBackupShare(
19266
+ serverUrl,
19267
+ token,
19268
+ providerType,
19269
+ lastEmailShare,
19270
+ authUser.email,
19271
+ shareVersion,
19272
+ tenantId
19273
+ ) : Promise.resolve();
19274
+ resend.catch(
19275
+ (e) => console.warn("Email backup share re-send failed (non-fatal):", e)
19276
+ );
19234
19277
  lastEmailShare = void 0;
19235
19278
  }
19236
19279
  switch (input.method) {
@@ -19240,27 +19283,44 @@ function createSSSStrategy(config) {
19240
19283
  shares.recoveryShare,
19241
19284
  credential.credentialId
19242
19285
  );
19243
- await postRecoveryMethod(serverUrl, token, providerType, {
19244
- type: "passkey",
19245
- encryptedShare: {
19246
- encryptedData: encryptedShare.encryptedData,
19247
- iv: encryptedShare.iv
19286
+ await postRecoveryMethod(
19287
+ serverUrl,
19288
+ token,
19289
+ providerType,
19290
+ {
19291
+ type: "passkey",
19292
+ encryptedShare: {
19293
+ encryptedData: encryptedShare.encryptedData,
19294
+ iv: encryptedShare.iv
19295
+ },
19296
+ credentialId: credential.credentialId,
19297
+ shareVersion
19248
19298
  },
19249
- credentialId: credential.credentialId,
19250
- shareVersion
19251
- }, vpJwt, tenantId);
19299
+ vpJwt,
19300
+ tenantId
19301
+ );
19252
19302
  return { method: "passkey", credentialId: credential.credentialId };
19253
19303
  }
19254
19304
  case "phrase": {
19255
19305
  const phrase = await shareToRecoveryPhrase(shares.recoveryShare);
19256
- await postRecoveryMethod(serverUrl, token, providerType, {
19257
- type: "phrase",
19258
- shareVersion
19259
- }, vpJwt, tenantId);
19306
+ await postRecoveryMethod(
19307
+ serverUrl,
19308
+ token,
19309
+ providerType,
19310
+ {
19311
+ type: "phrase",
19312
+ shareVersion
19313
+ },
19314
+ vpJwt,
19315
+ tenantId
19316
+ );
19260
19317
  return { method: "phrase", phrase };
19261
19318
  }
19262
19319
  case "backup": {
19263
- const encrypted = await encryptWithPassword(shares.recoveryShare, input.password);
19320
+ const encrypted = await encryptWithPassword(
19321
+ shares.recoveryShare,
19322
+ input.password
19323
+ );
19264
19324
  const backupFile = {
19265
19325
  version: 1,
19266
19326
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -19273,10 +19333,17 @@ function createSSSStrategy(config) {
19273
19333
  kdfParams: encrypted.kdfParams
19274
19334
  }
19275
19335
  };
19276
- await postRecoveryMethod(serverUrl, token, providerType, {
19277
- type: "backup",
19278
- shareVersion
19279
- }, vpJwt, tenantId);
19336
+ await postRecoveryMethod(
19337
+ serverUrl,
19338
+ token,
19339
+ providerType,
19340
+ {
19341
+ type: "backup",
19342
+ shareVersion
19343
+ },
19344
+ vpJwt,
19345
+ tenantId
19346
+ );
19280
19347
  return { method: "backup", backupFile };
19281
19348
  }
19282
19349
  case "email": {
@@ -19288,10 +19355,17 @@ function createSSSStrategy(config) {
19288
19355
  shareVersion,
19289
19356
  tenantId
19290
19357
  );
19291
- await postRecoveryMethod(serverUrl, token, providerType, {
19292
- type: "email",
19293
- shareVersion
19294
- }, vpJwt, tenantId);
19358
+ await postRecoveryMethod(
19359
+ serverUrl,
19360
+ token,
19361
+ providerType,
19362
+ {
19363
+ type: "email",
19364
+ shareVersion
19365
+ },
19366
+ vpJwt,
19367
+ tenantId
19368
+ );
19295
19369
  hasRecoveryEmail = true;
19296
19370
  return { method: "email" };
19297
19371
  }
@@ -19299,7 +19373,13 @@ function createSSSStrategy(config) {
19299
19373
  },
19300
19374
  async getAvailableRecoveryMethods(token, providerType) {
19301
19375
  try {
19302
- const serverData = await fetchAuthShareRaw(serverUrl, token, providerType, void 0, tenantId);
19376
+ const serverData = await fetchAuthShareRaw(
19377
+ serverUrl,
19378
+ token,
19379
+ providerType,
19380
+ void 0,
19381
+ tenantId
19382
+ );
19303
19383
  const methods = serverData?.recoveryMethods || [];
19304
19384
  if (enableEmailBackupShare && !methods.some((m) => m.type === "email")) {
19305
19385
  methods.push({ type: "email", createdAt: /* @__PURE__ */ new Date() });
@@ -19335,7 +19415,9 @@ function createSSSStrategy(config) {
19335
19415
  async sendEmailBackupShare(token, providerType, _privateKey, email) {
19336
19416
  if (!enableEmailBackupShare) return;
19337
19417
  if (!lastEmailShare) {
19338
- console.warn("Cannot send email backup share: no cached email share from splitKey()");
19418
+ console.warn(
19419
+ "Cannot send email backup share: no cached email share from splitKey()"
19420
+ );
19339
19421
  return;
19340
19422
  }
19341
19423
  if (hasRecoveryEmail) {
@@ -19352,7 +19434,15 @@ function createSSSStrategy(config) {
19352
19434
  console.warn("Cannot send email backup share: no email address");
19353
19435
  return;
19354
19436
  }
19355
- await sendEmailBackupShare(serverUrl, token, providerType, lastEmailShare, email, lastShareVersion, tenantId);
19437
+ await sendEmailBackupShare(
19438
+ serverUrl,
19439
+ token,
19440
+ providerType,
19441
+ lastEmailShare,
19442
+ email,
19443
+ lastShareVersion,
19444
+ tenantId
19445
+ );
19356
19446
  }
19357
19447
  lastEmailShare = void 0;
19358
19448
  lastShareVersion = void 0;