@enbox/dids 0.0.6 → 0.0.8

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 (40) hide show
  1. package/LICENSE +3 -2
  2. package/dist/browser.mjs +1 -1
  3. package/dist/browser.mjs.map +3 -3
  4. package/dist/esm/did.js +4 -0
  5. package/dist/esm/did.js.map +1 -1
  6. package/dist/esm/methods/did-dht-pkarr.js +3 -2
  7. package/dist/esm/methods/did-dht-pkarr.js.map +1 -1
  8. package/dist/esm/methods/did-ion.js +3 -2
  9. package/dist/esm/methods/did-ion.js.map +1 -1
  10. package/dist/esm/methods/did-web.js +64 -1
  11. package/dist/esm/methods/did-web.js.map +1 -1
  12. package/dist/esm/resolver/resolver-cache-level.js +9 -0
  13. package/dist/esm/resolver/resolver-cache-level.js.map +1 -1
  14. package/dist/esm/resolver/resolver-cache-memory.js +8 -0
  15. package/dist/esm/resolver/resolver-cache-memory.js.map +1 -1
  16. package/dist/esm/resolver/resolver-cache-noop.js +3 -0
  17. package/dist/esm/resolver/resolver-cache-noop.js.map +1 -1
  18. package/dist/esm/resolver/universal-resolver.js +18 -0
  19. package/dist/esm/resolver/universal-resolver.js.map +1 -1
  20. package/dist/types/did.d.ts.map +1 -1
  21. package/dist/types/methods/did-dht-pkarr.d.ts.map +1 -1
  22. package/dist/types/methods/did-ion.d.ts.map +1 -1
  23. package/dist/types/methods/did-web.d.ts.map +1 -1
  24. package/dist/types/resolver/resolver-cache-level.d.ts +7 -0
  25. package/dist/types/resolver/resolver-cache-level.d.ts.map +1 -1
  26. package/dist/types/resolver/resolver-cache-memory.d.ts +4 -0
  27. package/dist/types/resolver/resolver-cache-memory.d.ts.map +1 -1
  28. package/dist/types/resolver/resolver-cache-noop.d.ts.map +1 -1
  29. package/dist/types/resolver/universal-resolver.d.ts +10 -0
  30. package/dist/types/resolver/universal-resolver.d.ts.map +1 -1
  31. package/dist/utils.js.map +1 -1
  32. package/package.json +3 -3
  33. package/src/did.ts +3 -0
  34. package/src/methods/did-dht-pkarr.ts +3 -2
  35. package/src/methods/did-ion.ts +3 -2
  36. package/src/methods/did-web.ts +52 -1
  37. package/src/resolver/resolver-cache-level.ts +10 -0
  38. package/src/resolver/resolver-cache-memory.ts +7 -0
  39. package/src/resolver/resolver-cache-noop.ts +3 -0
  40. package/src/resolver/universal-resolver.ts +16 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enbox/dids",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "TBD DIDs library",
5
5
  "type": "module",
6
6
  "main": "./dist/esm/index.js",
