@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 +17 -17
- package/dist/sss-key-manager.cjs.development.js +126 -36
- package/dist/sss-key-manager.cjs.development.js.map +2 -2
- package/dist/sss-key-manager.cjs.production.min.js +6 -6
- package/dist/sss-key-manager.cjs.production.min.js.map +3 -3
- package/dist/sss-key-manager.esm.js +126 -36
- package/dist/sss-key-manager.esm.js.map +2 -2
- package/package.json +62 -52
- package/src/api-client.ts +257 -0
- package/src/atomic-operations.test.ts +327 -0
- package/src/atomic-operations.ts +275 -0
- package/src/auth-coordinator.test.ts +13 -0
- package/src/auth-coordinator.ts +12 -0
- package/src/critical-paths.test.ts +380 -0
- package/src/crypto.test.ts +214 -0
- package/src/crypto.ts +203 -0
- package/src/index.ts +146 -0
- package/src/key-manager.test.ts +330 -0
- package/src/key-manager.ts +323 -0
- package/src/passkey.test.ts +59 -0
- package/src/passkey.ts +222 -0
- package/src/qr-crypto.test.ts +122 -0
- package/src/qr-crypto.ts +206 -0
- package/src/qr-login-notify.test.ts +95 -0
- package/src/qr-login.test.ts +548 -0
- package/src/qr-login.ts +339 -0
- package/src/recovery-phrase.test.ts +287 -0
- package/src/recovery-phrase.ts +131 -0
- package/src/sss-strategy.test.ts +1956 -0
- package/src/sss-strategy.ts +1119 -0
- package/src/sss.test.ts +242 -0
- package/src/sss.ts +49 -0
- package/src/storage.test.ts +530 -0
- package/src/storage.ts +467 -0
- package/src/types.ts +200 -0
- package/LICENSE +0 -21
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
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
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
|
-
|
|
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
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
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
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
19233
|
-
|
|
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(
|
|
19244
|
-
|
|
19245
|
-
|
|
19246
|
-
|
|
19247
|
-
|
|
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
|
-
|
|
19250
|
-
|
|
19251
|
-
|
|
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(
|
|
19257
|
-
|
|
19258
|
-
|
|
19259
|
-
|
|
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(
|
|
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(
|
|
19277
|
-
|
|
19278
|
-
|
|
19279
|
-
|
|
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(
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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;
|