@metamask-previews/mfa-recovery-controller 0.0.0-preview-a2f6118d2

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 (46) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +6 -0
  3. package/LICENSE.APACHE2 +201 -0
  4. package/LICENSE.MIT +21 -0
  5. package/README.md +22 -0
  6. package/dist/MfaRecoveryController-method-action-types.d.ts +88 -0
  7. package/dist/MfaRecoveryController-method-action-types.d.ts.map +1 -0
  8. package/dist/MfaRecoveryController-method-action-types.js +6 -0
  9. package/dist/MfaRecoveryController-method-action-types.js.map +1 -0
  10. package/dist/MfaRecoveryController.d.ts +119 -0
  11. package/dist/MfaRecoveryController.d.ts.map +1 -0
  12. package/dist/MfaRecoveryController.js +456 -0
  13. package/dist/MfaRecoveryController.js.map +1 -0
  14. package/dist/crypto.d.ts +111 -0
  15. package/dist/crypto.d.ts.map +1 -0
  16. package/dist/crypto.js +268 -0
  17. package/dist/crypto.js.map +1 -0
  18. package/dist/errors.d.ts +16 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +25 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/escrow-utils.d.ts +42 -0
  23. package/dist/escrow-utils.d.ts.map +1 -0
  24. package/dist/escrow-utils.js +77 -0
  25. package/dist/escrow-utils.js.map +1 -0
  26. package/dist/identifier-auth.d.ts +83 -0
  27. package/dist/identifier-auth.d.ts.map +1 -0
  28. package/dist/identifier-auth.js +119 -0
  29. package/dist/identifier-auth.js.map +1 -0
  30. package/dist/index.d.ts +7 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +4 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/pending-operation-validation.d.ts +21 -0
  35. package/dist/pending-operation-validation.d.ts.map +1 -0
  36. package/dist/pending-operation-validation.js +181 -0
  37. package/dist/pending-operation-validation.js.map +1 -0
  38. package/dist/state-machine.d.ts +23 -0
  39. package/dist/state-machine.d.ts.map +1 -0
  40. package/dist/state-machine.js +34 -0
  41. package/dist/state-machine.js.map +1 -0
  42. package/dist/types.d.ts +235 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +5 -0
  45. package/dist/types.js.map +1 -0
  46. package/package.json +76 -0
