@learncard/sss-key-manager 0.1.24 → 0.2.1

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.
Files changed (75) hide show
  1. package/README.md +25 -95
  2. package/dist/api-client.d.ts +44 -7
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/atomic-operations.d.ts +10 -2
  5. package/dist/atomic-operations.d.ts.map +1 -1
  6. package/dist/email-relay-crypto.d.ts +49 -0
  7. package/dist/email-relay-crypto.d.ts.map +1 -0
  8. package/dist/escrow-attestation.d.ts +12 -0
  9. package/dist/escrow-attestation.d.ts.map +1 -0
  10. package/dist/escrow-crypto.d.ts +52 -0
  11. package/dist/escrow-crypto.d.ts.map +1 -0
  12. package/dist/escrow-nitro-attestation.d.ts +32 -0
  13. package/dist/escrow-nitro-attestation.d.ts.map +1 -0
  14. package/dist/escrow-pin.d.ts +22 -0
  15. package/dist/escrow-pin.d.ts.map +1 -0
  16. package/dist/index.d.ts +15 -4
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/key-manager.d.ts +2 -0
  19. package/dist/key-manager.d.ts.map +1 -1
  20. package/dist/qr-login.d.ts.map +1 -1
  21. package/dist/recovery-phrase.d.ts +6 -0
  22. package/dist/recovery-phrase.d.ts.map +1 -1
  23. package/dist/sss-key-manager.cjs.development.js +5564 -353
  24. package/dist/sss-key-manager.cjs.development.js.map +4 -4
  25. package/dist/sss-key-manager.cjs.production.min.js +33 -33
  26. package/dist/sss-key-manager.cjs.production.min.js.map +4 -4
  27. package/dist/sss-key-manager.esm.js +3494 -354
  28. package/dist/sss-key-manager.esm.js.map +4 -4
  29. package/dist/sss-strategy.d.ts +74 -4
  30. package/dist/sss-strategy.d.ts.map +1 -1
  31. package/dist/storage.d.ts +1 -0
  32. package/dist/storage.d.ts.map +1 -1
  33. package/dist/types.d.ts +98 -4
  34. package/dist/types.d.ts.map +1 -1
  35. package/package.json +7 -3
  36. package/src/__fixtures__/escrow-vectors-rust.json +82 -0
  37. package/src/__fixtures__/escrow-vectors.json +103 -0
  38. package/src/__fixtures__/nitro-attestation/bad-signature.cbor.b64 +1 -0
  39. package/src/__fixtures__/nitro-attestation/debug-zero-pcrs.cbor.b64 +1 -0
  40. package/src/__fixtures__/nitro-attestation/legacy-user-data.cbor.b64 +1 -0
  41. package/src/__fixtures__/nitro-attestation/manifest.json +60 -0
  42. package/src/__fixtures__/nitro-attestation/nonce-mismatch.cbor.b64 +1 -0
  43. package/src/__fixtures__/nitro-attestation/root.pem +12 -0
  44. package/src/__fixtures__/nitro-attestation/stale-timestamp.cbor.b64 +1 -0
  45. package/src/__fixtures__/nitro-attestation/tampered-previous-key-ids.cbor.b64 +1 -0
  46. package/src/__fixtures__/nitro-attestation/truncated-chain.cbor.b64 +1 -0
  47. package/src/__fixtures__/nitro-attestation/valid.cbor.b64 +1 -0
  48. package/src/__fixtures__/nitro-attestation/wrong-pcr0.cbor.b64 +1 -0
  49. package/src/__fixtures__/nitro-attestation/wrong-root.cbor.b64 +1 -0
  50. package/src/api-client.test.ts +122 -0
  51. package/src/api-client.ts +107 -116
  52. package/src/atomic-operations.test.ts +56 -16
  53. package/src/atomic-operations.ts +55 -19
  54. package/src/critical-paths.test.ts +51 -1
  55. package/src/email-relay-crypto.test.ts +87 -0
  56. package/src/email-relay-crypto.ts +293 -0
  57. package/src/escrow-attestation.test.ts +90 -0
  58. package/src/escrow-attestation.ts +125 -0
  59. package/src/escrow-crypto.test.ts +381 -0
  60. package/src/escrow-crypto.ts +319 -0
  61. package/src/escrow-nitro-attestation.test.ts +204 -0
  62. package/src/escrow-nitro-attestation.ts +286 -0
  63. package/src/escrow-pin.test.ts +119 -0
  64. package/src/escrow-pin.ts +83 -0
  65. package/src/escrow-vectors.test.ts +198 -0
  66. package/src/index.ts +70 -1
  67. package/src/key-manager.ts +4 -9
  68. package/src/qr-login.ts +22 -15
  69. package/src/recovery-phrase.ts +40 -0
  70. package/src/sss-session-writes.test.ts +109 -0
  71. package/src/sss-strategy.test.ts +2736 -111
  72. package/src/sss-strategy.ts +2226 -224
  73. package/src/storage.test.ts +66 -8
  74. package/src/storage.ts +41 -29
  75. package/src/types.ts +118 -6