@@ -80,8 +80,8 @@
80
80
  "dependencies": {
81
81
  "@decentralized-identity/ion-sdk": "1.0.4",
82
82
  "@dnsquery/dns-packet": "6.1.1",
83
- "@enbox/common": "0.0.4",
84
- "@enbox/crypto": "0.0.5",
83
+ "@enbox/common": "0.0.6",
84
+ "@enbox/crypto": "0.0.7",
85
85
  "abstract-level": "1.0.4",
86
86
  "bencode": "4.0.0",
87
87
  "level": "8.0.1",
package/src/did.ts CHANGED
@@ -145,6 +145,9 @@ export class Did {
145
145
  // Return null if the input string is empty or not provided.
146
146
  if (!didUri) {return null;}
147
147
 
148
+ // Guard against ReDoS: reject unreasonably long URIs before executing the regex.
149
+ if (didUri.length > 2048) {return null;}
150
+
148
151
  // Execute the regex pattern on the input string to extract URI components.
149
152
  const match = Did.DID_URI_PATTERN.exec(didUri);
150
153
 
@@ -44,7 +44,7 @@ export async function pkarrGet({ gatewayUri, publicKeyBytes }: {
44
44
  // Transmit the Get request to the DID DHT Gateway or Pkarr Relay and get the response.
45
45
  let response: Response;
46
46
  try {
47
- response = await fetch(url, { method: 'GET' });
47
+ response = await fetch(url, { method: 'GET', signal: AbortSignal.timeout(30_000) });
48
48
 
49
49
  if (!response.ok) {
50
50
  throw new DidError(DidErrorCode.NotFound, `Pkarr record not found for: ${identifier}`);
@@ -113,7 +113,8 @@ export async function pkarrPut({ gatewayUri, bep44Message }: {
113
113
  response = await fetch(url, {
114
114
  method : 'PUT',
115
115
  headers : { 'Content-Type': 'application/octet-stream' },
116
- body
116
+ body,
117
+ signal : AbortSignal.timeout(30_000),
117
118
  });
118
119
 
119
120
  } catch (error: any) {
@@ -600,7 +600,8 @@ export class DidIon extends DidMethod {
600
600
  method : 'POST',
601
601
  mode : 'cors',
602
602
  headers : { 'Content-Type': 'application/json' },
603
- body : JSON.stringify(createOperation)
603
+ body : JSON.stringify(createOperation),
604
+ signal : AbortSignal.timeout(30_000),
604
605
  });
605
606
 
606
607
  // Return the result of processing the Create operation, including the updated DID metadata
@@ -681,7 +682,7 @@ export class DidIon extends DidMethod {
681
682
  });
682
683
 
683
684
  // Attempt to retrieve the DID document and metadata from the Sidetree node.
684
- const response = await fetch(resolutionUrl);
685
+ const response = await fetch(resolutionUrl, { signal: AbortSignal.timeout(30_000) });
685
686
 
686
687
  // If the DID document was not found, return an error.
687
688
  if (!response.ok) {
@@ -4,6 +4,46 @@ import { Did } from '../did.js';
4
4
  import { DidMethod } from './did-method.js';
5
5
  import { EMPTY_DID_RESOLUTION_RESULT } from '../types/did-resolution.js';
6
6
 
7
+ /** Default fetch timeout for DID document retrieval (30 seconds). */
8
+ const FETCH_TIMEOUT_MS = 30_000;
9
+
10
+ /**
11
+ * Returns `true` when the hostname is a private, loopback, or link-local
12
+ * address. Used to block SSRF via crafted `did:web` identifiers such as
13
+ * `did:web:169.254.169.254` or `did:web:localhost`.
14
+ */
15
+ function isPrivateHostname(hostname: string): boolean {
16
+ const h = hostname.toLowerCase();
17
+
18
+ if (h === 'localhost' || h === 'localhost.') { return true; }
19
+
20
+ // IPv4 literal check
21
+ const parts = h.split('.');
22
+ if (parts.length === 4) {
23
+ const octets = parts.map(Number);
24
+ if (octets.every((o) => !Number.isNaN(o) && o >= 0 && o <= 255)) {
25
+ const [a, b] = octets;
26
+ if (a === 10) { return true; } // 10.0.0.0/8
27
+ if (a === 172 && b >= 16 && b <= 31) { return true; } // 172.16.0.0/12
28
+ if (a === 192 && b === 168) { return true; } // 192.168.0.0/16
29
+ if (a === 127) { return true; } // 127.0.0.0/8
30
+ if (a === 169 && b === 254) { return true; } // 169.254.0.0/16
31
+ if (a === 0) { return true; } // 0.0.0.0/8
32
+ }
33
+ }
34
+
35
+ // IPv6 literal check (bracket-wrapped by URL parser)
36
+ let v6 = h;
37
+ if (v6.startsWith('[') && v6.endsWith(']')) { v6 = v6.slice(1, -1); }
38
+ if (v6.includes(':')) {
39
+ if (v6 === '::1' || v6 === '::' || v6 === '::0') { return true; }
40
+ if (v6.startsWith('fe80:') || v6.startsWith('fe80%')) { return true; }
41
+ if (v6.startsWith('fc') || v6.startsWith('fd')) { return true; }
42
+ }
43
+
44
+ return false;
45
+ }
46
+
7
47
  /**
8
48
  * The `DidWeb` class provides an implementation of the `did:web` DID method.
9
49
  *
@@ -71,8 +111,19 @@ export class DidWeb extends DidMethod {
71
111
  `${baseUrl}/.well-known/did.json`;
72
112
 
73
113
  try {
114
+ // Block requests to private/loopback/link-local addresses (SSRF protection).
115
+ const parsedUrl = new URL(didDocumentUrl);
116
+ if (isPrivateHostname(parsedUrl.hostname)) {
117
+ return {
118
+ ...EMPTY_DID_RESOLUTION_RESULT,
119
+ didResolutionMetadata: { error: 'notFound' }
120
+ };
121
+ }
122
+
74
123
  // Perform an HTTP GET request to obtain the DID document.
75
- const response = await fetch(didDocumentUrl);
124
+ const response = await fetch(didDocumentUrl, {
125
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
126
+ });
76
127
 
77
128
  // If the response status code is not 200, return an error.
78
129
  if (!response.ok) {throw new Error('HTTP error status code returned');}
@@ -87,6 +87,16 @@ export class DidResolverCacheLevel implements DidResolverCache {
87
87
  this.ttl = ms(ttl);
88
88
  }
89
89
 
90
+ /**
91
+ * Opens the underlying LevelDB store.
92
+ * Calling `open()` on an already-open store is a safe no-op.
93
+ *
94
+ * @returns A promise that resolves when the store is ready for use.
95
+ */
96
+ open(): Promise<void> {
97
+ return this.cache.open();
98
+ }
99
+
90
100
  /**
91
101
  * Retrieves a DID resolution result from the cache.
92
102
  *
@@ -26,6 +26,13 @@ export class DidResolverCacheMemory implements DidResolverCache {
26
26
  this.cache = new TtlCache({ ttl: ms(ttl) });
27
27
  }
28
28
 
29
+ /**
30
+ * This method is a no-op since in-memory stores are always ready.
31
+ */
32
+ public async open(): Promise<void> {
33
+ // No-op since there is no underlying store to open.
34
+ }
35
+
29
36
  /**
30
37
  * Retrieves a DID resolution result from the cache.
31
38
  *
@@ -8,6 +8,9 @@ import type { DidResolverCache } from '../types/did-resolution.js';
8
8
  * potential for this library to be used in as many JS runtimes as possible.
9
9
  */
10
10
  export const DidResolverCacheNoop: DidResolverCache = {
11
+ open(): Promise<void> {
12
+ return Promise.resolve();
13
+ },
11
14
  get(_key: string): Promise<DidResolutionResult | void> {
12
15
  return Promise.resolve(undefined);
13
16
  },
@@ -86,6 +86,22 @@ export class UniversalResolver implements DidResolver, DidUrlDereferencer {
86
86
  }
87
87
  }
88
88
 
89
+ /**
90
+ * Opens the resolver's cache, acquiring any resources needed.
91
+ * Must be called before resolving DIDs if using a cache that requires initialization (e.g., LevelDB).
92
+ */
93
+ public async open(): Promise<void> {
94
+ await this.cache.open();
95
+ }
96
+
97
+ /**
98
+ * Closes the resolver's cache, releasing any resources held.
99
+ * Should be called during application shutdown.
100
+ */
101
+ public async close(): Promise<void> {
102
+ await this.cache.close();
103
+ }
104
+
89
105
  /**
90
106
  * Resolves a DID to a DID Resolution Result.
91
107
  *