package/dist/crypto.js ADDED
@@ -0,0 +1,268 @@
1
+ import { base64ToBytes, bytesToBase64, bytesToHex, concatBytes, hexToBytes, stringToBytes, } from '@metamask/utils';
2
+ import { chacha20poly1305 } from '@noble/ciphers/chacha';
3
+ import { p256 } from '@noble/curves/nist';
4
+ import { hkdf } from '@noble/hashes/hkdf';
5
+ import { sha256 as sha256Sync } from '@noble/hashes/sha2';
6
+ const WRAP_INFO = new TextEncoder().encode('escrow-wrap-v1');
7
+ const CHACHA_KEY_LEN = 32;
8
+ const CHACHA_NONCE_LEN = 12;
9
+ /**
10
+ * Recursively sorts object keys so hashes are independent of property order.
11
+ *
12
+ * @param value - JSON-compatible value.
13
+ * @returns A canonical JSON string.
14
+ */
15
+ export function canonicalize(value) {
16
+ return JSON.stringify(sortKeys(value));
17
+ }
18
+ /**
19
+ * SHA-256 of the canonical JSON form of `value`, as a 0x-prefixed hex string.
20
+ *
21
+ * @param value - Value to hash.
22
+ * @returns Hex digest.
23
+ */
24
+ export function hash(value) {
25
+ return bytesToHex(sha256Sync(stringToBytes(canonicalize(value))));
26
+ }
27
+ /**
28
+ * Canonical identifier list bound into AuthController `identifiersHash`.
29
+ *
30
+ * @param identifiers - Identifier set.
31
+ * @returns Sorted identifier records including verifier material.
32
+ */
33
+ export function canonicalizeIdentifiers(identifiers) {
34
+ return [...identifiers]
35
+ .map((identifier) => ({
36
+ namespace: identifier.namespace,
37
+ type: identifier.type,
38
+ value: identifier.value,
39
+ verifier: identifier.verifier,
40
+ }))
41
+ .sort((left, right) => canonicalize(left).localeCompare(canonicalize(right)));
42
+ }
43
+ /**
44
+ * @returns A random 0x-prefixed id.
45
+ */
46
+ export function randomId() {
47
+ return bytesToHex(randomBytes(16));
48
+ }
49
+ /**
50
+ * Unix time in seconds. AuthController tokens and PoP challenges use this unit.
51
+ *
52
+ * @returns Seconds since the Unix epoch.
53
+ */
54
+ export function unixNow() {
55
+ return Math.floor(Date.now() / 1000);
56
+ }
57
+ /**
58
+ * Generates an ephemeral ECDSA P-256 proof key pair for key-bound identifier PoP.
59
+ *
60
+ * @returns JWK-encoded public and private keys.
61
+ */
62
+ export function generateSigningKey() {
63
+ const privateKeyBytes = p256.utils.randomSecretKey();
64
+ const publicKeyBytes = p256.getPublicKey(privateKeyBytes, false);
65
+ const publicJwk = {
66
+ kty: 'EC',
67
+ crv: 'P-256',
68
+ x: toBase64Url(publicKeyBytes.slice(1, 33)),
69
+ y: toBase64Url(publicKeyBytes.slice(33, 65)),
70
+ };
71
+ return {
72
+ publicKey: JSON.stringify(publicJwk),
73
+ privateKey: JSON.stringify({
74
+ ...publicJwk,
75
+ d: toBase64Url(privateKeyBytes),
76
+ }),
77
+ };
78
+ }
79
+ /**
80
+ * Signs a message with the ephemeral proof private key.
81
+ *
82
+ * @param privateKey - JWK JSON private key.
83
+ * @param message - Message string; hashed with SHA-256 before ECDSA.
84
+ * @returns Compact IEEE P1363 hex signature (`r‖s`).
85
+ */
86
+ export function sign(privateKey, message) {
87
+ const jwk = JSON.parse(privateKey);
88
+ if (typeof jwk.d !== 'string') {
89
+ throw new Error('Invalid P-256 private JWK');
90
+ }
91
+ return bytesToHex(p256
92
+ .sign(sha256Sync(stringToBytes(message)), fromBase64Url(jwk.d))
93
+ .toBytes('compact'));
94
+ }
95
+ /**
96
+ * Verifies a P-256 signature against a public key.
97
+ *
98
+ * @param publicKey - JWK JSON public key.
99
+ * @param signature - Compact IEEE P1363 hex signature.
100
+ * @param message - Message string; hashed with SHA-256 before ECDSA.
101
+ * @returns Whether the signature is valid.
102
+ */
103
+ export function verifySignature(publicKey, signature, message) {
104
+ const jwk = JSON.parse(publicKey);
105
+ if (typeof jwk.x !== 'string' || typeof jwk.y !== 'string') {
106
+ return false;
107
+ }
108
+ try {
109
+ return p256.verify(hexToBytes(signature), sha256Sync(stringToBytes(message)), concatBytes([
110
+ new Uint8Array([0x04]),
111
+ fromBase64Url(jwk.x),
112
+ fromBase64Url(jwk.y),
113
+ ]));
114
+ }
115
+ catch {
116
+ return false;
117
+ }
118
+ }
119
+ /**
120
+ * Unsigned mutation-receipt digest. SHA-256 of canonical JSON of the receipt
121
+ * fields excluding `signature`.
122
+ *
123
+ * @param receipt - Receipt fields covered by the signature.
124
+ * @param receipt.escrowId - Escrow that issued the receipt.
125
+ * @param receipt.mutationId - Mutation id.
126
+ * @param receipt.receiptKeyId - SHA-256 of the uncompressed receipt public key.
127
+ * @param receipt.requestHash - Mutation request hash.
128
+ * @param receipt.version - Applied version.
129
+ * @returns Hex digest.
130
+ */
131
+ export function hashMutationReceipt(receipt) {
132
+ return hash({
133
+ escrowId: receipt.escrowId,
134
+ mutationId: receipt.mutationId,
135
+ receiptKeyId: receipt.receiptKeyId,
136
+ requestHash: receipt.requestHash,
137
+ version: receipt.version,
138
+ });
139
+ }
140
+ /**
141
+ * Decodes hex that may or may not have a `0x` prefix.
142
+ *
143
+ * @param hexString - Hex string.
144
+ * @returns Raw bytes.
145
+ */
146
+ export function decodeHex(hexString) {
147
+ return hexToBytes((hexString.startsWith('0x') ? hexString : `0x${hexString}`));
148
+ }
149
+ /**
150
+ * SHA-256 of the uncompressed P-256 wrap public key, as `0x` hex.
151
+ *
152
+ * @param wrapPublicKey - Wrap public JWK JSON.
153
+ * @returns Key id matching cubist `wrapKeyId`.
154
+ */
155
+ export function wrapKeyId(wrapPublicKey) {
156
+ return bytesToHex(sha256Sync(publicKeyBytesFromJwk(wrapPublicKey)));
157
+ }
158
+ /**
159
+ * Encrypts plaintext to a P-256 public key using escrow-wrap-v1.
160
+ *
161
+ * @param privateKey - Sender P-256 private JWK JSON.
162
+ * @param publicKey - Recipient P-256 public JWK JSON.
163
+ * @param plaintext - Bytes to encrypt.
164
+ * @returns Unprefixed hex of `nonce || ciphertext+tag`.
165
+ */
166
+ export function encryptToPublic(privateKey, publicKey, plaintext) {
167
+ const key = deriveTransportKey(privateKey, publicKey);
168
+ const nonce = randomBytes(CHACHA_NONCE_LEN);
169
+ const ciphertext = chacha20poly1305(key, nonce).encrypt(plaintext);
170
+ return bytesToHex(concatBytes([nonce, ciphertext])).slice(2);
171
+ }
172
+ /**
173
+ * Decrypts escrow-wrap-v1 ciphertext produced by {@link encryptToPublic}.
174
+ *
175
+ * @param privateKey - Recipient P-256 private JWK JSON.
176
+ * @param publicKey - Sender P-256 public JWK JSON.
177
+ * @param ciphertextHex - Unprefixed or `0x` hex of `nonce || ciphertext+tag`.
178
+ * @returns Plaintext bytes.
179
+ */
180
+ export function decryptFromPublic(privateKey, publicKey, ciphertextHex) {
181
+ const key = deriveTransportKey(privateKey, publicKey);
182
+ const blob = decodeHex(ciphertextHex);
183
+ if (blob.length < CHACHA_NONCE_LEN) {
184
+ throw new Error('Wrapped secret is truncated');
185
+ }
186
+ return chacha20poly1305(key, blob.subarray(0, CHACHA_NONCE_LEN)).decrypt(blob.subarray(CHACHA_NONCE_LEN));
187
+ }
188
+ function deriveTransportKey(privateKey, publicKey) {
189
+ const privateBytes = privateKeyBytesFromJwk(privateKey);
190
+ const publicBytes = publicKeyBytesFromJwk(publicKey);
191
+ const shared = p256.getSharedSecret(privateBytes, publicBytes);
192
+ // noble returns a compressed point (33 bytes). Cubist uses the 32-byte x-coordinate.
193
+ const ikm = shared.slice(1, 33);
194
+ return hkdf(sha256Sync, ikm, undefined, WRAP_INFO, CHACHA_KEY_LEN);
195
+ }
196
+ function privateKeyBytesFromJwk(privateKey) {
197
+ const jwk = JSON.parse(privateKey);
198
+ if (typeof jwk.d !== 'string') {
199
+ throw new Error('Invalid P-256 private JWK');
200
+ }
201
+ return fromBase64Url(jwk.d);
202
+ }
203
+ function publicKeyBytesFromJwk(publicKey) {
204
+ const jwk = JSON.parse(publicKey);
205
+ if (jwk.kty !== 'EC' ||
206
+ jwk.crv !== 'P-256' ||
207
+ typeof jwk.x !== 'string' ||
208
+ typeof jwk.y !== 'string') {
209
+ throw new Error('Invalid P-256 public JWK');
210
+ }
211
+ return concatBytes([
212
+ new Uint8Array([0x04]),
213
+ fromBase64Url(jwk.x),
214
+ fromBase64Url(jwk.y),
215
+ ]);
216
+ }
217
+ /**
218
+ * Encodes bytes as unpadded base64url (JWK coordinate format).
219
+ *
220
+ * @param bytes - Raw coordinate or scalar.
221
+ * @returns Base64url string.
222
+ */
223
+ function toBase64Url(bytes) {
224
+ return bytesToBase64(bytes)
225
+ .replace(/\+/gu, '-')
226
+ .replace(/\//gu, '_')
227
+ .replace(/[=]+$/u, '');
228
+ }
229
+ /**
230
+ * @param length - Number of bytes to generate.
231
+ * @returns CSPRNG bytes.
232
+ */
233
+ function randomBytes(length) {
234
+ return globalThis.crypto.getRandomValues(new Uint8Array(length));
235
+ }
236
+ /**
237
+ * Decodes unpadded base64url JWK coordinates.
238
+ *
239
+ * @param value - Base64url string.
240
+ * @returns Raw bytes.
241
+ */
242
+ function fromBase64Url(value) {
243
+ return base64ToBytes(value
244
+ .replace(/-/gu, '+')
245
+ .replace(/_/gu, '/')
246
+ .padEnd(value.length + ((4 - (value.length % 4)) % 4), '='));
247
+ }
248
+ /**
249
+ * Recursively sorts keys and encodes Uint8Array as hex.
250
+ *
251
+ * @param value - Value to normalize.
252
+ * @returns JSON-safe canonical form.
253
+ */
254
+ function sortKeys(value) {
255
+ if (value instanceof Uint8Array) {
256
+ return bytesToHex(value);
257
+ }
258
+ if (Array.isArray(value)) {
259
+ return value.map(sortKeys);
260
+ }
261
+ if (value !== null && typeof value === 'object') {
262
+ return Object.fromEntries(Object.keys(value)
263
+ .sort()
264
+ .map((key) => [key, sortKeys(value[key])]));
265
+ }
266
+ return value;
267
+ }
268
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n base64ToBytes,\n bytesToBase64,\n bytesToHex,\n concatBytes,\n hexToBytes,\n stringToBytes,\n} from '@metamask/utils';\nimport type { Hex } from '@metamask/utils';\nimport { chacha20poly1305 } from '@noble/ciphers/chacha';\nimport { p256 } from '@noble/curves/nist';\nimport { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 as sha256Sync } from '@noble/hashes/sha2';\n\nimport type { EcPublicJwk, Identifier } from './types.js';\n\nconst WRAP_INFO = new TextEncoder().encode('escrow-wrap-v1');\nconst CHACHA_KEY_LEN = 32;\nconst CHACHA_NONCE_LEN = 12;\n\n/**\n * Recursively sorts object keys so hashes are independent of property order.\n *\n * @param value - JSON-compatible value.\n * @returns A canonical JSON string.\n */\nexport function canonicalize(value: unknown): string {\n return JSON.stringify(sortKeys(value));\n}\n\n/**\n * SHA-256 of the canonical JSON form of `value`, as a 0x-prefixed hex string.\n *\n * @param value - Value to hash.\n * @returns Hex digest.\n */\nexport function hash(value: unknown): Hex {\n return bytesToHex(sha256Sync(stringToBytes(canonicalize(value))));\n}\n\n/**\n * Canonical identifier list bound into AuthController `identifiersHash`.\n *\n * @param identifiers - Identifier set.\n * @returns Sorted identifier records including verifier material.\n */\nexport function canonicalizeIdentifiers(identifiers: Identifier[]): unknown {\n return [...identifiers]\n .map((identifier) => ({\n namespace: identifier.namespace,\n type: identifier.type,\n value: identifier.value,\n verifier: identifier.verifier,\n }))\n .sort((left, right) =>\n canonicalize(left).localeCompare(canonicalize(right)),\n );\n}\n\n/**\n * @returns A random 0x-prefixed id.\n */\nexport function randomId(): Hex {\n return bytesToHex(randomBytes(16));\n}\n\n/**\n * Unix time in seconds. AuthController tokens and PoP challenges use this unit.\n *\n * @returns Seconds since the Unix epoch.\n */\nexport function unixNow(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/**\n * Generates an ephemeral ECDSA P-256 proof key pair for key-bound identifier PoP.\n *\n * @returns JWK-encoded public and private keys.\n */\nexport function generateSigningKey(): {\n publicKey: string;\n privateKey: string;\n} {\n const privateKeyBytes = p256.utils.randomSecretKey();\n const publicKeyBytes = p256.getPublicKey(privateKeyBytes, false);\n const publicJwk = {\n kty: 'EC',\n crv: 'P-256',\n x: toBase64Url(publicKeyBytes.slice(1, 33)),\n y: toBase64Url(publicKeyBytes.slice(33, 65)),\n };\n return {\n publicKey: JSON.stringify(publicJwk),\n privateKey: JSON.stringify({\n ...publicJwk,\n d: toBase64Url(privateKeyBytes),\n }),\n };\n}\n\n/**\n * Signs a message with the ephemeral proof private key.\n *\n * @param privateKey - JWK JSON private key.\n * @param message - Message string; hashed with SHA-256 before ECDSA.\n * @returns Compact IEEE P1363 hex signature (`r‖s`).\n */\nexport function sign(privateKey: string, message: string): Hex {\n const jwk = JSON.parse(privateKey) as { d?: string };\n if (typeof jwk.d !== 'string') {\n throw new Error('Invalid P-256 private JWK');\n }\n return bytesToHex(\n p256\n .sign(sha256Sync(stringToBytes(message)), fromBase64Url(jwk.d))\n .toBytes('compact'),\n );\n}\n\n/**\n * Verifies a P-256 signature against a public key.\n *\n * @param publicKey - JWK JSON public key.\n * @param signature - Compact IEEE P1363 hex signature.\n * @param message - Message string; hashed with SHA-256 before ECDSA.\n * @returns Whether the signature is valid.\n */\nexport function verifySignature(\n publicKey: string,\n signature: string,\n message: string,\n): boolean {\n const jwk = JSON.parse(publicKey) as { x?: string; y?: string };\n if (typeof jwk.x !== 'string' || typeof jwk.y !== 'string') {\n return false;\n }\n try {\n return p256.verify(\n hexToBytes(signature as Hex),\n sha256Sync(stringToBytes(message)),\n concatBytes([\n new Uint8Array([0x04]),\n fromBase64Url(jwk.x),\n fromBase64Url(jwk.y),\n ]),\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Unsigned mutation-receipt digest. SHA-256 of canonical JSON of the receipt\n * fields excluding `signature`.\n *\n * @param receipt - Receipt fields covered by the signature.\n * @param receipt.escrowId - Escrow that issued the receipt.\n * @param receipt.mutationId - Mutation id.\n * @param receipt.receiptKeyId - SHA-256 of the uncompressed receipt public key.\n * @param receipt.requestHash - Mutation request hash.\n * @param receipt.version - Applied version.\n * @returns Hex digest.\n */\nexport function hashMutationReceipt(receipt: {\n escrowId: string;\n mutationId: string;\n receiptKeyId: string;\n requestHash: string;\n version: number;\n}): Hex {\n return hash({\n escrowId: receipt.escrowId,\n mutationId: receipt.mutationId,\n receiptKeyId: receipt.receiptKeyId,\n requestHash: receipt.requestHash,\n version: receipt.version,\n });\n}\n\n/**\n * Decodes hex that may or may not have a `0x` prefix.\n *\n * @param hexString - Hex string.\n * @returns Raw bytes.\n */\nexport function decodeHex(hexString: string): Uint8Array {\n return hexToBytes(\n (hexString.startsWith('0x') ? hexString : `0x${hexString}`) as Hex,\n );\n}\n\n/**\n * SHA-256 of the uncompressed P-256 wrap public key, as `0x` hex.\n *\n * @param wrapPublicKey - Wrap public JWK JSON.\n * @returns Key id matching cubist `wrapKeyId`.\n */\nexport function wrapKeyId(wrapPublicKey: string): Hex {\n return bytesToHex(sha256Sync(publicKeyBytesFromJwk(wrapPublicKey)));\n}\n\n/**\n * Encrypts plaintext to a P-256 public key using escrow-wrap-v1.\n *\n * @param privateKey - Sender P-256 private JWK JSON.\n * @param publicKey - Recipient P-256 public JWK JSON.\n * @param plaintext - Bytes to encrypt.\n * @returns Unprefixed hex of `nonce || ciphertext+tag`.\n */\nexport function encryptToPublic(\n privateKey: string,\n publicKey: string,\n plaintext: Uint8Array,\n): string {\n const key = deriveTransportKey(privateKey, publicKey);\n const nonce = randomBytes(CHACHA_NONCE_LEN);\n const ciphertext = chacha20poly1305(key, nonce).encrypt(plaintext);\n return bytesToHex(concatBytes([nonce, ciphertext])).slice(2);\n}\n\n/**\n * Decrypts escrow-wrap-v1 ciphertext produced by {@link encryptToPublic}.\n *\n * @param privateKey - Recipient P-256 private JWK JSON.\n * @param publicKey - Sender P-256 public JWK JSON.\n * @param ciphertextHex - Unprefixed or `0x` hex of `nonce || ciphertext+tag`.\n * @returns Plaintext bytes.\n */\nexport function decryptFromPublic(\n privateKey: string,\n publicKey: string,\n ciphertextHex: string,\n): Uint8Array {\n const key = deriveTransportKey(privateKey, publicKey);\n const blob = decodeHex(ciphertextHex);\n if (blob.length < CHACHA_NONCE_LEN) {\n throw new Error('Wrapped secret is truncated');\n }\n return chacha20poly1305(key, blob.subarray(0, CHACHA_NONCE_LEN)).decrypt(\n blob.subarray(CHACHA_NONCE_LEN),\n );\n}\n\nfunction deriveTransportKey(privateKey: string, publicKey: string): Uint8Array {\n const privateBytes = privateKeyBytesFromJwk(privateKey);\n const publicBytes = publicKeyBytesFromJwk(publicKey);\n const shared = p256.getSharedSecret(privateBytes, publicBytes);\n // noble returns a compressed point (33 bytes). Cubist uses the 32-byte x-coordinate.\n const ikm = shared.slice(1, 33);\n return hkdf(sha256Sync, ikm, undefined, WRAP_INFO, CHACHA_KEY_LEN);\n}\n\nfunction privateKeyBytesFromJwk(privateKey: string): Uint8Array {\n const jwk = JSON.parse(privateKey) as { d?: string };\n if (typeof jwk.d !== 'string') {\n throw new Error('Invalid P-256 private JWK');\n }\n return fromBase64Url(jwk.d);\n}\n\nfunction publicKeyBytesFromJwk(publicKey: string): Uint8Array {\n const jwk = JSON.parse(publicKey) as EcPublicJwk;\n if (\n jwk.kty !== 'EC' ||\n jwk.crv !== 'P-256' ||\n typeof jwk.x !== 'string' ||\n typeof jwk.y !== 'string'\n ) {\n throw new Error('Invalid P-256 public JWK');\n }\n return concatBytes([\n new Uint8Array([0x04]),\n fromBase64Url(jwk.x),\n fromBase64Url(jwk.y),\n ]);\n}\n\n/**\n * Encodes bytes as unpadded base64url (JWK coordinate format).\n *\n * @param bytes - Raw coordinate or scalar.\n * @returns Base64url string.\n */\nfunction toBase64Url(bytes: Uint8Array): string {\n return bytesToBase64(bytes)\n .replace(/\\+/gu, '-')\n .replace(/\\//gu, '_')\n .replace(/[=]+$/u, '');\n}\n\n/**\n * @param length - Number of bytes to generate.\n * @returns CSPRNG bytes.\n */\nfunction randomBytes(length: number): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(length));\n}\n\n/**\n * Decodes unpadded base64url JWK coordinates.\n *\n * @param value - Base64url string.\n * @returns Raw bytes.\n */\nfunction fromBase64Url(value: string): Uint8Array {\n return base64ToBytes(\n value\n .replace(/-/gu, '+')\n .replace(/_/gu, '/')\n .padEnd(value.length + ((4 - (value.length % 4)) % 4), '='),\n );\n}\n\n/**\n * Recursively sorts keys and encodes Uint8Array as hex.\n *\n * @param value - Value to normalize.\n * @returns JSON-safe canonical form.\n */\nfunction sortKeys(value: unknown): unknown {\n if (value instanceof Uint8Array) {\n return bytesToHex(value);\n }\n if (Array.isArray(value)) {\n return value.map(sortKeys);\n }\n if (value !== null && typeof value === 'object') {\n return Object.fromEntries(\n Object.keys(value as Record<string, unknown>)\n .sort()\n .map((key) => [key, sortKeys((value as Record<string, unknown>)[key])]),\n );\n }\n return value;\n}\n"]}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Errors thrown by `MfaRecoveryController`.
3
+ */
4
+ export declare class MfaRecoveryError extends Error {
5
+ readonly code: string;
6
+ constructor(message: string, code: string);
7
+ }
8
+ /**
9
+ * Thrown when a mutation is not acknowledged by every configured escrow.
10
+ * Call `MfaRecoveryController.resume()` to retry the same mutation.
11
+ */
12
+ export declare class IncompleteMutationError extends MfaRecoveryError {
13
+ readonly mutationId: string;
14
+ constructor(mutationId: string);
15
+ }
16
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,YAAY,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAKxC;CACF;AAED;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,gBAAgB;IAC3D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,YAAY,UAAU,EAAE,MAAM,EAO7B;CACF"}
package/dist/errors.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Errors thrown by `MfaRecoveryController`.
3
+ */
4
+ export class MfaRecoveryError extends Error {
5
+ code;
6
+ constructor(message, code) {
7
+ super(message);
8
+ this.name = 'MfaRecoveryError';
9
+ this.code = code;
10
+ Object.setPrototypeOf(this, new.target.prototype);
11
+ }
12
+ }
13
+ /**
14
+ * Thrown when a mutation is not acknowledged by every configured escrow.
15
+ * Call `MfaRecoveryController.resume()` to retry the same mutation.
16
+ */
17
+ export class IncompleteMutationError extends MfaRecoveryError {
18
+ mutationId;
19
+ constructor(mutationId) {
20
+ super(`Mutation ${mutationId} is not acknowledged by every escrow`, 'incomplete_mutation');
21
+ this.name = 'IncompleteMutationError';
22
+ this.mutationId = mutationId;
23
+ }
24
+ }
25
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,IAAI,CAAS;IAEtB,YAAY,OAAe,EAAE,IAAY;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,uBAAwB,SAAQ,gBAAgB;IAClD,UAAU,CAAS;IAE5B,YAAY,UAAkB;QAC5B,KAAK,CACH,YAAY,UAAU,sCAAsC,EAC5D,qBAAqB,CACtB,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;QACtC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF","sourcesContent":["/**\n * Errors thrown by `MfaRecoveryController`.\n */\nexport class MfaRecoveryError extends Error {\n readonly code: string;\n\n constructor(message: string, code: string) {\n super(message);\n this.name = 'MfaRecoveryError';\n this.code = code;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Thrown when a mutation is not acknowledged by every configured escrow.\n * Call `MfaRecoveryController.resume()` to retry the same mutation.\n */\nexport class IncompleteMutationError extends MfaRecoveryError {\n readonly mutationId: string;\n\n constructor(mutationId: string) {\n super(\n `Mutation ${mutationId} is not acknowledged by every escrow`,\n 'incomplete_mutation',\n );\n this.name = 'IncompleteMutationError';\n this.mutationId = mutationId;\n }\n}\n"]}
@@ -0,0 +1,42 @@
1
+ import type { Mutation, MutationReceipt, RecoveryEscrowProvider } from './types.js';
2
+ export type RecoverySecretResult = {
3
+ escrowId: string;
4
+ recoverySecret: Uint8Array;
5
+ version: number;
6
+ lastMutationId: string;
7
+ };
8
+ /**
9
+ * Identifies a fulfilled promise result for TypeScript narrowing.
10
+ *
11
+ * @param result - Settled promise result.
12
+ * @returns Whether the result is fulfilled.
13
+ */
14
+ export declare function isFulfilledResult<Result>(result: PromiseSettledResult<Result>): result is PromiseFulfilledResult<Result>;
15
+ /**
16
+ * Checks the serialized shape of a mutation receipt.
17
+ *
18
+ * @param value - Decrypted or provider-returned value.
19
+ * @returns Whether the value has a mutation receipt shape.
20
+ */
21
+ export declare function isMutationReceipt(value: unknown): value is MutationReceipt;
22
+ /**
23
+ * Verifies a receipt against its expected target escrow.
24
+ *
25
+ * The provider checks the ECDSA receipt signature with its build-pinned
26
+ * receipt key; the target identity check is performed here as well.
27
+ *
28
+ * @param receipt - Receipt returned by an escrow.
29
+ * @param mutation - Mutation the receipt must acknowledge.
30
+ * @param escrow - Expected target escrow provider.
31
+ * @returns Whether the receipt is valid for the target.
32
+ */
33
+ export declare function verifyMutationReceipt(receipt: MutationReceipt, mutation: Mutation, escrow: RecoveryEscrowProvider): boolean;
34
+ /**
35
+ * Selects the highest consistent successful recovery-secret response.
36
+ *
37
+ * @param results - Settled escrow read results.
38
+ * @returns The selected recovery-secret response.
39
+ * @throws If no response succeeds or matching highest versions disagree.
40
+ */
41
+ export declare function selectHighestConsistentVersion(results: PromiseSettledResult<RecoverySecretResult>[]): Omit<RecoverySecretResult, 'escrowId'>;
42
+ //# sourceMappingURL=escrow-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escrow-utils.d.ts","sourceRoot":"","sources":["../src/escrow-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EACf,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,UAAU,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EACtC,MAAM,EAAE,oBAAoB,CAAC,MAAM,CAAC,GACnC,MAAM,IAAI,sBAAsB,CAAC,MAAM,CAAC,CAE1C;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAY1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAST;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,oBAAoB,CAAC,oBAAoB,CAAC,EAAE,GACpD,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,CA2BxC"}
@@ -0,0 +1,77 @@
1
+ import { areUint8ArraysEqual } from '@metamask/utils';
2
+ import { MfaRecoveryError } from './errors.js';
3
+ /**
4
+ * Identifies a fulfilled promise result for TypeScript narrowing.
5
+ *
6
+ * @param result - Settled promise result.
7
+ * @returns Whether the result is fulfilled.
8
+ */
9
+ export function isFulfilledResult(result) {
10
+ return result.status === 'fulfilled';
11
+ }
12
+ /**
13
+ * Checks the serialized shape of a mutation receipt.
14
+ *
15
+ * @param value - Decrypted or provider-returned value.
16
+ * @returns Whether the value has a mutation receipt shape.
17
+ */
18
+ export function isMutationReceipt(value) {
19
+ return (isRecord(value) &&
20
+ typeof value.mutationId === 'string' &&
21
+ typeof value.requestHash === 'string' &&
22
+ typeof value.escrowId === 'string' &&
23
+ typeof value.version === 'number' &&
24
+ Number.isInteger(value.version) &&
25
+ value.version >= 0 &&
26
+ typeof value.receiptKeyId === 'string' &&
27
+ typeof value.signature === 'string');
28
+ }
29
+ /**
30
+ * Verifies a receipt against its expected target escrow.
31
+ *
32
+ * The provider checks the ECDSA receipt signature with its build-pinned
33
+ * receipt key; the target identity check is performed here as well.
34
+ *
35
+ * @param receipt - Receipt returned by an escrow.
36
+ * @param mutation - Mutation the receipt must acknowledge.
37
+ * @param escrow - Expected target escrow provider.
38
+ * @returns Whether the receipt is valid for the target.
39
+ */
40
+ export function verifyMutationReceipt(receipt, mutation, escrow) {
41
+ if (receipt.escrowId !== escrow.id) {
42
+ return false;
43
+ }
44
+ try {
45
+ return escrow.verifyReceipt(receipt, mutation, escrow.id);
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ }
51
+ /**
52
+ * Selects the highest consistent successful recovery-secret response.
53
+ *
54
+ * @param results - Settled escrow read results.
55
+ * @returns The selected recovery-secret response.
56
+ * @throws If no response succeeds or matching highest versions disagree.
57
+ */
58
+ export function selectHighestConsistentVersion(results) {
59
+ const responses = results
60
+ .filter(isFulfilledResult)
61
+ .map((result) => result.value);
62
+ if (responses.length === 0) {
63
+ throw new MfaRecoveryError('No escrow returned a recovery secret', 'read_failed');
64
+ }
65
+ const highestVersion = Math.max(...responses.map((response) => response.version));
66
+ const highest = responses.filter((response) => response.version === highestVersion);
67
+ const selected = highest[0];
68
+ if (!highest.every((response) => response.lastMutationId === selected.lastMutationId &&
69
+ areUint8ArraysEqual(response.recoverySecret, selected.recoverySecret))) {
70
+ throw new MfaRecoveryError('Replica corruption', 'replica_corruption');
71
+ }
72
+ return selected;
73
+ }
74
+ function isRecord(value) {
75
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
76
+ }
77
+ //# sourceMappingURL=escrow-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escrow-utils.js","sourceRoot":"","sources":["../src/escrow-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAc/C;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAoC;IAEpC,OAAO,MAAM,CAAC,MAAM,KAAK,WAAW,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,CACL,QAAQ,CAAC,KAAK,CAAC;QACf,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ;QACpC,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ;QACrC,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAClC,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;QACjC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC;QAC/B,KAAK,CAAC,OAAO,IAAI,CAAC;QAClB,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ;QACtC,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,CACpC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAwB,EACxB,QAAkB,EAClB,MAA8B;IAE9B,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,8BAA8B,CAC5C,OAAqD;IAErD,MAAM,SAAS,GAAG,OAAO;SACtB,MAAM,CAAC,iBAAiB,CAAC;SACzB,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,gBAAgB,CACxB,sCAAsC,EACtC,aAAa,CACd,CAAC;IACJ,CAAC;IACD,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAC7B,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CACjD,CAAC;IACF,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAC9B,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,KAAK,cAAc,CAClD,CAAC;IACF,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5B,IACE,CAAC,OAAO,CAAC,KAAK,CACZ,CAAC,QAAQ,EAAE,EAAE,CACX,QAAQ,CAAC,cAAc,KAAK,QAAQ,CAAC,cAAc;QACnD,mBAAmB,CAAC,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,CACxE,EACD,CAAC;QACD,MAAM,IAAI,gBAAgB,CAAC,oBAAoB,EAAE,oBAAoB,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import { areUint8ArraysEqual } from '@metamask/utils';\n\nimport { MfaRecoveryError } from './errors.js';\nimport type {\n Mutation,\n MutationReceipt,\n RecoveryEscrowProvider,\n} from './types.js';\n\nexport type RecoverySecretResult = {\n escrowId: string;\n recoverySecret: Uint8Array;\n version: number;\n lastMutationId: string;\n};\n\n/**\n * Identifies a fulfilled promise result for TypeScript narrowing.\n *\n * @param result - Settled promise result.\n * @returns Whether the result is fulfilled.\n */\nexport function isFulfilledResult<Result>(\n result: PromiseSettledResult<Result>,\n): result is PromiseFulfilledResult<Result> {\n return result.status === 'fulfilled';\n}\n\n/**\n * Checks the serialized shape of a mutation receipt.\n *\n * @param value - Decrypted or provider-returned value.\n * @returns Whether the value has a mutation receipt shape.\n */\nexport function isMutationReceipt(value: unknown): value is MutationReceipt {\n return (\n isRecord(value) &&\n typeof value.mutationId === 'string' &&\n typeof value.requestHash === 'string' &&\n typeof value.escrowId === 'string' &&\n typeof value.version === 'number' &&\n Number.isInteger(value.version) &&\n value.version >= 0 &&\n typeof value.receiptKeyId === 'string' &&\n typeof value.signature === 'string'\n );\n}\n\n/**\n * Verifies a receipt against its expected target escrow.\n *\n * The provider checks the ECDSA receipt signature with its build-pinned\n * receipt key; the target identity check is performed here as well.\n *\n * @param receipt - Receipt returned by an escrow.\n * @param mutation - Mutation the receipt must acknowledge.\n * @param escrow - Expected target escrow provider.\n * @returns Whether the receipt is valid for the target.\n */\nexport function verifyMutationReceipt(\n receipt: MutationReceipt,\n mutation: Mutation,\n escrow: RecoveryEscrowProvider,\n): boolean {\n if (receipt.escrowId !== escrow.id) {\n return false;\n }\n try {\n return escrow.verifyReceipt(receipt, mutation, escrow.id);\n } catch {\n return false;\n }\n}\n\n/**\n * Selects the highest consistent successful recovery-secret response.\n *\n * @param results - Settled escrow read results.\n * @returns The selected recovery-secret response.\n * @throws If no response succeeds or matching highest versions disagree.\n */\nexport function selectHighestConsistentVersion(\n results: PromiseSettledResult<RecoverySecretResult>[],\n): Omit<RecoverySecretResult, 'escrowId'> {\n const responses = results\n .filter(isFulfilledResult)\n .map((result) => result.value);\n if (responses.length === 0) {\n throw new MfaRecoveryError(\n 'No escrow returned a recovery secret',\n 'read_failed',\n );\n }\n const highestVersion = Math.max(\n ...responses.map((response) => response.version),\n );\n const highest = responses.filter(\n (response) => response.version === highestVersion,\n );\n const selected = highest[0];\n if (\n !highest.every(\n (response) =>\n response.lastMutationId === selected.lastMutationId &&\n areUint8ArraysEqual(response.recoverySecret, selected.recoverySecret),\n )\n ) {\n throw new MfaRecoveryError('Replica corruption', 'replica_corruption');\n }\n return selected;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n"]}
@@ -0,0 +1,83 @@
1
+ import type { Identifier, IdentifierAuthMode, IdentifierAuthorization, KeyBoundIdentifierToken, RecoveryEscrowProvider, RecoveryIdentifierAuthProvider } from './types.js';
2
+ /**
3
+ * Minimum identifier count for register and updateIdentifiers.
4
+ */
5
+ export declare const MIN_IDENTIFIERS = 2;
6
+ /**
7
+ * Distinct identifier identities (`type` + `namespace` + `value`).
8
+ *
9
+ * @param identifiers - Identifier list.
10
+ * @returns Distinct identity count.
11
+ */
12
+ export declare function countDistinctIdentifiers(identifiers: Identifier[]): number;
13
+ /**
14
+ * Trusted identifier-type registry. Escrows and the controller derive the
15
+ * authentication mode from this table, never from a client-selected flag.
16
+ *
17
+ * TODO: MFA-605 / MFA-606 / MFA-568 — add `emailOtp` / `smsOtp` as
18
+ * `escrow-challenge` once escrow OTP commands exist.
19
+ */
20
+ export declare const IDENTIFIER_AUTH_MODES: Record<string, IdentifierAuthMode>;
21
+ export type AuthorizedEscrow = {
22
+ escrow: RecoveryEscrowProvider;
23
+ authorization: IdentifierAuthorization;
24
+ };
25
+ /**
26
+ * Requests a key-bound identifier token and returns the private key used to
27
+ * prove possession of it.
28
+ *
29
+ * @param params - Identifier authorization parameters.
30
+ * @param params.identifier - Identifier to authenticate.
31
+ * @param params.requestHash - Request hash bound to the authorization.
32
+ * @param params.identifierAuthProvider - Provider for key-bound assertions.
33
+ * @returns Key-bound identifier token and its proof private key.
34
+ */
35
+ export declare function requestKeyBoundIdentifierToken({ identifier, requestHash, identifierAuthProvider, }: {
36
+ identifier: Identifier;
37
+ requestHash: string;
38
+ identifierAuthProvider: RecoveryIdentifierAuthProvider;
39
+ }): Promise<{
40
+ token: KeyBoundIdentifierToken;
41
+ proofPrivateKey: string;
42
+ }>;
43
+ /**
44
+ * Authorizes a key-bound identifier with each escrow that successfully
45
+ * provides a challenge.
46
+ *
47
+ * @param params - Key-bound authorization parameters.
48
+ * @param params.escrows - Escrows to authorize.
49
+ * @param params.token - Key-bound identifier token.
50
+ * @param params.proofPrivateKey - Private key for proving token possession.
51
+ * @returns Successful escrow authorizations.
52
+ */
53
+ export declare function authorizeWithKeyBoundToken({ escrows, token, proofPrivateKey, }: {
54
+ escrows: RecoveryEscrowProvider[];
55
+ token: KeyBoundIdentifierToken;
56
+ proofPrivateKey: string;
57
+ }): Promise<AuthorizedEscrow[]>;
58
+ /**
59
+ * Requests and authorizes a key-bound identifier with each escrow that
60
+ * successfully provides a challenge.
61
+ *
62
+ * @param params - Identifier authorization parameters.
63
+ * @param params.escrows - Escrows to authorize.
64
+ * @param params.identifier - Identifier to authorize.
65
+ * @param params.requestHash - Request hash bound to the authorization.
66
+ * @param params.identifierAuthProvider - Provider for key-bound assertions.
67
+ * @returns Successful escrow authorizations.
68
+ */
69
+ export declare function authorizeKeyBoundIdentifier({ escrows, identifier, requestHash, identifierAuthProvider, }: {
70
+ escrows: RecoveryEscrowProvider[];
71
+ identifier: Identifier;
72
+ requestHash: string;
73
+ identifierAuthProvider: RecoveryIdentifierAuthProvider;
74
+ }): Promise<AuthorizedEscrow[]>;
75
+ /**
76
+ * Returns the auth mode for an identifier type.
77
+ *
78
+ * @param type - Identifier type.
79
+ * @returns Auth mode.
80
+ * @throws If the type is not in the trusted registry.
81
+ */
82
+ export declare function getIdentifierAuthMode(type: string): IdentifierAuthMode;
83
+ //# sourceMappingURL=identifier-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identifier-auth.d.ts","sourceRoot":"","sources":["../src/identifier-auth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,8BAA8B,EAC/B,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,MAAM,CAM1E;AAED;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAIpE,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,sBAAsB,CAAC;IAC/B,aAAa,EAAE,uBAAuB,CAAC;CACxC,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAsB,8BAA8B,CAAC,EACnD,UAAU,EACV,WAAW,EACX,sBAAsB,GACvB,EAAE;IACD,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,sBAAsB,EAAE,8BAA8B,CAAC;CACxD,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,uBAAuB,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC,CAQvE;AAED;;;;;;;;;GASG;AACH,wBAAsB,0BAA0B,CAAC,EAC/C,OAAO,EACP,KAAK,EACL,eAAe,GAChB,EAAE;IACD,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAClC,KAAK,EAAE,uBAAuB,CAAC;IAC/B,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA6B9B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,2BAA2B,CAAC,EAChD,OAAO,EACP,UAAU,EACV,WAAW,EACX,sBAAsB,GACvB,EAAE;IACD,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAClC,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,sBAAsB,EAAE,8BAA8B,CAAC;CACxD,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAW9B;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAStE"}