@@ -0,0 +1,60 @@
1
+ {
2
+ "cases": {
3
+ "bad-signature": {
4
+ "accept": false,
5
+ "reason": "signature"
6
+ },
7
+ "debug-zero-pcrs": {
8
+ "accept": false,
9
+ "reason": "debug PCRs"
10
+ },
11
+ "legacy-user-data": {
12
+ "accept": true,
13
+ "previousKeyIds": []
14
+ },
15
+ "nonce-mismatch": {
16
+ "accept": false,
17
+ "nonceHex": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a",
18
+ "reason": "nonce"
19
+ },
20
+ "stale-timestamp": {
21
+ "accept": false,
22
+ "reason": "freshness"
23
+ },
24
+ "tampered-previous-key-ids": {
25
+ "accept": false,
26
+ "reason": "signature"
27
+ },
28
+ "truncated-chain": {
29
+ "accept": false,
30
+ "reason": "missing intermediate"
31
+ },
32
+ "valid": {
33
+ "accept": true
34
+ },
35
+ "wrong-pcr0": {
36
+ "accept": false,
37
+ "reason": "pcr"
38
+ },
39
+ "wrong-root": {
40
+ "accept": false,
41
+ "reason": "root pin"
42
+ }
43
+ },
44
+ "description": "Public fake CA, never trust in production. Freeze verifier clock to nowMs. Certificates valid 2020-2050. userDataBase64 was generated once by crypto::generate_escrow_key_pair; regeneration reuses it. keyId/previousKeyIds (P9.1) are fixture-only labels bound into the canonical-CBOR user_data wrapper, distinct from userDataBase64 (the escrow SPKI itself, still extracted identically from either user_data format).",
45
+ "keyId": "fixture-key",
46
+ "maxAgeMs": 300000,
47
+ "nonceHex": "0909090909090909090909090909090909090909090909090909090909090909",
48
+ "nowMs": 1790294401000,
49
+ "pcrs": {
50
+ "0": "010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101",
51
+ "1": "020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202",
52
+ "2": "030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303"
53
+ },
54
+ "previousKeyIds": ["fixture-previous-key"],
55
+ "publicKeyBase64": null,
56
+ "rootSha256": "d71ae13f792676894bf8549178ea45acc360b1935e628f678d9e2f3befae9dfe",
57
+ "testOnly": true,
58
+ "timestamp": 1790294400000,
59
+ "userDataBase64": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnX5GwyaaFpFYdl7m9ft+aF3MG6GanWFe7rvTqEGNoojfQtaaRXWNmYbC9affj2Twoy2RlWkOTGFETJQC5/f93w=="
60
+ }
@@ -0,0 +1 @@
1
+ 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
@@ -0,0 +1,12 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIBwjCCAUigAwIBAgIBATAKBggqhkjOPQQDAzApMScwJQYDVQQDDB5MZWFybkNh
3
+ cmQgVEVTVCBPTkxZIE5pdHJvIFJvb3QwIBcNMjAwMTAxMDAwMDAwWhgPMjA1MDAx
4
+ MDEwMDAwMDBaMCkxJzAlBgNVBAMMHkxlYXJuQ2FyZCBURVNUIE9OTFkgTml0cm8g
5
+ Um9vdDB2MBAGByqGSM49AgEGBSuBBAAiA2IABBck9uTlAFdttlkj3wkMkhPJrXNJ
6
+ ONf0FdJ5LLG8snfdW2PcZRQBGONRWfeZRqriSz+YkAODjFMLLnsBgWoBqER90QsP
7
+ a2AG4OjfOPIlmTHBqdjVRJ2zVK76DOKqU5xinaNCMEAwHQYDVR0OBBYEFH5W2DvY
8
+ +F7QW1n4UyVGh7YW3YnbMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG
9
+ MAoGCCqGSM49BAMDA2gAMGUCMQC3icReCK/AVxSDJpCGMMq06g5DlbIYg1Tg9Ad+
10
+ cZvYD0yr0ZuomoKL4B8JI0GBGiUCMFkQ4nGjuxzQu9jc7gb3O2UEhxivT2I2/8/P
11
+ NwOH2sbZ/YY5Za+AsWQ2vgbabHJxYw==
12
+ -----END CERTIFICATE-----
@@ -0,0 +1 @@
1
+ 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
@@ -0,0 +1 @@
1
+ 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
@@ -0,0 +1 @@
1
+ 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
@@ -0,0 +1 @@
1
+ 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
@@ -0,0 +1 @@
1
+ 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
@@ -0,0 +1 @@
1
+ hEShATgioFkKCKlpbW9kdWxlX2lkeBxsZWFybmNhcmQtZmFrZS1uc20tVEVTVC1PTkxZZmRpZ2VzdGZTSEEzODRpdGltZXN0YW1wGwAAAaDV3JwAZHBjcnOwAFgwAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAVgwAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAlgwAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA1gwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABVgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABlgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1gwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACFgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAClgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC1gwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADFgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADVgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADlgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1gwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa2NlcnRpZmljYXRlWQHtMIIB6TCCAW6gAwIBAgIBAzAKBggqhkjOPQQDAzAxMS8wLQYDVQQDDCZMZWFybkNhcmQgVEVTVCBPTkxZIE5pdHJvIEludGVybWVkaWF0ZTAgFw0yMDAxMDEwMDAwMDBaGA8yMDUwMDEwMTAwMDAwMFowKTEnMCUGA1UEAwweTGVhcm5DYXJkIFRFU1QgT05MWSBOaXRybyBMZWFmMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEBJ8TDzJLbNZ/uJqLrFm/zHS2TDLfb97e39EHwkfZ0xwE2/StOUKaKKyOhAEGIbKMILPR7zPUQdJ6Taz5qbU4ABER6YiycYopBTKrFnZS4z/Lhw+PAxoOllGUfNwrwjkGo2AwXjAdBgNVHQ4EFgQUVn+9f0Oa7MWFDZZPT8BZ3REubDswHwYDVR0jBBgwFoAUzY+QmTTCSOlx0H3DFcYcxnRR62cwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBsAwCgYIKoZIzj0EAwMDaQAwZgIxALb+USDVCdSilLcxV9cqnNpGVKDTezY/mh9WGNy1xkyB73x35GplnmdYvRqKfBrXEgIxAKrAxvebS390zZrIBUUuifW0b0xafxuMdMrAKEv9ZYUj77dTwHxx5ZREbZ9oMCXRQGhjYWJ1bmRsZYJZAccwggHDMIIBSKADAgECAgEBMAoGCCqGSM49BAMDMCkxJzAlBgNVBAMMHkxlYXJuQ2FyZCBURVNUIE9OTFkgTml0cm8gUm9vdDAgFw0yMDAxMDEwMDAwMDBaGA8yMDUwMDEwMTAwMDAwMFowKTEnMCUGA1UEAwweTGVhcm5DYXJkIFRFU1QgT05MWSBOaXRybyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEe6EoC9fLj1w7BvlRr8NdgbOiODxslwtNshNYa4w/J1xq7yla+GQGzE5U+3fYVu4en1PhMCAKKE7JRF40Xoy5PWr9oqIYaiM9t/eh2zEigBtl5au7Rud96/m8556swayco0IwQDAdBgNVHQ4EFgQU9OLCHJG1VjDguCiHUMX4iunyE2EwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaQAwZgIxANWY4PSaYRRwNx1U+W52KEZ1eyrtgo1WPUkeKCJ7EXyhZqftF9cQMeoIqi/OVbR3OAIxAMexIUP95FFx6HGZ4hC5ITW0kDDChAx06Za+Kl2XE7MZP/PrIj+bvE5Z5Hjlguwyp1kB8jCCAe4wggF0oAMCAQICAQIwCgYIKoZIzj0EAwMwKTEnMCUGA1UEAwweTGVhcm5DYXJkIFRFU1QgT05MWSBOaXRybyBSb290MCAXDTIwMDEwMTAwMDAwMFoYDzIwNTAwMTAxMDAwMDAwWjAxMS8wLQYDVQQDDCZMZWFybkNhcmQgVEVTVCBPTkxZIE5pdHJvIEludGVybWVkaWF0ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIG3U3QGqwIou+Ea2HBCgmck0mjouJVHxryvXtX1BjelcxGfynbpzNgeC6a24/3GJa1ModVWez/r9Xbz4Zdgh8MW/GBG03kcClvfqA/rQStbiWL7TuY2HVMxfnIrMqO1OqNmMGQwHQYDVR0OBBYEFM2PkJk0wkjpcdB9wxXGHMZ0UetnMB8GA1UdIwQYMBaAFPTiwhyRtVYw4Lgoh1DF+Irp8hNhMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMQDmDS76TFJxb4PJTY0+VOcfVHBTEv315YWmvUxkdh0DDKbSRp4ycO9wq+qlzSs1hpgCMAmXov+6OE3B53xDLEE91MqDA4/9eiH++zpA21LMaqO/Y38ndJd1H8u80HzquF44impwdWJsaWNfa2V59ml1c2VyX2RhdGFYg6MAWFswWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASdfkbDJpoWkVh2Xub1+35oXcwboZqdYV7uu9OoQY2iiN9C1ppFdY2ZhsL1p9+PZPCjLZGVaQ5MYURMlALn9/3fAWtmaXh0dXJlLWtleQKBdGZpeHR1cmUtcHJldmlvdXMta2V5ZW5vbmNlWCAJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCVhg7Uxjxl0oj51tI1I0PaYniKfmjtA1QL9mWMwEDBXtSsUOAkE/vLo9bIWkAngUbht+chS1XzPsCaRzWSNQ65RckLIAFkqF75vywIL/u85pTKulJtrxPSbVGVEj7Pz39fgu
@@ -0,0 +1,122 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { SSSApiClient } from './api-client';
4
+
5
+ import type { AuthProvider } from './types';
6
+
7
+ const SENTINEL_TOKEN = 'SENTINEL_TOKEN_XYZ';
8
+ const DID_CHALLENGE_ERROR = 'This operation requires the DID-challenge flow; use createSSSStrategy';
9
+
10
+ const createMockAuthProvider = (): AuthProvider => ({
11
+ getIdToken: vi.fn().mockResolvedValue(SENTINEL_TOKEN),
12
+ getCurrentUser: vi.fn().mockResolvedValue({
13
+ id: 'user-123',
14
+ email: 'sentinel-user@example.com',
15
+ providerType: 'firebase' as const,
16
+ }),
17
+ getProviderType: vi.fn().mockReturnValue('firebase' as const),
18
+ signOut: vi.fn().mockResolvedValue(undefined),
19
+ });
20
+
21
+ describe('SSSApiClient', () => {
22
+ let client: SSSApiClient;
23
+ let calls: { url: string; init?: RequestInit }[];
24
+
25
+ beforeEach(() => {
26
+ calls = [];
27
+
28
+ vi.spyOn(globalThis, 'fetch').mockImplementation(async (url, init) => {
29
+ calls.push({ url: url.toString(), init });
30
+ return new Response(
31
+ JSON.stringify({
32
+ authShare: null,
33
+ primaryDid: null,
34
+ securityLevel: 'basic',
35
+ recoveryMethods: [],
36
+ keyProvider: 'sss',
37
+ shareVersion: 1,
38
+ sssActivationState: 'active',
39
+ recoverySessionToken: 'recovery-session-token',
40
+ rebindSessionToken: 'rebind-session-token',
41
+ recoveryMethodsRequireConfirmation: [],
42
+ }),
43
+ { status: 200 }
44
+ );
45
+ });
46
+
47
+ client = new SSSApiClient({
48
+ serverUrl: 'http://test-server:5100/api',
49
+ authProvider: createMockAuthProvider(),
50
+ });
51
+ });
52
+
53
+ afterEach(() => {
54
+ vi.restoreAllMocks();
55
+ });
56
+
57
+ it('uses X-Auth-Token rather than Authorization for provider-authenticated reads', async () => {
58
+ await client.getAuthShare();
59
+
60
+ const headers = calls[0]!.init?.headers as Record<string, string>;
61
+ expect(headers['X-Auth-Token']).toBe(SENTINEL_TOKEN);
62
+ expect(headers.Authorization).toBeUndefined();
63
+ expect(calls[0]!.url).not.toContain(SENTINEL_TOKEN);
64
+ });
65
+
66
+ it('keeps recovery read credentials out of the URL', async () => {
67
+ await client.getRecoveryShare('passkey', 'credential-1');
68
+
69
+ const headers = calls[0]!.init?.headers as Record<string, string>;
70
+ expect(headers['X-Auth-Token']).toBe(SENTINEL_TOKEN);
71
+ expect(calls[0]!.url).not.toContain(SENTINEL_TOKEN);
72
+ expect(calls[0]!.url).not.toMatch(/[?&](token|share)=/i);
73
+ });
74
+
75
+ it('retains the valid unauthenticated identity-recovery helpers', async () => {
76
+ await client.startIdentityRecovery('recovery@example.com');
77
+ await client.verifyIdentityRecovery('recovery@example.com', '123456');
78
+ await client.useIdentityRecoverySession('recovery-session-token', 'phrase');
79
+
80
+ expect(calls.map(call => call.url)).toEqual([
81
+ 'http://test-server:5100/api/keys/recovery-session/start',
82
+ 'http://test-server:5100/api/keys/recovery-session/verify',
83
+ 'http://test-server:5100/api/keys/recovery-session/recover',
84
+ ]);
85
+ });
86
+
87
+ it('retains identity rebind because it supplies both DID proof and the auth token', async () => {
88
+ await client.completeIdentityRecoveryRebind({
89
+ recoverySessionToken: 'rebind-session-token',
90
+ newAuthToken: SENTINEL_TOKEN,
91
+ providerType: 'firebase',
92
+ primaryDid: 'did:key:zTest',
93
+ authShare: { encryptedData: 'encrypted-share', encryptedDek: '', iv: '' },
94
+ didAuthVp: 'did-auth-vp',
95
+ });
96
+
97
+ const headers = calls[0]!.init?.headers as Record<string, string>;
98
+ expect(headers.Authorization).toBe('Bearer did-auth-vp');
99
+ expect(headers['X-Auth-Token']).toBe(SENTINEL_TOKEN);
100
+ });
101
+
102
+ it.each([
103
+ [
104
+ 'storeAuthShare',
105
+ () =>
106
+ client.storeAuthShare({
107
+ authShare: { encryptedData: 'share', encryptedDek: '', iv: '' },
108
+ primaryDid: 'did:key:zTest',
109
+ }),
110
+ ],
111
+ ['addRecoveryMethod', () => client.addRecoveryMethod({ type: 'phrase' })],
112
+ ['markMigrated', () => client.markMigrated()],
113
+ ['activate', () => client.activate()],
114
+ ['sendEmailBackupShare', () => client.sendEmailBackupShare('email-share')],
115
+ ['addRecoveryEmail', () => client.addRecoveryEmail('recovery@example.com')],
116
+ ['verifyRecoveryEmail', () => client.verifyRecoveryEmail('123456')],
117
+ ['deleteUserKey', () => client.deleteUserKey()],
118
+ ])('%s fails closed instead of calling a route without DID proof', async (_name, call) => {
119
+ await expect(call()).rejects.toThrow(DID_CHALLENGE_ERROR);
120
+ expect(calls).toHaveLength(0);
121
+ });
122
+ });
package/src/api-client.ts CHANGED
@@ -5,12 +5,21 @@
5
5
  import type {
6
6
  AuthProvider,
7
7
  AuthProviderType,
8
- ContactMethod,
9
8
  ServerEncryptedShare,
10
9
  RecoveryMethodInfo,
11
10
  EncryptedShare,
12
11
  SecurityLevel,
12
+ SssActivationState,
13
+ IdentityRecoverySession,
13
14
  } from './types';
