@learncard/sss-key-manager 0.1.13 → 0.1.15

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/package.json CHANGED
@@ -1,54 +1,64 @@
1
1
  {
2
- "name": "@learncard/sss-key-manager",
3
- "version": "0.1.13",
4
- "description": "Shamir Secret Sharing key manager for LearnCard - replaces Web3Auth SFA",
5
- "main": "./dist/index.js",
6
- "module": "./dist/sss-key-manager.esm.js",
7
- "types": "./dist/sss-key-manager.d.ts",
8
- "files": [
9
- "dist"
10
- ],
11
- "author": "Learning Economy Foundation (www.learningeconomy.io)",
12
- "license": "MIT",
13
- "homepage": "https://github.com/learningeconomy/LearnCard/tree/main/packages/sss-key-manager/README.md",
14
- "repository": {
15
- "type": "git",
16
- "url": "https://github.com/learningeconomy/LearnCard"
17
- },
18
- "bugs": {
19
- "url": "https://github.com/learningeconomy/LearnCard/issues"
20
- },
21
- "devDependencies": {
22
- "@esbuild-plugins/node-resolve": "^0.1.4",
23
- "@types/node": "^18.7.19",
24
- "dts-bundle-generator": "^6.10.0",
25
- "esbuild": "^0.27.1",
26
- "fake-indexeddb": "^6.2.5",
27
- "jsdom": "^24.0.0",
28
- "shx": "^0.3.4",
29
- "typescript": "^5.0.0",
30
- "vitest": "^1.0.0"
31
- },
32
- "dependencies": {
33
- "@noble/ed25519": "^2.0.0",
34
- "@scure/bip39": "^1.2.1",
35
- "hash-wasm": "^4.11.0",
36
- "shamir-secret-sharing": "^0.0.4",
37
- "@learncard/types": "5.17.3"
38
- },
39
- "peerDependencies": {
40
- "@simplewebauthn/browser": ">=9.0.0"
41
- },
42
- "peerDependenciesMeta": {
43
- "@simplewebauthn/browser": {
44
- "optional": true
2
+ "name": "@learncard/sss-key-manager",
3
+ "version": "0.1.15",
4
+ "description": "Shamir Secret Sharing key manager for LearnCard - replaces Web3Auth SFA",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/sss-key-manager.esm.js",
7
+ "types": "./dist/sss-key-manager.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "src"
11
+ ],
12
+ "scripts": {
13
+ "build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntrypoint.js ./dist/index.js && dts-bundle-generator src/index.ts -o dist/sss-key-manager.d.ts --no-check",
14
+ "dev": "node ./scripts/build.mjs --watch",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest",
17
+ "typecheck": "tsc --noEmit"
18
+ },
19
+ "author": "Learning Economy Foundation (www.learningeconomy.io)",
20
+ "license": "MIT",
21
+ "homepage": "https://github.com/learningeconomy/LearnCard/tree/main/packages/sss-key-manager/README.md",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/learningeconomy/LearnCard"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/learningeconomy/LearnCard/issues"
28
+ },
29
+ "devDependencies": {
30
+ "@esbuild-plugins/node-resolve": "^0.1.4",
31
+ "@types/node": "^18.7.19",
32
+ "dts-bundle-generator": "^6.10.0",
33
+ "esbuild": "^0.27.1",
34
+ "fake-indexeddb": "^6.2.5",
35
+ "jsdom": "^24.0.0",
36
+ "shx": "^0.3.4",
37
+ "typescript": "^5.0.0",
38
+ "vitest": "^1.0.0"
39
+ },
40
+ "dependencies": {
41
+ "@learncard/types": "workspace:*",
42
+ "@noble/ed25519": "^2.0.0",
43
+ "@scure/bip39": "^1.2.1",
44
+ "hash-wasm": "^4.11.0",
45
+ "shamir-secret-sharing": "^0.0.4"
46
+ },
47
+ "peerDependencies": {
48
+ "@simplewebauthn/browser": ">=9.0.0"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@simplewebauthn/browser": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "exports": {
56
+ ".": {
57
+ "development": "./src/index.ts",
58
+ "types": "./dist/sss-key-manager.d.ts",
59
+ "import": "./dist/sss-key-manager.esm.js",
60
+ "require": "./dist/index.js",
61
+ "default": "./dist/sss-key-manager.esm.js"
62
+ }
45
63
  }
46
- },
47
- "scripts": {
48
- "build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntrypoint.js ./dist/index.js && dts-bundle-generator src/index.ts -o dist/sss-key-manager.d.ts --no-check",
49
- "dev": "node ./scripts/build.mjs --watch",
50
- "test": "vitest run",
51
- "test:watch": "vitest",
52
- "typecheck": "tsc --noEmit"
53
- }
54
- }
64
+ }
@@ -0,0 +1,257 @@
1
+ /**
2
+ * API client for SSS Key Manager server routes
3
+ */
4
+
5
+ import type {
6
+ AuthProvider,
7
+ AuthProviderType,
8
+ ContactMethod,
9
+ ServerEncryptedShare,
10
+ RecoveryMethodInfo,
11
+ EncryptedShare,
12
+ SecurityLevel,
13
+ } from './types';
14
+
15
+ export interface GetAuthShareResponse {
16
+ authShare: ServerEncryptedShare | null;
17
+ primaryDid: string | null;
18
+ securityLevel: SecurityLevel;
19
+ recoveryMethods: RecoveryMethodInfo[];
20
+ keyProvider: 'web3auth' | 'sss';
21
+ maskedRecoveryEmail?: string | null;
22
+ }
23
+
24
+ export interface StoreAuthShareInput {
25
+ authShare: ServerEncryptedShare;
26
+ primaryDid: string;
27
+ securityLevel?: SecurityLevel;
28
+ }
29
+
30
+ export interface StoreRecoveryShareInput {
31
+ type: 'passkey' | 'backup' | 'phrase' | 'email';
32
+ encryptedShare?: EncryptedShare;
33
+ credentialId?: string;
34
+ shareVersion?: number;
35
+ }
36
+
37
+ export interface ApiClientConfig {
38
+ serverUrl: string;
39
+ authProvider: AuthProvider;
40
+ }
41
+
42
+ export class SSSApiClient {
43
+ private serverUrl: string;
44
+ private authProvider: AuthProvider;
45
+
46
+ constructor(config: ApiClientConfig) {
47
+ this.serverUrl = config.serverUrl.replace(/\/$/, '');
48
+ this.authProvider = config.authProvider;
49
+ }
50
+
51
+ private async getAuthHeaders(): Promise<Record<string, string>> {
52
+ const token = await this.authProvider.getIdToken();
53
+ return {
54
+ 'Content-Type': 'application/json',
55
+ Authorization: `Bearer ${token}`,
56
+ };
57
+ }
58
+
59
+ private async getContactMethodFromUser(): Promise<ContactMethod | null> {
60
+ const user = await this.authProvider.getCurrentUser();
61
+ if (!user) return null;
62
+
63
+ if (user.email) {
64
+ return { type: 'email' as const, value: user.email.toLowerCase() };
65
+ }
66
+ if (user.phone) {
67
+ return { type: 'phone' as const, value: user.phone };
68
+ }
69
+ return null;
70
+ }
71
+
72
+ async getAuthShare(): Promise<GetAuthShareResponse | null> {
73
+ const headers = await this.getAuthHeaders();
74
+ const providerType = this.authProvider.getProviderType();
75
+
76
+ const response = await fetch(`${this.serverUrl}/keys/auth-share`, {
77
+ method: 'POST',
78
+ headers,
79
+ body: JSON.stringify({ providerType }),
80
+ });
81
+
82
+ if (!response.ok) {
83
+ if (response.status === 404) {
84
+ return null;
85
+ }
86
+ throw new Error(`Failed to get auth share: ${response.statusText}`);
87
+ }
88
+
89
+ return response.json();
90
+ }
91
+
92
+ async storeAuthShare(input: StoreAuthShareInput): Promise<void> {
93
+ const headers = await this.getAuthHeaders();
94
+ const providerType = this.authProvider.getProviderType();
95
+
96
+ const response = await fetch(`${this.serverUrl}/keys/auth-share`, {
97
+ method: 'PUT',
98
+ headers,
99
+ body: JSON.stringify({
100
+ ...input,
101
+ providerType,
102
+ }),
103
+ });
104
+
105
+ if (!response.ok) {
106
+ throw new Error(`Failed to store auth share: ${response.statusText}`);
107
+ }
108
+ }
109
+
110
+ async addRecoveryMethod(input: StoreRecoveryShareInput): Promise<void> {
111
+ const headers = await this.getAuthHeaders();
112
+ const providerType = this.authProvider.getProviderType();
113
+
114
+ const response = await fetch(`${this.serverUrl}/keys/recovery`, {
115
+ method: 'POST',
116
+ headers,
117
+ body: JSON.stringify({
118
+ ...input,
119
+ providerType,
120
+ }),
121
+ });
122
+
123
+ if (!response.ok) {
124
+ throw new Error(`Failed to add recovery method: ${response.statusText}`);
125
+ }
126
+ }
127
+
128
+ async getRecoveryShare(
129
+ type: 'passkey' | 'backup' | 'phrase' | 'email',
130
+ credentialId?: string
131
+ ): Promise<{ encryptedShare?: EncryptedShare; shareVersion?: number } | null> {
132
+ const token = await this.authProvider.getIdToken();
133
+ const providerType = this.authProvider.getProviderType();
134
+
135
+ const params = new URLSearchParams({
136
+ type,
137
+ providerType,
138
+ authToken: token,
139
+ });
140
+
141
+ if (credentialId) {
142
+ params.append('credentialId', credentialId);
143
+ }
144
+
145
+ const response = await fetch(`${this.serverUrl}/keys/recovery?${params}`, {
146
+ method: 'GET',
147
+ headers: {
148
+ 'Content-Type': 'application/json',
149
+ },
150
+ });
151
+
152
+ if (!response.ok) {
153
+ if (response.status === 404) {
154
+ return null;
155
+ }
156
+ throw new Error(`Failed to get recovery share: ${response.statusText}`);
157
+ }
158
+
159
+ return response.json();
160
+ }
161
+
162
+ async markMigrated(): Promise<void> {
163
+ const headers = await this.getAuthHeaders();
164
+ const providerType = this.authProvider.getProviderType();
165
+
166
+ const response = await fetch(`${this.serverUrl}/keys/migrate`, {
167
+ method: 'POST',
168
+ headers,
169
+ body: JSON.stringify({ providerType }),
170
+ });
171
+
172
+ if (!response.ok) {
173
+ throw new Error(`Failed to mark as migrated: ${response.statusText}`);
174
+ }
175
+ }
176
+
177
+ async sendEmailBackupShare(emailShare: string, overrideEmail?: string): Promise<void> {
178
+ const headers = await this.getAuthHeaders();
179
+ const providerType = this.authProvider.getProviderType();
180
+
181
+ let email = overrideEmail;
182
+
183
+ if (!email) {
184
+ const user = await this.authProvider.getCurrentUser();
185
+ email = user?.email;
186
+ }
187
+
188
+ if (!email) {
189
+ console.warn('Cannot send email backup share: no email address available');
190
+ return;
191
+ }
192
+
193
+ const response = await fetch(`${this.serverUrl}/keys/email-backup`, {
194
+ method: 'POST',
195
+ headers,
196
+ body: JSON.stringify({
197
+ providerType,
198
+ emailShare,
199
+ email,
200
+ }),
201
+ });
202
+
203
+ if (!response.ok) {
204
+ // Non-fatal: log but don't throw — the user can still use the app
205
+ console.warn(`Failed to send email backup share: ${response.statusText}`);
206
+ }
207
+ }
208
+
209
+ async addRecoveryEmail(email: string): Promise<void> {
210
+ const token = await this.authProvider.getIdToken();
211
+ const providerType = this.authProvider.getProviderType();
212
+
213
+ const response = await fetch(`${this.serverUrl}/keys/recovery-email/add`, {
214
+ method: 'POST',
215
+ headers: { 'Content-Type': 'application/json' },
216
+ body: JSON.stringify({ authToken: token, providerType, email }),
217
+ });
218
+
219
+ if (!response.ok) {
220
+ const data = await response.json().catch(() => ({}));
221
+ throw new Error(data?.message || `Failed to add recovery email: ${response.statusText}`);
222
+ }
223
+ }
224
+
225
+ async verifyRecoveryEmail(code: string): Promise<{ maskedEmail: string }> {
226
+ const token = await this.authProvider.getIdToken();
227
+ const providerType = this.authProvider.getProviderType();
228
+
229
+ const response = await fetch(`${this.serverUrl}/keys/recovery-email/verify`, {
230
+ method: 'POST',
231
+ headers: { 'Content-Type': 'application/json' },
232
+ body: JSON.stringify({ authToken: token, providerType, code }),
233
+ });
234
+
235
+ if (!response.ok) {
236
+ const data = await response.json().catch(() => ({}));
237
+ throw new Error(data?.message || `Failed to verify recovery email: ${response.statusText}`);
238
+ }
239
+
240
+ return response.json();
241
+ }
242
+
243
+ async deleteUserKey(): Promise<void> {
244
+ const headers = await this.getAuthHeaders();
245
+ const providerType = this.authProvider.getProviderType();
246
+
247
+ const response = await fetch(`${this.serverUrl}/keys`, {
248
+ method: 'DELETE',
249
+ headers,
250
+ body: JSON.stringify({ providerType }),
251
+ });
252
+
253
+ if (!response.ok) {
254
+ throw new Error(`Failed to delete user key: ${response.statusText}`);
255
+ }
256
+ }
257
+ }