@t-0/provider-sdk 1.1.36 → 1.1.37
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/README.md +58 -31
- package/lib/cjs/common/crypto/decode.d.ts +41 -0
- package/lib/cjs/common/crypto/decode.js +120 -0
- package/lib/cjs/common/crypto/index.d.ts +1 -0
- package/lib/cjs/crypto/index.d.ts +1 -0
- package/lib/cjs/crypto/index.js +3 -0
- package/lib/cjs/index.d.ts +2 -1
- package/lib/cjs/index.js +10 -2
- package/lib/cjs/service/validate_response.d.ts +6 -0
- package/lib/cjs/service/validate_response.js +8 -0
- package/lib/cjs/version.d.ts +1 -1
- package/lib/cjs/version.js +1 -1
- package/lib/esm/common/crypto/decode.d.ts +41 -0
- package/lib/esm/common/crypto/decode.js +114 -0
- package/lib/esm/common/crypto/index.d.ts +1 -0
- package/lib/esm/crypto/index.d.ts +1 -0
- package/lib/esm/crypto/index.js +1 -0
- package/lib/esm/index.d.ts +2 -1
- package/lib/esm/index.js +1 -1
- package/lib/esm/service/validate_response.d.ts +6 -0
- package/lib/esm/service/validate_response.js +7 -0
- package/lib/esm/version.d.ts +1 -1
- package/lib/esm/version.js +1 -1
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,55 +74,82 @@ const server = http.createServer(
|
|
|
74
74
|
`signatureValidation` captures raw request bytes for hashing, `nodeAdapter` bridges the RPC transport to Node.js HTTP, and `createService` registers your handlers with signature verification.
|
|
75
75
|
</details>
|
|
76
76
|
|
|
77
|
-
### Standalone
|
|
77
|
+
### Standalone Request Decoding
|
|
78
78
|
|
|
79
|
-
For frameworks that don't use Node's `http.createServer` (Effect, Koa, Fastify, etc.), use `
|
|
79
|
+
For frameworks that don't use Node's `http.createServer` (Hono, Effect, Koa, Fastify, etc.), use `createRequestDecoder` for one-call signature verification + Content-Type-aware decoding + protovalidation. It returns an either-type result: success with the decoded message and a response encoder, or failure with a ready-to-send HTTP error.
|
|
80
80
|
|
|
81
81
|
```ts
|
|
82
|
-
import {
|
|
83
|
-
import { createRequestVerifier, rejectRequest, PayoutRequestSchema, PayoutResponseSchema } from "@t-0/provider-sdk";
|
|
82
|
+
import { createRequestDecoder, PayoutRequestSchema, PayoutResponseSchema } from "@t-0/provider-sdk";
|
|
84
83
|
|
|
85
|
-
const
|
|
84
|
+
const decode = createRequestDecoder({
|
|
86
85
|
networkPublicKey: process.env.NETWORK_PUBLIC_KEY!,
|
|
87
86
|
});
|
|
88
87
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
signatureHeader: headers["x-signature"],
|
|
94
|
-
publicKeyHeader: headers["x-public-key"],
|
|
95
|
-
timestampHeader: headers["x-signature-timestamp"],
|
|
96
|
-
});
|
|
88
|
+
// Hono / fetch-shaped framework — route by Connect procedure path:
|
|
89
|
+
app.post("/tzero.v1.payment.ProviderService/PayOut", async (c) => {
|
|
90
|
+
const body = new Uint8Array(await c.req.arrayBuffer());
|
|
91
|
+
const result = decode(PayoutRequestSchema, { body, headers: c.req.raw.headers });
|
|
97
92
|
|
|
98
|
-
if (!result.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
if (!result.ok) {
|
|
94
|
+
return new Response(result.error.body, {
|
|
95
|
+
status: result.error.status,
|
|
96
|
+
headers: result.error.headers,
|
|
97
|
+
});
|
|
103
98
|
}
|
|
104
99
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
const response = await handlePayout(result.request);
|
|
101
|
+
|
|
102
|
+
// encodeResponse validates + encodes in the matching wire format (JSON or proto)
|
|
103
|
+
const wire = result.encodeResponse(PayoutResponseSchema, response);
|
|
104
|
+
return new Response(wire.body, { status: wire.status, headers: wire.headers });
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// Raw Node http example:
|
|
110
|
+
import http from "node:http";
|
|
111
|
+
import { createRequestDecoder, PayoutRequestSchema, PayoutResponseSchema } from "@t-0/provider-sdk";
|
|
112
|
+
|
|
113
|
+
const decode = createRequestDecoder({
|
|
114
|
+
networkPublicKey: process.env.NETWORK_PUBLIC_KEY!,
|
|
115
|
+
});
|
|
108
116
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
http.createServer((req, res) => {
|
|
118
|
+
const chunks: Buffer[] = [];
|
|
119
|
+
req.on("data", (c) => chunks.push(c));
|
|
120
|
+
req.on("end", async () => {
|
|
121
|
+
const body = Buffer.concat(chunks);
|
|
122
|
+
const result = decode(PayoutRequestSchema, { body, headers: req.headers });
|
|
123
|
+
|
|
124
|
+
if (!result.ok) {
|
|
125
|
+
res.writeHead(result.error.status, result.error.headers);
|
|
126
|
+
res.end(result.error.body);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const response = await handlePayout(result.request);
|
|
131
|
+
const wire = result.encodeResponse(PayoutResponseSchema, response);
|
|
132
|
+
res.writeHead(wire.status, wire.headers);
|
|
133
|
+
res.end(wire.body);
|
|
112
134
|
});
|
|
113
|
-
}
|
|
135
|
+
}).listen(3000);
|
|
114
136
|
```
|
|
115
137
|
|
|
116
|
-
The
|
|
138
|
+
The decoder accepts both fetch `Headers` and Node's `Record<string, string | string[] | undefined>`. It normalizes header case internally, detects Content-Type (`application/json` or `application/proto` / `application/protobuf` / `application/x-protobuf`), and the returned `encodeResponse` closure responds in the matching format.
|
|
139
|
+
|
|
140
|
+
For custom proto registries (e.g. non-network schemas with custom predefined rules), use the generic `createRequestDecoder` from `@t-0/provider-sdk/crypto` and pass your own `registry`.
|
|
117
141
|
|
|
118
142
|
**Important constraints for standalone integrations:**
|
|
119
143
|
|
|
120
|
-
- **Raw body bytes only.** Pass the exact wire bytes
|
|
121
|
-
- **Pass `Uint8Array`, not `ArrayBuffer`.** If your framework gives you an `ArrayBuffer` (e.g. `request.arrayBuffer()`), wrap it: `new Uint8Array(buf)`.
|
|
122
|
-
- **Header case.** `NetworkHeaders` enum values are title-case (`X-Signature`), but Node lowercases incoming headers. Look up headers by lowercase name: `headers["x-signature"]`.
|
|
123
|
-
- **Wire format is binary protobuf.** Requests and successful responses use `Content-Type: application/proto`. Deserialize with `fromBinary()`, serialize with `toBinary()` from `@bufbuild/protobuf`. For verification errors, use `rejectRequest(result.reason)` to get the correct HTTP status, headers, and JSON body. Success responses **must** include `Content-Type: application/proto`.
|
|
144
|
+
- **Raw body bytes only.** Pass the exact wire bytes — no body parsers, no auto-decompression, never re-serialized protobuf. Protobuf encoding is not canonical; re-encoding produces different bytes and breaks verification.
|
|
124
145
|
- **Health endpoint.** The T-0 Network probes `/grpc.health.v1.Health/Check` on every endpoint. The probe is signed. Standalone integrations must route this path and return a valid health response. See [`docs/HEALTH_SERVICE.md`](../../docs/HEALTH_SERVICE.md) for the wire contract.
|
|
125
|
-
- **`
|
|
146
|
+
- **`DecodeRequestFailure` is an open union.** New error shapes may be added without a major version bump. Handle unknown failures as generic errors.
|
|
147
|
+
|
|
148
|
+
<details>
|
|
149
|
+
<summary>Lower-level primitives</summary>
|
|
150
|
+
|
|
151
|
+
The individual building blocks are also exported: `createRequestVerifier`, `rejectRequest`, `verifySignature`, `computeDigest`, `keccak256`, `parsePublicKey`, `publicKeysEqual`. You can import just the crypto module via the `./crypto` subpath: `import { createRequestVerifier } from "@t-0/provider-sdk/crypto"`.
|
|
152
|
+
</details>
|
|
126
153
|
|
|
127
154
|
### Network Client
|
|
128
155
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { DescMessage, MessageShape, Registry } from '@bufbuild/protobuf';
|
|
2
|
+
import type { CreateVerifierOptions, RejectedRequest } from './request.js';
|
|
3
|
+
export interface CreateDecoderOptions extends CreateVerifierOptions {
|
|
4
|
+
registry?: Registry;
|
|
5
|
+
}
|
|
6
|
+
export type IncomingHeaders = {
|
|
7
|
+
get(name: string): string | null;
|
|
8
|
+
} | Record<string, string | string[] | undefined>;
|
|
9
|
+
export interface IncomingRequest {
|
|
10
|
+
body: Uint8Array | ArrayBufferView | ArrayBufferLike;
|
|
11
|
+
headers: IncomingHeaders;
|
|
12
|
+
}
|
|
13
|
+
export type WireFormat = 'json' | 'proto';
|
|
14
|
+
export interface Violation {
|
|
15
|
+
field: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
export type DecodeError = 'unsupported_content_type' | 'malformed_body' | 'invalid_request' | 'validation_error';
|
|
19
|
+
export interface WireResponse {
|
|
20
|
+
status: number;
|
|
21
|
+
headers: Record<string, string>;
|
|
22
|
+
body: string | Uint8Array<ArrayBuffer>;
|
|
23
|
+
}
|
|
24
|
+
export type DecodeRequestFailure = RejectedRequest | {
|
|
25
|
+
status: number;
|
|
26
|
+
headers: Record<string, string>;
|
|
27
|
+
body: string;
|
|
28
|
+
error: DecodeError;
|
|
29
|
+
violations?: Violation[];
|
|
30
|
+
};
|
|
31
|
+
export type DecodeRequestResult<Desc extends DescMessage> = {
|
|
32
|
+
ok: true;
|
|
33
|
+
request: MessageShape<Desc>;
|
|
34
|
+
format: WireFormat;
|
|
35
|
+
encodeResponse: <R extends DescMessage>(schema: R, message: MessageShape<R>) => WireResponse;
|
|
36
|
+
} | {
|
|
37
|
+
ok: false;
|
|
38
|
+
error: DecodeRequestFailure;
|
|
39
|
+
};
|
|
40
|
+
export type RequestDecoder = <Desc extends DescMessage>(schema: Desc, req: IncomingRequest) => DecodeRequestResult<Desc>;
|
|
41
|
+
export declare function createRequestDecoder(opts: CreateDecoderOptions): RequestDecoder;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createRequestDecoder = createRequestDecoder;
|
|
7
|
+
const protobuf_1 = require("@bufbuild/protobuf");
|
|
8
|
+
const protovalidate_1 = require("@bufbuild/protovalidate");
|
|
9
|
+
const request_js_1 = require("./request.js");
|
|
10
|
+
const headers_js_1 = __importDefault(require("../headers.js"));
|
|
11
|
+
function getHeader(headers, name) {
|
|
12
|
+
if (typeof headers.get === 'function') {
|
|
13
|
+
return headers.get(name) ?? '';
|
|
14
|
+
}
|
|
15
|
+
const rec = headers;
|
|
16
|
+
const lc = name.toLowerCase();
|
|
17
|
+
for (const key of Object.keys(rec)) {
|
|
18
|
+
if (key.toLowerCase() === lc) {
|
|
19
|
+
const v = rec[key];
|
|
20
|
+
if (Array.isArray(v))
|
|
21
|
+
return v[0] ?? '';
|
|
22
|
+
return v ?? '';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
function normalizeBody(body) {
|
|
28
|
+
if (body instanceof Uint8Array)
|
|
29
|
+
return body;
|
|
30
|
+
if (ArrayBuffer.isView(body))
|
|
31
|
+
return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
|
|
32
|
+
return new Uint8Array(body);
|
|
33
|
+
}
|
|
34
|
+
function detectFormat(contentType) {
|
|
35
|
+
const base = contentType.split(';')[0].trim().toLowerCase();
|
|
36
|
+
if (base === 'application/json')
|
|
37
|
+
return 'json';
|
|
38
|
+
if (base === 'application/proto' || base === 'application/protobuf' || base === 'application/x-protobuf')
|
|
39
|
+
return 'proto';
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
function failResponse(status, code, message, error, violations) {
|
|
43
|
+
return {
|
|
44
|
+
status,
|
|
45
|
+
headers: { 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify(violations ? { code, message, violations } : { code, message }),
|
|
47
|
+
error,
|
|
48
|
+
violations,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function createRequestDecoder(opts) {
|
|
52
|
+
const verify = (0, request_js_1.createRequestVerifier)(opts);
|
|
53
|
+
const validator = (0, protovalidate_1.createValidator)(opts.registry ? { registry: opts.registry } : undefined);
|
|
54
|
+
const textDecoder = new TextDecoder('utf-8', { fatal: true });
|
|
55
|
+
return (schema, req) => {
|
|
56
|
+
const body = normalizeBody(req.body);
|
|
57
|
+
const sigResult = verify({
|
|
58
|
+
body,
|
|
59
|
+
signatureHeader: getHeader(req.headers, headers_js_1.default.Signature),
|
|
60
|
+
publicKeyHeader: getHeader(req.headers, headers_js_1.default.PublicKey),
|
|
61
|
+
timestampHeader: getHeader(req.headers, headers_js_1.default.SignatureTimestamp),
|
|
62
|
+
});
|
|
63
|
+
if (!sigResult.valid) {
|
|
64
|
+
return { ok: false, error: (0, request_js_1.rejectRequest)(sigResult.reason) };
|
|
65
|
+
}
|
|
66
|
+
const format = detectFormat(getHeader(req.headers, 'content-type'));
|
|
67
|
+
if (!format) {
|
|
68
|
+
return { ok: false, error: failResponse(415, 'unsupported_content_type', 'Unsupported Content-Type', 'unsupported_content_type') };
|
|
69
|
+
}
|
|
70
|
+
let message;
|
|
71
|
+
try {
|
|
72
|
+
if (format === 'json') {
|
|
73
|
+
message = (0, protobuf_1.fromJsonString)(schema, textDecoder.decode(body), { ignoreUnknownFields: true, registry: opts.registry });
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
message = (0, protobuf_1.fromBinary)(schema, body);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return { ok: false, error: failResponse(400, 'invalid_argument', 'Malformed request body', 'malformed_body') };
|
|
81
|
+
}
|
|
82
|
+
const valResult = validator.validate(schema, message);
|
|
83
|
+
if (valResult.kind === 'invalid') {
|
|
84
|
+
const violations = valResult.violations.map(v => ({
|
|
85
|
+
field: v.field?.toString() ?? '',
|
|
86
|
+
message: v.message,
|
|
87
|
+
}));
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
error: failResponse(400, 'invalid_argument', 'Request validation failed', 'invalid_request', violations),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (valResult.kind === 'error') {
|
|
94
|
+
return { ok: false, error: failResponse(500, 'internal', `Validation error: ${valResult.error.message}`, 'validation_error') };
|
|
95
|
+
}
|
|
96
|
+
const encodeResponse = (respSchema, resp) => {
|
|
97
|
+
const respVal = validator.validate(respSchema, resp);
|
|
98
|
+
if (respVal.kind === 'invalid' || respVal.kind === 'error') {
|
|
99
|
+
return {
|
|
100
|
+
status: 500,
|
|
101
|
+
headers: { 'Content-Type': 'application/json' },
|
|
102
|
+
body: JSON.stringify({ code: 'internal', message: 'Response validation failed' }),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (format === 'json') {
|
|
106
|
+
return {
|
|
107
|
+
status: 200,
|
|
108
|
+
headers: { 'Content-Type': 'application/json' },
|
|
109
|
+
body: (0, protobuf_1.toJsonString)(respSchema, resp, { registry: opts.registry }),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
status: 200,
|
|
114
|
+
headers: { 'Content-Type': 'application/proto' },
|
|
115
|
+
body: (0, protobuf_1.toBinary)(respSchema, resp),
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
return { ok: true, request: message, format, encodeResponse };
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -3,3 +3,4 @@ export { keccak256, computeDigest } from './hash.js';
|
|
|
3
3
|
export { parsePublicKey, publicKeysEqual } from './keys.js';
|
|
4
4
|
export { createRequestVerifier, DEFAULT_TOLERANCE_MS, rejectRequest } from './request.js';
|
|
5
5
|
export type { CreateVerifierOptions, VerifyRequest, VerifyRequestResult, VerifyRequestFailure, RequestVerifier, RejectedRequest } from './request.js';
|
|
6
|
+
export type { CreateDecoderOptions, IncomingHeaders, IncomingRequest, WireFormat, DecodeRequestFailure, Violation, WireResponse, DecodeError, DecodeRequestResult, RequestDecoder } from './decode.js';
|
package/lib/cjs/crypto/index.js
CHANGED
|
@@ -14,4 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.createRequestDecoder = void 0;
|
|
17
18
|
__exportStar(require("../common/crypto/index.js"), exports);
|
|
19
|
+
var decode_js_1 = require("../common/crypto/decode.js");
|
|
20
|
+
Object.defineProperty(exports, "createRequestDecoder", { enumerable: true, get: function () { return decode_js_1.createRequestDecoder; } });
|
package/lib/cjs/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { verifySignature, keccak256, computeDigest, parsePublicKey, publicKeysEqual, createRequestVerifier, DEFAULT_TOLERANCE_MS, rejectRequest } from "./crypto/index.js";
|
|
2
|
+
export type { CreateVerifierOptions, VerifyRequest, VerifyRequestResult, VerifyRequestFailure, RequestVerifier, RejectedRequest, CreateDecoderOptions, IncomingHeaders, IncomingRequest, WireFormat, DecodeRequestFailure, Violation, WireResponse, DecodeError, DecodeRequestResult, RequestDecoder } from "./crypto/index.js";
|
|
2
3
|
export * from "./client/client.js";
|
|
3
4
|
export * from "./service/service.js";
|
|
4
5
|
export * from "./common/validation.js";
|
package/lib/cjs/index.js
CHANGED
|
@@ -39,8 +39,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
39
39
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
40
|
};
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.PaymentIntentRecipient = exports.PaymentIntentProvider = exports.PaymentIntentBeneficiary = exports.PaymentIntentPayInProvider = exports.PaymentIntentNetwork = exports.nodeAdapter = exports.NetworkHeaders = void 0;
|
|
43
|
-
|
|
42
|
+
exports.PaymentIntentRecipient = exports.PaymentIntentProvider = exports.PaymentIntentBeneficiary = exports.PaymentIntentPayInProvider = exports.PaymentIntentNetwork = exports.nodeAdapter = exports.NetworkHeaders = exports.rejectRequest = exports.DEFAULT_TOLERANCE_MS = exports.createRequestVerifier = exports.publicKeysEqual = exports.parsePublicKey = exports.computeDigest = exports.keccak256 = exports.verifySignature = void 0;
|
|
43
|
+
var index_js_1 = require("./crypto/index.js");
|
|
44
|
+
Object.defineProperty(exports, "verifySignature", { enumerable: true, get: function () { return index_js_1.verifySignature; } });
|
|
45
|
+
Object.defineProperty(exports, "keccak256", { enumerable: true, get: function () { return index_js_1.keccak256; } });
|
|
46
|
+
Object.defineProperty(exports, "computeDigest", { enumerable: true, get: function () { return index_js_1.computeDigest; } });
|
|
47
|
+
Object.defineProperty(exports, "parsePublicKey", { enumerable: true, get: function () { return index_js_1.parsePublicKey; } });
|
|
48
|
+
Object.defineProperty(exports, "publicKeysEqual", { enumerable: true, get: function () { return index_js_1.publicKeysEqual; } });
|
|
49
|
+
Object.defineProperty(exports, "createRequestVerifier", { enumerable: true, get: function () { return index_js_1.createRequestVerifier; } });
|
|
50
|
+
Object.defineProperty(exports, "DEFAULT_TOLERANCE_MS", { enumerable: true, get: function () { return index_js_1.DEFAULT_TOLERANCE_MS; } });
|
|
51
|
+
Object.defineProperty(exports, "rejectRequest", { enumerable: true, get: function () { return index_js_1.rejectRequest; } });
|
|
44
52
|
__exportStar(require("./client/client.js"), exports);
|
|
45
53
|
__exportStar(require("./service/service.js"), exports);
|
|
46
54
|
__exportStar(require("./common/validation.js"), exports);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Interceptor } from "@connectrpc/connect";
|
|
2
2
|
import type { Logger } from "../common/validation.js";
|
|
3
3
|
import { createValidationInterceptor } from "../common/validation.js";
|
|
4
|
+
import type { RequestDecoder } from "../common/crypto/decode.js";
|
|
5
|
+
import type { CreateVerifierOptions } from "../common/crypto/request.js";
|
|
4
6
|
export { createValidationInterceptor, type Logger, type ValidationInterceptorOptions } from "../common/validation.js";
|
|
5
7
|
/**
|
|
6
8
|
* Registry covering the t-0 network provider contract protos. Leaf file
|
|
@@ -12,6 +14,10 @@ export declare const networkRegistry: import("@bufbuild/protobuf").Registry;
|
|
|
12
14
|
* Validation interceptor pre-configured for the t-0 network provider contract.
|
|
13
15
|
*/
|
|
14
16
|
export declare function createNetworkValidationInterceptor(logger?: Logger): Interceptor;
|
|
17
|
+
/**
|
|
18
|
+
* Request decoder pre-configured for the t-0 network provider contract.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createRequestDecoder(opts: CreateVerifierOptions): RequestDecoder;
|
|
15
21
|
/**
|
|
16
22
|
* @deprecated Use createValidationInterceptor instead.
|
|
17
23
|
*/
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createResponseValidation = exports.networkRegistry = exports.createValidationInterceptor = void 0;
|
|
4
4
|
exports.createNetworkValidationInterceptor = createNetworkValidationInterceptor;
|
|
5
|
+
exports.createRequestDecoder = createRequestDecoder;
|
|
5
6
|
const protobuf_1 = require("@bufbuild/protobuf");
|
|
6
7
|
const provider_pb_js_1 = require("../common/gen/tzero/v1/payment/provider_pb.js");
|
|
7
8
|
const network_pb_js_1 = require("../common/gen/tzero/v1/payment/network_pb.js");
|
|
8
9
|
const validation_js_1 = require("../common/validation.js");
|
|
10
|
+
const decode_js_1 = require("../common/crypto/decode.js");
|
|
9
11
|
// Re-export everything from the common layer for backward compatibility.
|
|
10
12
|
var validation_js_2 = require("../common/validation.js");
|
|
11
13
|
Object.defineProperty(exports, "createValidationInterceptor", { enumerable: true, get: function () { return validation_js_2.createValidationInterceptor; } });
|
|
@@ -21,6 +23,12 @@ exports.networkRegistry = (0, protobuf_1.createRegistry)(provider_pb_js_1.file_t
|
|
|
21
23
|
function createNetworkValidationInterceptor(logger) {
|
|
22
24
|
return (0, validation_js_1.createValidationInterceptor)({ logger, registry: exports.networkRegistry });
|
|
23
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Request decoder pre-configured for the t-0 network provider contract.
|
|
28
|
+
*/
|
|
29
|
+
function createRequestDecoder(opts) {
|
|
30
|
+
return (0, decode_js_1.createRequestDecoder)({ ...opts, registry: exports.networkRegistry });
|
|
31
|
+
}
|
|
24
32
|
/**
|
|
25
33
|
* @deprecated Use createValidationInterceptor instead.
|
|
26
34
|
*/
|
package/lib/cjs/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "1.1.
|
|
1
|
+
export declare const SDK_VERSION = "1.1.37";
|
package/lib/cjs/version.js
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { DescMessage, MessageShape, Registry } from '@bufbuild/protobuf';
|
|
2
|
+
import type { CreateVerifierOptions, RejectedRequest } from './request.js';
|
|
3
|
+
export interface CreateDecoderOptions extends CreateVerifierOptions {
|
|
4
|
+
registry?: Registry;
|
|
5
|
+
}
|
|
6
|
+
export type IncomingHeaders = {
|
|
7
|
+
get(name: string): string | null;
|
|
8
|
+
} | Record<string, string | string[] | undefined>;
|
|
9
|
+
export interface IncomingRequest {
|
|
10
|
+
body: Uint8Array | ArrayBufferView | ArrayBufferLike;
|
|
11
|
+
headers: IncomingHeaders;
|
|
12
|
+
}
|
|
13
|
+
export type WireFormat = 'json' | 'proto';
|
|
14
|
+
export interface Violation {
|
|
15
|
+
field: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
export type DecodeError = 'unsupported_content_type' | 'malformed_body' | 'invalid_request' | 'validation_error';
|
|
19
|
+
export interface WireResponse {
|
|
20
|
+
status: number;
|
|
21
|
+
headers: Record<string, string>;
|
|
22
|
+
body: string | Uint8Array<ArrayBuffer>;
|
|
23
|
+
}
|
|
24
|
+
export type DecodeRequestFailure = RejectedRequest | {
|
|
25
|
+
status: number;
|
|
26
|
+
headers: Record<string, string>;
|
|
27
|
+
body: string;
|
|
28
|
+
error: DecodeError;
|
|
29
|
+
violations?: Violation[];
|
|
30
|
+
};
|
|
31
|
+
export type DecodeRequestResult<Desc extends DescMessage> = {
|
|
32
|
+
ok: true;
|
|
33
|
+
request: MessageShape<Desc>;
|
|
34
|
+
format: WireFormat;
|
|
35
|
+
encodeResponse: <R extends DescMessage>(schema: R, message: MessageShape<R>) => WireResponse;
|
|
36
|
+
} | {
|
|
37
|
+
ok: false;
|
|
38
|
+
error: DecodeRequestFailure;
|
|
39
|
+
};
|
|
40
|
+
export type RequestDecoder = <Desc extends DescMessage>(schema: Desc, req: IncomingRequest) => DecodeRequestResult<Desc>;
|
|
41
|
+
export declare function createRequestDecoder(opts: CreateDecoderOptions): RequestDecoder;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { fromJsonString, fromBinary, toJsonString, toBinary } from '@bufbuild/protobuf';
|
|
2
|
+
import { createValidator } from '@bufbuild/protovalidate';
|
|
3
|
+
import { createRequestVerifier, rejectRequest } from './request.js';
|
|
4
|
+
import NetworkHeaders from '../headers.js';
|
|
5
|
+
function getHeader(headers, name) {
|
|
6
|
+
if (typeof headers.get === 'function') {
|
|
7
|
+
return headers.get(name) ?? '';
|
|
8
|
+
}
|
|
9
|
+
const rec = headers;
|
|
10
|
+
const lc = name.toLowerCase();
|
|
11
|
+
for (const key of Object.keys(rec)) {
|
|
12
|
+
if (key.toLowerCase() === lc) {
|
|
13
|
+
const v = rec[key];
|
|
14
|
+
if (Array.isArray(v))
|
|
15
|
+
return v[0] ?? '';
|
|
16
|
+
return v ?? '';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
function normalizeBody(body) {
|
|
22
|
+
if (body instanceof Uint8Array)
|
|
23
|
+
return body;
|
|
24
|
+
if (ArrayBuffer.isView(body))
|
|
25
|
+
return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
|
|
26
|
+
return new Uint8Array(body);
|
|
27
|
+
}
|
|
28
|
+
function detectFormat(contentType) {
|
|
29
|
+
const base = contentType.split(';')[0].trim().toLowerCase();
|
|
30
|
+
if (base === 'application/json')
|
|
31
|
+
return 'json';
|
|
32
|
+
if (base === 'application/proto' || base === 'application/protobuf' || base === 'application/x-protobuf')
|
|
33
|
+
return 'proto';
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function failResponse(status, code, message, error, violations) {
|
|
37
|
+
return {
|
|
38
|
+
status,
|
|
39
|
+
headers: { 'Content-Type': 'application/json' },
|
|
40
|
+
body: JSON.stringify(violations ? { code, message, violations } : { code, message }),
|
|
41
|
+
error,
|
|
42
|
+
violations,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function createRequestDecoder(opts) {
|
|
46
|
+
const verify = createRequestVerifier(opts);
|
|
47
|
+
const validator = createValidator(opts.registry ? { registry: opts.registry } : undefined);
|
|
48
|
+
const textDecoder = new TextDecoder('utf-8', { fatal: true });
|
|
49
|
+
return (schema, req) => {
|
|
50
|
+
const body = normalizeBody(req.body);
|
|
51
|
+
const sigResult = verify({
|
|
52
|
+
body,
|
|
53
|
+
signatureHeader: getHeader(req.headers, NetworkHeaders.Signature),
|
|
54
|
+
publicKeyHeader: getHeader(req.headers, NetworkHeaders.PublicKey),
|
|
55
|
+
timestampHeader: getHeader(req.headers, NetworkHeaders.SignatureTimestamp),
|
|
56
|
+
});
|
|
57
|
+
if (!sigResult.valid) {
|
|
58
|
+
return { ok: false, error: rejectRequest(sigResult.reason) };
|
|
59
|
+
}
|
|
60
|
+
const format = detectFormat(getHeader(req.headers, 'content-type'));
|
|
61
|
+
if (!format) {
|
|
62
|
+
return { ok: false, error: failResponse(415, 'unsupported_content_type', 'Unsupported Content-Type', 'unsupported_content_type') };
|
|
63
|
+
}
|
|
64
|
+
let message;
|
|
65
|
+
try {
|
|
66
|
+
if (format === 'json') {
|
|
67
|
+
message = fromJsonString(schema, textDecoder.decode(body), { ignoreUnknownFields: true, registry: opts.registry });
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
message = fromBinary(schema, body);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return { ok: false, error: failResponse(400, 'invalid_argument', 'Malformed request body', 'malformed_body') };
|
|
75
|
+
}
|
|
76
|
+
const valResult = validator.validate(schema, message);
|
|
77
|
+
if (valResult.kind === 'invalid') {
|
|
78
|
+
const violations = valResult.violations.map(v => ({
|
|
79
|
+
field: v.field?.toString() ?? '',
|
|
80
|
+
message: v.message,
|
|
81
|
+
}));
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
error: failResponse(400, 'invalid_argument', 'Request validation failed', 'invalid_request', violations),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (valResult.kind === 'error') {
|
|
88
|
+
return { ok: false, error: failResponse(500, 'internal', `Validation error: ${valResult.error.message}`, 'validation_error') };
|
|
89
|
+
}
|
|
90
|
+
const encodeResponse = (respSchema, resp) => {
|
|
91
|
+
const respVal = validator.validate(respSchema, resp);
|
|
92
|
+
if (respVal.kind === 'invalid' || respVal.kind === 'error') {
|
|
93
|
+
return {
|
|
94
|
+
status: 500,
|
|
95
|
+
headers: { 'Content-Type': 'application/json' },
|
|
96
|
+
body: JSON.stringify({ code: 'internal', message: 'Response validation failed' }),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (format === 'json') {
|
|
100
|
+
return {
|
|
101
|
+
status: 200,
|
|
102
|
+
headers: { 'Content-Type': 'application/json' },
|
|
103
|
+
body: toJsonString(respSchema, resp, { registry: opts.registry }),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
status: 200,
|
|
108
|
+
headers: { 'Content-Type': 'application/proto' },
|
|
109
|
+
body: toBinary(respSchema, resp),
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
return { ok: true, request: message, format, encodeResponse };
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -3,3 +3,4 @@ export { keccak256, computeDigest } from './hash.js';
|
|
|
3
3
|
export { parsePublicKey, publicKeysEqual } from './keys.js';
|
|
4
4
|
export { createRequestVerifier, DEFAULT_TOLERANCE_MS, rejectRequest } from './request.js';
|
|
5
5
|
export type { CreateVerifierOptions, VerifyRequest, VerifyRequestResult, VerifyRequestFailure, RequestVerifier, RejectedRequest } from './request.js';
|
|
6
|
+
export type { CreateDecoderOptions, IncomingHeaders, IncomingRequest, WireFormat, DecodeRequestFailure, Violation, WireResponse, DecodeError, DecodeRequestResult, RequestDecoder } from './decode.js';
|
package/lib/esm/crypto/index.js
CHANGED
package/lib/esm/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { verifySignature, keccak256, computeDigest, parsePublicKey, publicKeysEqual, createRequestVerifier, DEFAULT_TOLERANCE_MS, rejectRequest } from "./crypto/index.js";
|
|
2
|
+
export type { CreateVerifierOptions, VerifyRequest, VerifyRequestResult, VerifyRequestFailure, RequestVerifier, RejectedRequest, CreateDecoderOptions, IncomingHeaders, IncomingRequest, WireFormat, DecodeRequestFailure, Violation, WireResponse, DecodeError, DecodeRequestResult, RequestDecoder } from "./crypto/index.js";
|
|
2
3
|
export * from "./client/client.js";
|
|
3
4
|
export * from "./service/service.js";
|
|
4
5
|
export * from "./common/validation.js";
|
package/lib/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { verifySignature, keccak256, computeDigest, parsePublicKey, publicKeysEqual, createRequestVerifier, DEFAULT_TOLERANCE_MS, rejectRequest } from "./crypto/index.js";
|
|
2
2
|
export * from "./client/client.js";
|
|
3
3
|
export * from "./service/service.js";
|
|
4
4
|
export * from "./common/validation.js";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Interceptor } from "@connectrpc/connect";
|
|
2
2
|
import type { Logger } from "../common/validation.js";
|
|
3
3
|
import { createValidationInterceptor } from "../common/validation.js";
|
|
4
|
+
import type { RequestDecoder } from "../common/crypto/decode.js";
|
|
5
|
+
import type { CreateVerifierOptions } from "../common/crypto/request.js";
|
|
4
6
|
export { createValidationInterceptor, type Logger, type ValidationInterceptorOptions } from "../common/validation.js";
|
|
5
7
|
/**
|
|
6
8
|
* Registry covering the t-0 network provider contract protos. Leaf file
|
|
@@ -12,6 +14,10 @@ export declare const networkRegistry: import("@bufbuild/protobuf").Registry;
|
|
|
12
14
|
* Validation interceptor pre-configured for the t-0 network provider contract.
|
|
13
15
|
*/
|
|
14
16
|
export declare function createNetworkValidationInterceptor(logger?: Logger): Interceptor;
|
|
17
|
+
/**
|
|
18
|
+
* Request decoder pre-configured for the t-0 network provider contract.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createRequestDecoder(opts: CreateVerifierOptions): RequestDecoder;
|
|
15
21
|
/**
|
|
16
22
|
* @deprecated Use createValidationInterceptor instead.
|
|
17
23
|
*/
|
|
@@ -2,6 +2,7 @@ import { createRegistry } from "@bufbuild/protobuf";
|
|
|
2
2
|
import { file_tzero_v1_payment_provider } from "../common/gen/tzero/v1/payment/provider_pb.js";
|
|
3
3
|
import { file_tzero_v1_payment_network } from "../common/gen/tzero/v1/payment/network_pb.js";
|
|
4
4
|
import { createValidationInterceptor } from "../common/validation.js";
|
|
5
|
+
import { createRequestDecoder as createBaseRequestDecoder } from "../common/crypto/decode.js";
|
|
5
6
|
// Re-export everything from the common layer for backward compatibility.
|
|
6
7
|
export { createValidationInterceptor } from "../common/validation.js";
|
|
7
8
|
/**
|
|
@@ -16,6 +17,12 @@ export const networkRegistry = createRegistry(file_tzero_v1_payment_provider, fi
|
|
|
16
17
|
export function createNetworkValidationInterceptor(logger) {
|
|
17
18
|
return createValidationInterceptor({ logger, registry: networkRegistry });
|
|
18
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Request decoder pre-configured for the t-0 network provider contract.
|
|
22
|
+
*/
|
|
23
|
+
export function createRequestDecoder(opts) {
|
|
24
|
+
return createBaseRequestDecoder({ ...opts, registry: networkRegistry });
|
|
25
|
+
}
|
|
19
26
|
/**
|
|
20
27
|
* @deprecated Use createValidationInterceptor instead.
|
|
21
28
|
*/
|
package/lib/esm/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "1.1.
|
|
1
|
+
export declare const SDK_VERSION = "1.1.37";
|
package/lib/esm/version.js
CHANGED