15
+ import type { EmailRelayBranding } from './email-relay-crypto';
16
+
17
+ const DID_CHALLENGE_REQUIRED_ERROR =
18
+ 'This operation requires the DID-challenge flow; use createSSSStrategy';
19
+
20
+ const throwDidChallengeRequired = (): never => {
21
+ throw new Error(DID_CHALLENGE_REQUIRED_ERROR);
22
+ };
14
23
 
15
24
  export interface GetAuthShareResponse {
16
25
  authShare: ServerEncryptedShare | null;
@@ -18,13 +27,16 @@ export interface GetAuthShareResponse {
18
27
  securityLevel: SecurityLevel;
19
28
  recoveryMethods: RecoveryMethodInfo[];
20
29
  keyProvider: 'web3auth' | 'sss';
30
+ shareVersion: number;
21
31
  maskedRecoveryEmail?: string | null;
32
+ sssActivationState: SssActivationState;
22
33
  }
23
34
 
24
35
  export interface StoreAuthShareInput {
25
36
  authShare: ServerEncryptedShare;
26
37
  primaryDid: string;
27
38
  securityLevel?: SecurityLevel;
39
+ sssActivationState?: 'provisional';
28
40
  }
29
41
 
30
42
  export interface StoreRecoveryShareInput {
@@ -34,11 +46,23 @@ export interface StoreRecoveryShareInput {
34
46
  shareVersion?: number;
35
47
  }
36
48
 
49
+ export interface RecoverySessionMaterial {
50
+ authShare: ServerEncryptedShare;
51
+ encryptedShare?: EncryptedShare;
52
+ primaryDid: string;
53
+ shareVersion: number;
54
+ rebindSessionToken: string;
55
+ }
56
+
37
57
  export interface ApiClientConfig {
38
58
  serverUrl: string;
39
59
  authProvider: AuthProvider;
60
+ escrowRelayPublicKey?: string;
61
+ escrowRelayKeyId?: string;
62
+ emailBranding?: EmailRelayBranding;
40
63
  }
41
64
 
65
+ /** @deprecated Use `createSSSStrategy`, which implements the hardened DID-challenge flow. */
42
66
  export class SSSApiClient {
43
67
  private serverUrl: string;
44
68
  private authProvider: AuthProvider;
@@ -52,23 +76,10 @@ export class SSSApiClient {
52
76
  const token = await this.authProvider.getIdToken();
53
77
  return {
54
78
  'Content-Type': 'application/json',
55
- Authorization: `Bearer ${token}`,
79
+ 'X-Auth-Token': token,
56
80
  };
57
81
  }
58
82
 
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
83
  async getAuthShare(): Promise<GetAuthShareResponse | null> {
73
84
  const headers = await this.getAuthHeaders();
74
85
  const providerType = this.authProvider.getProviderType();
@@ -89,54 +100,28 @@ export class SSSApiClient {
89
100
  return response.json();
90
101
  }
91
102
 
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
- }
103
+ /** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
104
+ async storeAuthShare(_input: StoreAuthShareInput): Promise<void> {
105
+ throwDidChallengeRequired();
108
106
  }
109
107
 
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
- }
108
+ /** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
109
+ async addRecoveryMethod(_input: StoreRecoveryShareInput): Promise<void> {
110
+ throwDidChallengeRequired();
126
111
  }
127
112
 
128
113
  async getRecoveryShare(
129
114
  type: 'passkey' | 'backup' | 'phrase' | 'email',
130
115
  credentialId?: string
131
116
  ): Promise<{ encryptedShare?: EncryptedShare; shareVersion?: number } | null> {
117
+ // P0-4: authToken travels via the X-Auth-Token header — never as a
118
+ // query param, which would land in proxy/ALB access logs. A
119
+ // dedicated header (not Authorization) avoids colliding with
120
+ // clients that send a DID-Auth VP as Authorization for this route.
132
121
  const token = await this.authProvider.getIdToken();
133
122
  const providerType = this.authProvider.getProviderType();
134
123
 
135
- const params = new URLSearchParams({
136
- type,
137
- providerType,
138
- authToken: token,
139
- });
124
+ const params = new URLSearchParams({ type, providerType });
140
125
 
141
126
  if (credentialId) {
142
127
  params.append('credentialId', credentialId);
@@ -144,9 +129,7 @@ export class SSSApiClient {
144
129
 
145
130
  const response = await fetch(`${this.serverUrl}/keys/recovery?${params}`, {
146
131
  method: 'GET',
147
- headers: {
148
- 'Content-Type': 'application/json',
149
- },
132
+ headers: { 'Content-Type': 'application/json', 'X-Auth-Token': token },
150
133
  });
151
134
 
152
135
  if (!response.ok) {
@@ -159,99 +142,107 @@ export class SSSApiClient {
159
142
  return response.json();
160
143
  }
161
144
 
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`, {
145
+ async startIdentityRecovery(email: string): Promise<void> {
146
+ const response = await fetch(`${this.serverUrl}/keys/recovery-session/start`, {
167
147
  method: 'POST',
168
- headers,
169
- body: JSON.stringify({ providerType }),
148
+ headers: { 'Content-Type': 'application/json' },
149
+ body: JSON.stringify({ email }),
170
150
  });
171
151
 
172
152
  if (!response.ok) {
173
- throw new Error(`Failed to mark as migrated: ${response.statusText}`);
153
+ throw new Error(`Failed to send recovery code: ${response.statusText}`);
174
154
  }
175
155
  }
176
156
 
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`, {
157
+ async verifyIdentityRecovery(email: string, code: string): Promise<IdentityRecoverySession> {
158
+ const response = await fetch(`${this.serverUrl}/keys/recovery-session/verify`, {
194
159
  method: 'POST',
195
- headers,
196
- body: JSON.stringify({
197
- providerType,
198
- emailShare,
199
- email,
200
- }),
160
+ headers: { 'Content-Type': 'application/json' },
161
+ body: JSON.stringify({ email, code }),
201
162
  });
202
163
 
203
164
  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}`);
165
+ throw new Error(`Failed to verify recovery code: ${response.statusText}`);
206
166
  }
207
- }
208
167
 
209
- async addRecoveryEmail(email: string): Promise<void> {
210
- const token = await this.authProvider.getIdToken();
211
- const providerType = this.authProvider.getProviderType();
168
+ return response.json();
169
+ }
212
170
 
213
- const response = await fetch(`${this.serverUrl}/keys/recovery-email/add`, {
171
+ async useIdentityRecoverySession(
172
+ recoverySessionToken: string,
173
+ type: StoreRecoveryShareInput['type'],
174
+ credentialId?: string
175
+ ): Promise<RecoverySessionMaterial> {
176
+ const response = await fetch(`${this.serverUrl}/keys/recovery-session/recover`, {
214
177
  method: 'POST',
215
178
  headers: { 'Content-Type': 'application/json' },
216
- body: JSON.stringify({ authToken: token, providerType, email }),
179
+ body: JSON.stringify({ recoverySessionToken, type, credentialId }),
217
180
  });
218
181
 
219
182
  if (!response.ok) {
220
- const data = await response.json().catch(() => ({}));
221
- throw new Error(data?.message || `Failed to add recovery email: ${response.statusText}`);
183
+ throw new Error(`Failed to retrieve recovery data: ${response.statusText}`);
222
184
  }
223
- }
224
185
 
225
- async verifyRecoveryEmail(code: string): Promise<{ maskedEmail: string }> {
226
- const token = await this.authProvider.getIdToken();
227
- const providerType = this.authProvider.getProviderType();
186
+ return response.json();
187
+ }
228
188
 
229
- const response = await fetch(`${this.serverUrl}/keys/recovery-email/verify`, {
189
+ async completeIdentityRecoveryRebind(input: {
190
+ recoverySessionToken: string;
191
+ newAuthToken: string;
192
+ providerType: AuthProviderType;
193
+ primaryDid: string;
194
+ authShare: ServerEncryptedShare;
195
+ didAuthVp: string;
196
+ }): Promise<{ shareVersion: number; recoveryMethodsRequireConfirmation: string[] }> {
197
+ const response = await fetch(`${this.serverUrl}/keys/recovery-session/rebind`, {
230
198
  method: 'POST',
231
- headers: { 'Content-Type': 'application/json' },
232
- body: JSON.stringify({ authToken: token, providerType, code }),
199
+ headers: {
200
+ 'Content-Type': 'application/json',
201
+ Authorization: `Bearer ${input.didAuthVp}`,
202
+ 'X-Auth-Token': input.newAuthToken,
203
+ },
204
+ body: JSON.stringify({
205
+ recoverySessionToken: input.recoverySessionToken,
206
+ providerType: input.providerType,
207
+ primaryDid: input.primaryDid,
208
+ authShare: input.authShare,
209
+ }),
233
210
  });
234
211
 
235
212
  if (!response.ok) {
236
- const data = await response.json().catch(() => ({}));
237
- throw new Error(data?.message || `Failed to verify recovery email: ${response.statusText}`);
213
+ throw new Error(`Failed to bind the new sign-in: ${response.statusText}`);
238
214
  }
239
215
 
240
216
  return response.json();
241
217
  }
242
218
 
243
- async deleteUserKey(): Promise<void> {
244
- const headers = await this.getAuthHeaders();
245
- const providerType = this.authProvider.getProviderType();
219
+ /** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
220
+ async markMigrated(): Promise<void> {
221
+ throwDidChallengeRequired();
222
+ }
246
223
 
247
- const response = await fetch(`${this.serverUrl}/keys`, {
248
- method: 'DELETE',
249
- headers,
250
- body: JSON.stringify({ providerType }),
251
- });
224
+ /** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
225
+ async activate(): Promise<void> {
226
+ throwDidChallengeRequired();
227
+ }
252
228
 
253
- if (!response.ok) {
254
- throw new Error(`Failed to delete user key: ${response.statusText}`);
255
- }
229
+ /** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
230
+ async sendEmailBackupShare(_emailShare: string, _overrideEmail?: string): Promise<void> {
231
+ throwDidChallengeRequired();
232
+ }
233
+
234
+ /** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
235
+ async addRecoveryEmail(_email: string): Promise<void> {
236
+ throwDidChallengeRequired();
237
+ }
238
+
239
+ /** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
240
+ async verifyRecoveryEmail(_code: string): Promise<{ maskedEmail: string }> {
241
+ return throwDidChallengeRequired();
242
+ }
243
+
244
+ /** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
245
+ async deleteUserKey(): Promise<void> {
246
+ throwDidChallengeRequired();
256
247
  }
257
248
  }