@oxy.so/protocol 1.0.0
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/LICENSE +202 -0
- package/NOTICE +16 -0
- package/dist/cjs/.tsbuildinfo +1 -0
- package/dist/cjs/chain/continuity.js +54 -0
- package/dist/cjs/chain/engine.js +34 -0
- package/dist/cjs/chain/recordStore.js +25 -0
- package/dist/cjs/chain/types.js +22 -0
- package/dist/cjs/chain/verify.js +82 -0
- package/dist/cjs/envelope/canonicalJson.js +107 -0
- package/dist/cjs/envelope/recordId.js +60 -0
- package/dist/cjs/envelope/sign.js +75 -0
- package/dist/cjs/envelope/signingInput.js +32 -0
- package/dist/cjs/identity/resolver.js +50 -0
- package/dist/cjs/index.js +71 -0
- package/dist/cjs/node/constants.js +85 -0
- package/dist/cjs/node/didWebResolver.js +126 -0
- package/dist/cjs/node/httpFetch.js +61 -0
- package/dist/cjs/node/index.js +71 -0
- package/dist/cjs/node/nodeApp.js +344 -0
- package/dist/cjs/node/nodeClient.js +204 -0
- package/dist/cjs/node/rateLimit.js +187 -0
- package/dist/cjs/node/verifyRecord.js +51 -0
- package/dist/cjs/platform/crypto.js +186 -0
- package/dist/cjs/platform/crypto.native.js +204 -0
- package/dist/cjs/platform/expoTypes.js +24 -0
- package/dist/cjs/platform/platform.js +33 -0
- package/dist/cjs/secp256k1.js +148 -0
- package/dist/cjs/transparency/checkpoint.js +79 -0
- package/dist/cjs/transparency/tree.js +197 -0
- package/dist/esm/.tsbuildinfo +1 -0
- package/dist/esm/chain/continuity.js +51 -0
- package/dist/esm/chain/engine.js +31 -0
- package/dist/esm/chain/recordStore.js +24 -0
- package/dist/esm/chain/types.js +19 -0
- package/dist/esm/chain/verify.js +78 -0
- package/dist/esm/envelope/canonicalJson.js +104 -0
- package/dist/esm/envelope/recordId.js +56 -0
- package/dist/esm/envelope/sign.js +69 -0
- package/dist/esm/envelope/signingInput.js +29 -0
- package/dist/esm/identity/resolver.js +47 -0
- package/dist/esm/index.js +36 -0
- package/dist/esm/node/constants.js +82 -0
- package/dist/esm/node/didWebResolver.js +122 -0
- package/dist/esm/node/httpFetch.js +55 -0
- package/dist/esm/node/index.js +28 -0
- package/dist/esm/node/nodeApp.js +336 -0
- package/dist/esm/node/nodeClient.js +198 -0
- package/dist/esm/node/rateLimit.js +182 -0
- package/dist/esm/node/verifyRecord.js +48 -0
- package/dist/esm/platform/crypto.js +145 -0
- package/dist/esm/platform/crypto.native.js +196 -0
- package/dist/esm/platform/expoTypes.js +23 -0
- package/dist/esm/platform/platform.js +29 -0
- package/dist/esm/secp256k1.js +137 -0
- package/dist/esm/transparency/checkpoint.js +73 -0
- package/dist/esm/transparency/tree.js +189 -0
- package/dist/types/.tsbuildinfo +1 -0
- package/dist/types/chain/continuity.d.ts +28 -0
- package/dist/types/chain/engine.d.ts +27 -0
- package/dist/types/chain/recordStore.d.ts +85 -0
- package/dist/types/chain/types.d.ts +79 -0
- package/dist/types/chain/verify.d.ts +45 -0
- package/dist/types/envelope/canonicalJson.d.ts +44 -0
- package/dist/types/envelope/recordId.d.ts +30 -0
- package/dist/types/envelope/sign.d.ts +47 -0
- package/dist/types/envelope/signingInput.d.ts +33 -0
- package/dist/types/identity/resolver.d.ts +67 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/node/constants.d.ts +80 -0
- package/dist/types/node/didWebResolver.d.ts +47 -0
- package/dist/types/node/httpFetch.d.ts +60 -0
- package/dist/types/node/index.d.ts +28 -0
- package/dist/types/node/nodeApp.d.ts +120 -0
- package/dist/types/node/nodeClient.d.ts +135 -0
- package/dist/types/node/rateLimit.d.ts +95 -0
- package/dist/types/node/verifyRecord.d.ts +41 -0
- package/dist/types/platform/crypto.d.ts +93 -0
- package/dist/types/platform/crypto.native.d.ts +77 -0
- package/dist/types/platform/expoTypes.d.ts +99 -0
- package/dist/types/platform/platform.d.ts +25 -0
- package/dist/types/secp256k1.d.ts +45 -0
- package/dist/types/transparency/checkpoint.d.ts +71 -0
- package/dist/types/transparency/tree.d.ts +135 -0
- package/package.json +157 -0
- package/src/__tests__/canonicalJson.test.ts +116 -0
- package/src/__tests__/chain.test.ts +279 -0
- package/src/__tests__/didWebResolver.test.ts +132 -0
- package/src/__tests__/envelope.test.ts +267 -0
- package/src/__tests__/nodeApp.test.ts +410 -0
- package/src/__tests__/nodeClient.test.ts +177 -0
- package/src/__tests__/nodeHarness.ts +151 -0
- package/src/__tests__/optionalNativePeers.test.ts +233 -0
- package/src/__tests__/rateLimit.test.ts +268 -0
- package/src/__tests__/runnerGuard.test.ts +85 -0
- package/src/__tests__/secp256k1.test.ts +118 -0
- package/src/__tests__/transparency.test.ts +353 -0
- package/src/chain/continuity.ts +59 -0
- package/src/chain/engine.ts +43 -0
- package/src/chain/recordStore.ts +98 -0
- package/src/chain/types.ts +85 -0
- package/src/chain/verify.ts +102 -0
- package/src/envelope/canonicalJson.ts +120 -0
- package/src/envelope/recordId.ts +63 -0
- package/src/envelope/sign.ts +86 -0
- package/src/envelope/signingInput.ts +48 -0
- package/src/identity/resolver.ts +90 -0
- package/src/index.ts +101 -0
- package/src/node/constants.ts +105 -0
- package/src/node/didWebResolver.ts +162 -0
- package/src/node/httpFetch.ts +88 -0
- package/src/node/index.ts +87 -0
- package/src/node/nodeApp.ts +471 -0
- package/src/node/nodeClient.ts +322 -0
- package/src/node/rateLimit.ts +233 -0
- package/src/node/verifyRecord.ts +60 -0
- package/src/platform/crypto.native.ts +251 -0
- package/src/platform/crypto.ts +172 -0
- package/src/platform/expoTypes.ts +99 -0
- package/src/platform/platform.ts +31 -0
- package/src/secp256k1.ts +207 -0
- package/src/transparency/checkpoint.ts +109 -0
- package/src/transparency/tree.ts +258 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Node-protocol shape constants — the wire-level contract of an Oxy-protocol
|
|
4
|
+
* data node, shared by the generic node app factory ({@link ./nodeApp}), the
|
|
5
|
+
* HTTP {@link ./nodeClient.NodeClient}, and any app's node deployment
|
|
6
|
+
* (`@oxy.so/node`, a future `mention-node`).
|
|
7
|
+
*
|
|
8
|
+
* These were previously hardcoded inside `@oxy.so/node`; they live here so the
|
|
9
|
+
* SAME values drive a server and a client without either side re-declaring (and
|
|
10
|
+
* drifting) the contract. Deployment-specific knobs (owner key, port, data dir)
|
|
11
|
+
* are still resolved per-deployment from the environment — only the protocol's
|
|
12
|
+
* own shape constants live here.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.DEFAULT_DID_DOC_MAX_BYTES = exports.DEFAULT_WRITE_RESPONSE_MAX_BYTES = exports.DEFAULT_LOG_MAX_BYTES = exports.DEFAULT_HEAD_MAX_BYTES = exports.DEFAULT_CLIENT_MAX_REDIRECTS = exports.DEFAULT_CLIENT_TIMEOUT_MS = exports.NODE_BLOBS_PATH = exports.NODE_SYNC_PUSH_PATH = exports.NODE_RECORDS_PATH = exports.NODE_LOG_PATH = exports.NODE_HEAD_PATH = exports.SHA256_HEX = exports.OWNER_ACTION_BLOB_PIN = exports.NODE_MODES = exports.OWNER_AUTH_MAX_AGE_MS = exports.OWNER_AUTH_HEADERS = exports.JSON_BODY_LIMIT = exports.MAX_SYNC_BATCH = exports.DEFAULT_MAX_BLOB_BYTES = exports.MAX_LOG_LIMIT = exports.DEFAULT_LOG_LIMIT = exports.DEFAULT_PORT = exports.DEFAULT_APP_NAMESPACE = exports.DEFAULT_SERVICE_TYPE = exports.DEFAULT_WELL_KNOWN_PATH = exports.PROTOCOL_VERSION = void 0;
|
|
16
|
+
/**
|
|
17
|
+
* The node-protocol version advertised at the well-known manifest. Bumped only
|
|
18
|
+
* on a breaking change to the wire shape of the log / head / record APIs. This
|
|
19
|
+
* is the DEFAULT `protocolId` a deployment advertises (overridable per app).
|
|
20
|
+
*/
|
|
21
|
+
exports.PROTOCOL_VERSION = 'oxy-node/1';
|
|
22
|
+
/** Default well-known manifest path (the existing `@oxy.so/node` value). */
|
|
23
|
+
exports.DEFAULT_WELL_KNOWN_PATH = '/.well-known/oxy-node.json';
|
|
24
|
+
/** Default DID-document service-type label advertised by a node deployment. */
|
|
25
|
+
exports.DEFAULT_SERVICE_TYPE = 'OxyPersonalDataNode';
|
|
26
|
+
/**
|
|
27
|
+
* Default application namespace a node deployment serves. The records a node
|
|
28
|
+
* stores all live under this namespace (e.g. `app.oxy.*`); a `collections`
|
|
29
|
+
* allowlist (when set) MUST be within it.
|
|
30
|
+
*/
|
|
31
|
+
exports.DEFAULT_APP_NAMESPACE = 'app.oxy';
|
|
32
|
+
/** Default HTTP port when the port env var is unset (always overridable). */
|
|
33
|
+
exports.DEFAULT_PORT = 4000;
|
|
34
|
+
/** Default and maximum number of log entries returned by `GET /oxy/log`. */
|
|
35
|
+
exports.DEFAULT_LOG_LIMIT = 100;
|
|
36
|
+
exports.MAX_LOG_LIMIT = 500;
|
|
37
|
+
/** Default upper bound on a single pinned blob's size when unset (25 MiB). */
|
|
38
|
+
exports.DEFAULT_MAX_BLOB_BYTES = 25 * 1024 * 1024;
|
|
39
|
+
/** Maximum number of envelopes accepted in one `POST /sync/push` batch. */
|
|
40
|
+
exports.MAX_SYNC_BATCH = 200;
|
|
41
|
+
/** Body-size ceiling for JSON request bodies (`/records`, `/sync/push`). */
|
|
42
|
+
exports.JSON_BODY_LIMIT = '5mb';
|
|
43
|
+
/** HTTP headers carrying an owner-signed action authorization (blob pins). */
|
|
44
|
+
exports.OWNER_AUTH_HEADERS = {
|
|
45
|
+
publicKey: 'x-oxy-node-public-key',
|
|
46
|
+
signature: 'x-oxy-node-signature',
|
|
47
|
+
timestamp: 'x-oxy-node-timestamp',
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Freshness window for an owner-signed action (e.g. a blob pin). A signed
|
|
51
|
+
* authorization header older/newer than this (accounting for clock skew) is
|
|
52
|
+
* rejected, bounding replay of a captured pin authorization.
|
|
53
|
+
*/
|
|
54
|
+
exports.OWNER_AUTH_MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
55
|
+
/** Operating modes a node can advertise. */
|
|
56
|
+
exports.NODE_MODES = ['self-hosted', 'managed'];
|
|
57
|
+
/** The node operation an owner can authorize with a signed header. */
|
|
58
|
+
exports.OWNER_ACTION_BLOB_PIN = 'blob-pin';
|
|
59
|
+
/** A 32-byte (64 hex char) lowercase SHA-256 digest, used as the blob address. */
|
|
60
|
+
exports.SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
61
|
+
/* -------------------------------------------------------------------------- */
|
|
62
|
+
/* HTTP NodeClient — the node-facing routes a client drives */
|
|
63
|
+
/* -------------------------------------------------------------------------- */
|
|
64
|
+
/** Chain head endpoint (`GET`). */
|
|
65
|
+
exports.NODE_HEAD_PATH = '/oxy/head';
|
|
66
|
+
/** Ordered log endpoint (`GET ?since=&limit=`). */
|
|
67
|
+
exports.NODE_LOG_PATH = '/oxy/log';
|
|
68
|
+
/** Single-record write endpoint (`POST`, owner-signed envelope). */
|
|
69
|
+
exports.NODE_RECORDS_PATH = '/records';
|
|
70
|
+
/** Batch push endpoint (`POST`, owner-signed envelopes). */
|
|
71
|
+
exports.NODE_SYNC_PUSH_PATH = '/sync/push';
|
|
72
|
+
/** Content-addressed blob endpoint prefix (`GET|PUT /blobs/:hash`). */
|
|
73
|
+
exports.NODE_BLOBS_PATH = '/blobs';
|
|
74
|
+
/** Default time-to-first-byte deadline for a NodeClient request (ms). */
|
|
75
|
+
exports.DEFAULT_CLIENT_TIMEOUT_MS = 8000;
|
|
76
|
+
/** Default redirect budget for a NodeClient request (each re-validated upstream). */
|
|
77
|
+
exports.DEFAULT_CLIENT_MAX_REDIRECTS = 1;
|
|
78
|
+
/** Default bounded read for a `/oxy/head` response (tiny JSON). */
|
|
79
|
+
exports.DEFAULT_HEAD_MAX_BYTES = 64 * 1024;
|
|
80
|
+
/** Default bounded read for a `/oxy/log` page response. */
|
|
81
|
+
exports.DEFAULT_LOG_MAX_BYTES = 2 * 1024 * 1024;
|
|
82
|
+
/** Default bounded read for a small JSON write response (`/records`, blob pin). */
|
|
83
|
+
exports.DEFAULT_WRITE_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
84
|
+
/** Default bounded read for a fetched `<did>.json` document. */
|
|
85
|
+
exports.DEFAULT_DID_DOC_MAX_BYTES = 256 * 1024;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `createDidWebResolver` — a {@link VerificationMethodResolver} for arbitrary
|
|
4
|
+
* `did:web` subjects, backing the chain engine on multi-subject relay/ingest
|
|
5
|
+
* paths (where the signer is NOT a local account but any DID that publishes a
|
|
6
|
+
* DID document).
|
|
7
|
+
*
|
|
8
|
+
* It resolves a `did:web:<host>[:<path>]` subject to its `did.json`, fetched via
|
|
9
|
+
* an INJECTED {@link NodeFetch} — Oxy passes an adapter over `@oxy.so/core/server`
|
|
10
|
+
* `safeFetch` (so the SSRF/transport policy stays in core); a test passes a
|
|
11
|
+
* stub. The subject's current verification keys are read from the DID document's
|
|
12
|
+
* `verificationMethod[].publicKeyHex` (the active assertion methods), and the
|
|
13
|
+
* engine's `isAuthorizedKey` then applies the uniform self-issued rule.
|
|
14
|
+
*
|
|
15
|
+
* `did:web` → URL mapping (W3C did:web):
|
|
16
|
+
* - `did:web:example.com` → `https://example.com/.well-known/did.json`
|
|
17
|
+
* - `did:web:example.com:u:123` → `https://example.com/u/123/did.json`
|
|
18
|
+
* - a `%3A` in the host segment is decoded to `:` (an explicit port).
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.didWebToUrl = didWebToUrl;
|
|
22
|
+
exports.createDidWebResolver = createDidWebResolver;
|
|
23
|
+
const contracts_1 = require("@oxy.so/contracts");
|
|
24
|
+
const httpFetch_1 = require("./httpFetch");
|
|
25
|
+
const constants_1 = require("./constants");
|
|
26
|
+
/**
|
|
27
|
+
* Map a `did:web` DID to its `did.json` URL, or `null` when `did` is not a
|
|
28
|
+
* well-formed `did:web` identifier.
|
|
29
|
+
*/
|
|
30
|
+
function didWebToUrl(did) {
|
|
31
|
+
const prefix = 'did:web:';
|
|
32
|
+
if (!did.startsWith(prefix)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const msi = did.slice(prefix.length);
|
|
36
|
+
if (msi.length === 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const [domainPart, ...pathParts] = msi.split(':');
|
|
40
|
+
const host = domainPart.replace(/%3A/gi, ':');
|
|
41
|
+
if (host.length === 0 || host.includes('/')) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const base = `https://${host}`;
|
|
45
|
+
if (pathParts.length === 0) {
|
|
46
|
+
return `${base}/.well-known/did.json`;
|
|
47
|
+
}
|
|
48
|
+
if (pathParts.some((part) => part.length === 0)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return `${base}/${pathParts.join('/')}/did.json`;
|
|
52
|
+
}
|
|
53
|
+
/** True for a secp256k1 verification method (carries `publicKeyHex`). */
|
|
54
|
+
function isSecp256k1Vm(vm) {
|
|
55
|
+
return vm.type === 'EcdsaSecp256k1VerificationKey2019';
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Collect the subject's current verification keys from its DID document: the
|
|
59
|
+
* `publicKeyHex` of every secp256k1 verification method referenced by
|
|
60
|
+
* `assertionMethod` (the keys that may sign assertions/records), deduped. Falls
|
|
61
|
+
* back to ALL secp256k1 `verificationMethod[]` keys when `assertionMethod`
|
|
62
|
+
* references nothing local. Non-secp256k1 methods (e.g. the atproto `Multikey`,
|
|
63
|
+
* which carries the SAME key in multibase form, not hex) are skipped — record
|
|
64
|
+
* signatures verify against the hex key.
|
|
65
|
+
*/
|
|
66
|
+
function collectCurrentPublicKeys(doc) {
|
|
67
|
+
const byId = new Map(doc.verificationMethod
|
|
68
|
+
.filter(isSecp256k1Vm)
|
|
69
|
+
.map((vm) => [vm.id, vm.publicKeyHex]));
|
|
70
|
+
const keys = [];
|
|
71
|
+
for (const id of doc.assertionMethod) {
|
|
72
|
+
const key = byId.get(id);
|
|
73
|
+
if (key && !keys.includes(key)) {
|
|
74
|
+
keys.push(key);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (keys.length === 0) {
|
|
78
|
+
for (const key of byId.values()) {
|
|
79
|
+
if (!keys.includes(key)) {
|
|
80
|
+
keys.push(key);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return keys;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build a {@link VerificationMethodResolver} that resolves `did:web` subjects via
|
|
88
|
+
* the injected `fetch`. Returns `null` for any subject that is not a `did:web`
|
|
89
|
+
* DID, whose `did.json` cannot be fetched, or whose document fails schema
|
|
90
|
+
* validation — the engine then treats the signer as unauthorized.
|
|
91
|
+
*/
|
|
92
|
+
function createDidWebResolver(fetch, options = {}) {
|
|
93
|
+
const headersTimeoutMs = options.headersTimeoutMs ?? constants_1.DEFAULT_CLIENT_TIMEOUT_MS;
|
|
94
|
+
const maxRedirects = options.maxRedirects ?? constants_1.DEFAULT_CLIENT_MAX_REDIRECTS;
|
|
95
|
+
const maxBytes = options.maxBytes ?? constants_1.DEFAULT_DID_DOC_MAX_BYTES;
|
|
96
|
+
return {
|
|
97
|
+
async resolve(subjectDid) {
|
|
98
|
+
const url = didWebToUrl(subjectDid);
|
|
99
|
+
if (!url) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch(url, { method: 'GET', headersTimeoutMs, maxRedirects });
|
|
104
|
+
if (res.status < 200 || res.status >= 300) {
|
|
105
|
+
res.destroy();
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const body = await (0, httpFetch_1.readBoundedJson)(res, maxBytes);
|
|
109
|
+
const parsed = contracts_1.didDocumentSchema.safeParse(body);
|
|
110
|
+
if (!parsed.success) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
// The DID document `id` MUST match the subject we asked for — a document
|
|
114
|
+
// served at the subject's URL but claiming another id is not authoritative.
|
|
115
|
+
if (parsed.data.id !== subjectDid) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return { currentPublicKeys: collectCurrentPublicKeys(parsed.data) };
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
options.onError?.(err, subjectDid);
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The injected HTTP transport the node {@link ./nodeClient.NodeClient} and the
|
|
4
|
+
* {@link ./didWebResolver} drive — plus the bounded-read helpers that make a
|
|
5
|
+
* response stream safe to consume.
|
|
6
|
+
*
|
|
7
|
+
* The protocol package is app-agnostic and MUST NOT depend on `@oxy.so/core`
|
|
8
|
+
* (core depends on protocol). So instead of importing `@oxy.so/core/server`'s
|
|
9
|
+
* `safeFetch` directly, the node client/resolver accept a {@link NodeFetch} —
|
|
10
|
+
* Oxy supplies an adapter over `safeFetch` (HTTPS-only, DNS-pinned, private-IP
|
|
11
|
+
* denylist, bounded redirects); a test supplies an in-process stub. Either way
|
|
12
|
+
* the SSRF/transport policy stays in the injected implementation, and the
|
|
13
|
+
* bounded body read (the cap that stops a malicious node streaming forever)
|
|
14
|
+
* stays here, close to the parsing.
|
|
15
|
+
*
|
|
16
|
+
* A Node `IncomingMessage` (what `safeFetch` returns) satisfies
|
|
17
|
+
* {@link NodeFetchResponse.body} directly — it is an `AsyncIterable<Buffer>`,
|
|
18
|
+
* and `Buffer` is a `Uint8Array`.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.ResponseTooLargeError = void 0;
|
|
22
|
+
exports.readBoundedBytes = readBoundedBytes;
|
|
23
|
+
exports.readBoundedJson = readBoundedJson;
|
|
24
|
+
/** Thrown when a response body exceeds the caller's byte ceiling. */
|
|
25
|
+
class ResponseTooLargeError extends Error {
|
|
26
|
+
constructor(maxBytes) {
|
|
27
|
+
super(`response exceeded ${maxBytes} bytes`);
|
|
28
|
+
this.maxBytes = maxBytes;
|
|
29
|
+
this.name = 'ResponseTooLargeError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.ResponseTooLargeError = ResponseTooLargeError;
|
|
33
|
+
/**
|
|
34
|
+
* Read a response body into a single buffer, aborting (and destroying the
|
|
35
|
+
* stream) the moment it exceeds `maxBytes`. The bound is the defence against a
|
|
36
|
+
* node that streams an unbounded body.
|
|
37
|
+
*/
|
|
38
|
+
async function readBoundedBytes(res, maxBytes) {
|
|
39
|
+
const chunks = [];
|
|
40
|
+
let total = 0;
|
|
41
|
+
try {
|
|
42
|
+
for await (const chunk of res.body) {
|
|
43
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
44
|
+
total += buf.length;
|
|
45
|
+
if (total > maxBytes) {
|
|
46
|
+
throw new ResponseTooLargeError(maxBytes);
|
|
47
|
+
}
|
|
48
|
+
chunks.push(buf);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
res.destroy();
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
return Buffer.concat(chunks);
|
|
56
|
+
}
|
|
57
|
+
/** Read a bounded response body and parse it as JSON. */
|
|
58
|
+
async function readBoundedJson(res, maxBytes) {
|
|
59
|
+
const bytes = await readBoundedBytes(res, maxBytes);
|
|
60
|
+
return JSON.parse(bytes.toString('utf8'));
|
|
61
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@oxy.so/protocol/node` — the runnable node substrate.
|
|
4
|
+
*
|
|
5
|
+
* The Node-only half of the protocol: the Express app factory that backs any
|
|
6
|
+
* Oxy-protocol data node ({@link createNodeApp}), the HTTP {@link NodeClient}
|
|
7
|
+
* that drives a node's routes, the `did:web` verification-method resolver, the
|
|
8
|
+
* record verifier, and the node-protocol shape constants. A SEPARATE subpath
|
|
9
|
+
* from the package root so this Express/Node-only code never enters React
|
|
10
|
+
* Native / web bundles that import `@oxy.so/protocol`.
|
|
11
|
+
*
|
|
12
|
+
* Reused by `@oxy.so/node` (the runnable node), a future `mention-node` (an
|
|
13
|
+
* env-only deployment of the same base), and oxy-api's node sync (which drives
|
|
14
|
+
* a node via `NodeClient`).
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.DEFAULT_DID_DOC_MAX_BYTES = exports.DEFAULT_WRITE_RESPONSE_MAX_BYTES = exports.DEFAULT_LOG_MAX_BYTES = exports.DEFAULT_HEAD_MAX_BYTES = exports.DEFAULT_CLIENT_MAX_REDIRECTS = exports.DEFAULT_CLIENT_TIMEOUT_MS = exports.NODE_BLOBS_PATH = exports.NODE_SYNC_PUSH_PATH = exports.NODE_RECORDS_PATH = exports.NODE_LOG_PATH = exports.NODE_HEAD_PATH = exports.SHA256_HEX = exports.OWNER_ACTION_BLOB_PIN = exports.NODE_MODES = exports.OWNER_AUTH_MAX_AGE_MS = exports.OWNER_AUTH_HEADERS = exports.JSON_BODY_LIMIT = exports.MAX_SYNC_BATCH = exports.DEFAULT_MAX_BLOB_BYTES = exports.MAX_LOG_LIMIT = exports.DEFAULT_LOG_LIMIT = exports.DEFAULT_PORT = exports.DEFAULT_APP_NAMESPACE = exports.DEFAULT_SERVICE_TYPE = exports.DEFAULT_WELL_KNOWN_PATH = exports.PROTOCOL_VERSION = exports.didWebToUrl = exports.createDidWebResolver = exports.ResponseTooLargeError = exports.readBoundedJson = exports.readBoundedBytes = exports.trimTrailingSlashes = exports.NodeClientError = exports.NodeClient = exports.verifyNodeRecordEnvelope = exports.DEFAULT_MAX_RATE_LIMIT_ENTRIES = exports.DEFAULT_WRITE_RATE_LIMIT = exports.createRateLimiter = exports.BlobHashMismatchError = exports.createNodeApp = void 0;
|
|
18
|
+
// ── Express app factory ───────────────────────────────────────────────────────
|
|
19
|
+
var nodeApp_1 = require("./nodeApp");
|
|
20
|
+
Object.defineProperty(exports, "createNodeApp", { enumerable: true, get: function () { return nodeApp_1.createNodeApp; } });
|
|
21
|
+
Object.defineProperty(exports, "BlobHashMismatchError", { enumerable: true, get: function () { return nodeApp_1.BlobHashMismatchError; } });
|
|
22
|
+
// ── Per-IP write rate limiter ──────────────────────────────────────────────────
|
|
23
|
+
var rateLimit_1 = require("./rateLimit");
|
|
24
|
+
Object.defineProperty(exports, "createRateLimiter", { enumerable: true, get: function () { return rateLimit_1.createRateLimiter; } });
|
|
25
|
+
Object.defineProperty(exports, "DEFAULT_WRITE_RATE_LIMIT", { enumerable: true, get: function () { return rateLimit_1.DEFAULT_WRITE_RATE_LIMIT; } });
|
|
26
|
+
Object.defineProperty(exports, "DEFAULT_MAX_RATE_LIMIT_ENTRIES", { enumerable: true, get: function () { return rateLimit_1.DEFAULT_MAX_RATE_LIMIT_ENTRIES; } });
|
|
27
|
+
// ── Record verification (signature + v2 + content address) ─────────────────────
|
|
28
|
+
var verifyRecord_1 = require("./verifyRecord");
|
|
29
|
+
Object.defineProperty(exports, "verifyNodeRecordEnvelope", { enumerable: true, get: function () { return verifyRecord_1.verifyNodeRecordEnvelope; } });
|
|
30
|
+
// ── HTTP client ────────────────────────────────────────────────────────────────
|
|
31
|
+
var nodeClient_1 = require("./nodeClient");
|
|
32
|
+
Object.defineProperty(exports, "NodeClient", { enumerable: true, get: function () { return nodeClient_1.NodeClient; } });
|
|
33
|
+
Object.defineProperty(exports, "NodeClientError", { enumerable: true, get: function () { return nodeClient_1.NodeClientError; } });
|
|
34
|
+
Object.defineProperty(exports, "trimTrailingSlashes", { enumerable: true, get: function () { return nodeClient_1.trimTrailingSlashes; } });
|
|
35
|
+
// ── Injected transport contract + bounded readers ──────────────────────────────
|
|
36
|
+
var httpFetch_1 = require("./httpFetch");
|
|
37
|
+
Object.defineProperty(exports, "readBoundedBytes", { enumerable: true, get: function () { return httpFetch_1.readBoundedBytes; } });
|
|
38
|
+
Object.defineProperty(exports, "readBoundedJson", { enumerable: true, get: function () { return httpFetch_1.readBoundedJson; } });
|
|
39
|
+
Object.defineProperty(exports, "ResponseTooLargeError", { enumerable: true, get: function () { return httpFetch_1.ResponseTooLargeError; } });
|
|
40
|
+
// ── did:web verification-method resolver ───────────────────────────────────────
|
|
41
|
+
var didWebResolver_1 = require("./didWebResolver");
|
|
42
|
+
Object.defineProperty(exports, "createDidWebResolver", { enumerable: true, get: function () { return didWebResolver_1.createDidWebResolver; } });
|
|
43
|
+
Object.defineProperty(exports, "didWebToUrl", { enumerable: true, get: function () { return didWebResolver_1.didWebToUrl; } });
|
|
44
|
+
// ── Node-protocol shape constants ──────────────────────────────────────────────
|
|
45
|
+
var constants_1 = require("./constants");
|
|
46
|
+
Object.defineProperty(exports, "PROTOCOL_VERSION", { enumerable: true, get: function () { return constants_1.PROTOCOL_VERSION; } });
|
|
47
|
+
Object.defineProperty(exports, "DEFAULT_WELL_KNOWN_PATH", { enumerable: true, get: function () { return constants_1.DEFAULT_WELL_KNOWN_PATH; } });
|
|
48
|
+
Object.defineProperty(exports, "DEFAULT_SERVICE_TYPE", { enumerable: true, get: function () { return constants_1.DEFAULT_SERVICE_TYPE; } });
|
|
49
|
+
Object.defineProperty(exports, "DEFAULT_APP_NAMESPACE", { enumerable: true, get: function () { return constants_1.DEFAULT_APP_NAMESPACE; } });
|
|
50
|
+
Object.defineProperty(exports, "DEFAULT_PORT", { enumerable: true, get: function () { return constants_1.DEFAULT_PORT; } });
|
|
51
|
+
Object.defineProperty(exports, "DEFAULT_LOG_LIMIT", { enumerable: true, get: function () { return constants_1.DEFAULT_LOG_LIMIT; } });
|
|
52
|
+
Object.defineProperty(exports, "MAX_LOG_LIMIT", { enumerable: true, get: function () { return constants_1.MAX_LOG_LIMIT; } });
|
|
53
|
+
Object.defineProperty(exports, "DEFAULT_MAX_BLOB_BYTES", { enumerable: true, get: function () { return constants_1.DEFAULT_MAX_BLOB_BYTES; } });
|
|
54
|
+
Object.defineProperty(exports, "MAX_SYNC_BATCH", { enumerable: true, get: function () { return constants_1.MAX_SYNC_BATCH; } });
|
|
55
|
+
Object.defineProperty(exports, "JSON_BODY_LIMIT", { enumerable: true, get: function () { return constants_1.JSON_BODY_LIMIT; } });
|
|
56
|
+
Object.defineProperty(exports, "OWNER_AUTH_HEADERS", { enumerable: true, get: function () { return constants_1.OWNER_AUTH_HEADERS; } });
|
|
57
|
+
Object.defineProperty(exports, "OWNER_AUTH_MAX_AGE_MS", { enumerable: true, get: function () { return constants_1.OWNER_AUTH_MAX_AGE_MS; } });
|
|
58
|
+
Object.defineProperty(exports, "NODE_MODES", { enumerable: true, get: function () { return constants_1.NODE_MODES; } });
|
|
59
|
+
Object.defineProperty(exports, "OWNER_ACTION_BLOB_PIN", { enumerable: true, get: function () { return constants_1.OWNER_ACTION_BLOB_PIN; } });
|
|
60
|
+
Object.defineProperty(exports, "SHA256_HEX", { enumerable: true, get: function () { return constants_1.SHA256_HEX; } });
|
|
61
|
+
Object.defineProperty(exports, "NODE_HEAD_PATH", { enumerable: true, get: function () { return constants_1.NODE_HEAD_PATH; } });
|
|
62
|
+
Object.defineProperty(exports, "NODE_LOG_PATH", { enumerable: true, get: function () { return constants_1.NODE_LOG_PATH; } });
|
|
63
|
+
Object.defineProperty(exports, "NODE_RECORDS_PATH", { enumerable: true, get: function () { return constants_1.NODE_RECORDS_PATH; } });
|
|
64
|
+
Object.defineProperty(exports, "NODE_SYNC_PUSH_PATH", { enumerable: true, get: function () { return constants_1.NODE_SYNC_PUSH_PATH; } });
|
|
65
|
+
Object.defineProperty(exports, "NODE_BLOBS_PATH", { enumerable: true, get: function () { return constants_1.NODE_BLOBS_PATH; } });
|
|
66
|
+
Object.defineProperty(exports, "DEFAULT_CLIENT_TIMEOUT_MS", { enumerable: true, get: function () { return constants_1.DEFAULT_CLIENT_TIMEOUT_MS; } });
|
|
67
|
+
Object.defineProperty(exports, "DEFAULT_CLIENT_MAX_REDIRECTS", { enumerable: true, get: function () { return constants_1.DEFAULT_CLIENT_MAX_REDIRECTS; } });
|
|
68
|
+
Object.defineProperty(exports, "DEFAULT_HEAD_MAX_BYTES", { enumerable: true, get: function () { return constants_1.DEFAULT_HEAD_MAX_BYTES; } });
|
|
69
|
+
Object.defineProperty(exports, "DEFAULT_LOG_MAX_BYTES", { enumerable: true, get: function () { return constants_1.DEFAULT_LOG_MAX_BYTES; } });
|
|
70
|
+
Object.defineProperty(exports, "DEFAULT_WRITE_RESPONSE_MAX_BYTES", { enumerable: true, get: function () { return constants_1.DEFAULT_WRITE_RESPONSE_MAX_BYTES; } });
|
|
71
|
+
Object.defineProperty(exports, "DEFAULT_DID_DOC_MAX_BYTES", { enumerable: true, get: function () { return constants_1.DEFAULT_DID_DOC_MAX_BYTES; } });
|