trustplane-sdk 0.1.1 → 0.3.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Trustplane JS SDK (v0.1)
1
+ # Trustplane JS SDK (v0.2)
2
2
 
3
3
  Minimal SDK to generate Trustplane proof headers.
4
4
 
@@ -55,3 +55,52 @@ const out = client.sign({
55
55
  privateKey: '<private_key_b64url>'
56
56
  });
57
57
  ```
58
+
59
+ ## Blindfold verify (one call)
60
+
61
+ ```js
62
+ const { blindfoldVerify, fromFile } = require('trustplane-sdk');
63
+
64
+ const res = await blindfoldVerify({
65
+ authBaseUrl: 'https://auth.trustplane.mergematter.io',
66
+ tenantId: 'new_tenant',
67
+ apiId: 'api_demo_2',
68
+ clientId: 'client_demo',
69
+ privateKey: '<private_key_b64url>',
70
+ method: 'GET',
71
+ path: '/orders',
72
+ body: '',
73
+ });
74
+
75
+ console.log(res.status, res.data);
76
+ ```
77
+
78
+ Blindfold uses a blind OPRF exchange under the hood and only sends a blinded input to the Auth Plane.
79
+
80
+ You can also load `auth_base_url` from `trustplane.json`:
81
+
82
+ ```js
83
+ const { fromFile } = require('trustplane-sdk');
84
+
85
+ const client = fromFile('./trustplane.json');
86
+ const res = await client.blindfoldVerify({
87
+ method: 'GET',
88
+ path: '/orders',
89
+ body: '',
90
+ privateKey: '<private_key_b64url>'
91
+ });
92
+ ```
93
+
94
+ ## Integration test (against auth plane)
95
+
96
+ ```bash
97
+ TP_AUTH_BASE_URL=https://auth.trustplane.mergematter.io \
98
+ TP_TENANT_ID=<tenant_id> \
99
+ TP_API_ID=<api_id> \
100
+ TP_CLIENT_ID=<client_id> \
101
+ TP_PRIVATE_KEY=<private_key_b64url> \
102
+ TP_MODE=core \
103
+ npm run test:integration
104
+ ```
105
+
106
+ For blindfold APIs, use `TP_MODE=blindfold`.
package/index.d.ts CHANGED
@@ -30,15 +30,53 @@ export type SignOutput = {
30
30
 
31
31
  export function sign(input: SignInput): SignOutput;
32
32
  export function signAsync(input: SignInput): Promise<SignOutput>;
33
+ export function blindfoldVerify(input: SignInput & {
34
+ authBaseUrl: string;
35
+ fetchFn?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
36
+ }): Promise<{
37
+ step: string;
38
+ status: number;
39
+ ok: boolean;
40
+ data: any;
41
+ verifyPayload?: any;
42
+ transcript?: string;
43
+ digest?: string;
44
+ }>;
33
45
 
34
46
  export function createClient(input: {
35
47
  tenantId: string;
36
48
  apiId: string;
37
49
  clientId: string;
38
50
  bucketSeconds?: number;
51
+ authBaseUrl?: string;
52
+ gatewayUrl?: string;
53
+ requestPath?: string;
54
+ proofType?: string;
39
55
  }): {
56
+ config: {
57
+ tenantId: string;
58
+ apiId: string;
59
+ clientId: string;
60
+ bucketSeconds?: number;
61
+ authBaseUrl?: string;
62
+ gatewayUrl?: string;
63
+ requestPath?: string;
64
+ proofType?: string;
65
+ };
40
66
  sign(input: Omit<SignInput, "tenantId" | "apiId" | "clientId" | "bucketSeconds">): SignOutput;
41
67
  signAsync(input: Omit<SignInput, "tenantId" | "apiId" | "clientId" | "bucketSeconds">): Promise<SignOutput>;
68
+ blindfoldVerify(input: Omit<SignInput, "tenantId" | "apiId" | "clientId" | "bucketSeconds"> & {
69
+ authBaseUrl?: string;
70
+ fetchFn?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
71
+ }): Promise<{
72
+ step: string;
73
+ status: number;
74
+ ok: boolean;
75
+ data: any;
76
+ verifyPayload?: any;
77
+ transcript?: string;
78
+ digest?: string;
79
+ }>;
42
80
  };
43
81
 
44
82
  export function fromFile(path: string): ReturnType<typeof createClient>;
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const crypto = require('crypto');
2
2
  const nacl = require('tweetnacl');
3
3
  const fs = require('fs');
4
+ const OPRF = require('oprf');
4
5
 
5
6
  function base64urlEncode(buf) {
6
7
  return Buffer.from(buf)
@@ -189,21 +190,122 @@ function fromFile(path) {
189
190
  apiId: cfg.api_id,
190
191
  clientId: cfg.client_id,
191
192
  bucketSeconds: cfg.bucket_seconds,
193
+ authBaseUrl: cfg.auth_base_url,
194
+ gatewayUrl: cfg.gateway_url,
195
+ requestPath: cfg.request_path,
196
+ proofType: cfg.proof_type,
192
197
  });
193
198
  }
194
199
 
195
- function createClient({ tenantId, apiId, clientId, bucketSeconds }) {
200
+ function createClient({ tenantId, apiId, clientId, bucketSeconds, authBaseUrl, gatewayUrl, requestPath, proofType }) {
196
201
  return {
202
+ config: {
203
+ tenantId,
204
+ apiId,
205
+ clientId,
206
+ bucketSeconds,
207
+ authBaseUrl,
208
+ gatewayUrl,
209
+ requestPath,
210
+ proofType,
211
+ },
197
212
  sign: ({ method, path, body, privateKey }) =>
198
213
  signProof({ tenantId, apiId, clientId, privateKey, method, path, body, bucketSeconds }),
199
214
  signAsync: ({ method, path, body, privateKey }) =>
200
215
  signProofAsync({ tenantId, apiId, clientId, privateKey, method, path, body, bucketSeconds }),
216
+ blindfoldVerify: async ({ authBaseUrl: authOverride, method, path, body, privateKey, fetchFn }) =>
217
+ blindfoldVerify({
218
+ authBaseUrl: authOverride || authBaseUrl,
219
+ tenantId,
220
+ apiId,
221
+ clientId,
222
+ privateKey,
223
+ method,
224
+ path,
225
+ body,
226
+ bucketSeconds,
227
+ fetchFn,
228
+ }),
229
+ };
230
+ }
231
+
232
+ async function httpJSON(url, payload, fetchFn) {
233
+ const impl = fetchFn || (typeof fetch !== 'undefined' ? fetch : null);
234
+ if (!impl) {
235
+ throw new Error('fetch is required; provide fetchFn or run in an environment with global fetch');
236
+ }
237
+ const res = await impl(url, {
238
+ method: 'POST',
239
+ headers: { 'content-type': 'application/json' },
240
+ body: JSON.stringify(payload),
241
+ });
242
+ const text = await res.text();
243
+ let data;
244
+ try {
245
+ data = JSON.parse(text);
246
+ } catch (err) {
247
+ data = { raw: text };
248
+ }
249
+ return { status: res.status, ok: res.ok, data };
250
+ }
251
+
252
+ async function blindfoldVerify({ authBaseUrl, tenantId, apiId, clientId, privateKey, method, path, body, bucketSeconds, fetchFn }) {
253
+ if (!authBaseUrl) throw new Error('authBaseUrl is required');
254
+ const signed = signProof({ tenantId, apiId, clientId, privateKey, method, path, body, bucketSeconds });
255
+ const base = String(authBaseUrl).replace(/\/+$/, '');
256
+ const oprf = new OPRF();
257
+ if (oprf.ready) {
258
+ await oprf.ready;
259
+ }
260
+ const start = await httpJSON(base + '/auth/blindfold/start', {
261
+ tenant_id: signed.verifyPayload.tenant_id,
262
+ api_id: signed.verifyPayload.api_id,
263
+ client_id: signed.verifyPayload.client_id,
264
+ method: signed.verifyPayload.method,
265
+ path: signed.verifyPayload.path,
266
+ body_hash: signed.verifyPayload.body_hash,
267
+ time_bucket: signed.verifyPayload.time_bucket,
268
+ nonce: signed.verifyPayload.nonce,
269
+ }, fetchFn);
270
+ if (!start.ok) {
271
+ return { step: 'start', ...start };
272
+ }
273
+ const oprfInput = signed.digest;
274
+ const masked = oprf.maskInput(oprfInput);
275
+ const evalRes = await httpJSON(base + '/oprf/full-evaluate', {
276
+ blinded_input_b64url: base64urlEncode(masked.point),
277
+ }, fetchFn);
278
+ if (!evalRes.ok) {
279
+ return { step: 'evaluate', ...evalRes };
280
+ }
281
+ const evaluated = base64urlDecode((evalRes.data || {}).evaluated_b64url || '');
282
+ const unmasked = oprf.unmaskPoint(new Uint8Array(evaluated), masked.mask);
283
+ const proofPayload = base64urlEncode(Buffer.from(unmasked));
284
+ const finalize = await httpJSON(base + '/auth/blindfold/finalize', {
285
+ session_id: (start.data || {}).session_id,
286
+ proof_payload: proofPayload,
287
+ }, fetchFn);
288
+ if (!finalize.ok) {
289
+ return { step: 'finalize', ...finalize };
290
+ }
291
+ const verifyPayload = Object.assign({}, signed.verifyPayload, {
292
+ proof_type: 'blindfold',
293
+ proof_payload: ((finalize.data || {}).verify_payload || {}).proof_payload || proofPayload || '',
294
+ });
295
+ const verify = await httpJSON(base + '/auth/verify', verifyPayload, fetchFn);
296
+ return {
297
+ step: 'verify',
298
+ ...verify,
299
+ verifyPayload,
300
+ transcript: signed.transcript,
301
+ digest: signed.digest,
201
302
  };
202
303
  }
203
304
 
204
305
  module.exports = {
205
306
  sign: signProof,
206
307
  signAsync: signProofAsync,
308
+ blindfoldVerify,
207
309
  createClient,
208
310
  fromFile,
209
311
  };
@@ -0,0 +1,59 @@
1
+ const { sign, blindfoldVerify } = require('./index');
2
+
3
+ async function postJSON(url, payload) {
4
+ const res = await fetch(url, {
5
+ method: 'POST',
6
+ headers: { 'content-type': 'application/json' },
7
+ body: JSON.stringify(payload),
8
+ });
9
+ const data = await res.json().catch(() => ({}));
10
+ return { status: res.status, ok: res.ok, data };
11
+ }
12
+
13
+ async function main() {
14
+ const base = process.env.TP_AUTH_BASE_URL || '';
15
+ const tenantId = process.env.TP_TENANT_ID || '';
16
+ const apiId = process.env.TP_API_ID || '';
17
+ const clientId = process.env.TP_CLIENT_ID || '';
18
+ const privateKey = process.env.TP_PRIVATE_KEY || '';
19
+ const path = process.env.TP_PATH || '/orders';
20
+ const mode = (process.env.TP_MODE || 'core').toLowerCase();
21
+
22
+ if (!base || !tenantId || !apiId || !clientId || !privateKey) {
23
+ console.log('SKIP: set TP_AUTH_BASE_URL TP_TENANT_ID TP_API_ID TP_CLIENT_ID TP_PRIVATE_KEY');
24
+ process.exit(0);
25
+ }
26
+
27
+ if (mode === 'blindfold') {
28
+ const out = await blindfoldVerify({
29
+ authBaseUrl: base,
30
+ tenantId,
31
+ apiId,
32
+ clientId,
33
+ privateKey,
34
+ method: 'GET',
35
+ path,
36
+ body: '',
37
+ });
38
+ console.log('blindfold', out.status, out.data && out.data.decision);
39
+ process.exit(out.ok ? 0 : 1);
40
+ }
41
+
42
+ const out = sign({
43
+ tenantId,
44
+ apiId,
45
+ clientId,
46
+ privateKey,
47
+ method: 'GET',
48
+ path,
49
+ body: '',
50
+ });
51
+ const res = await postJSON(String(base).replace(/\/+$/, '') + '/auth/verify', out.verifyPayload);
52
+ console.log('core', res.status, res.data && res.data.decision);
53
+ process.exit(res.ok ? 0 : 1);
54
+ }
55
+
56
+ main().catch((err) => {
57
+ console.error(err);
58
+ process.exit(1);
59
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trustplane-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Trustplane SDK (JS) for generating request proof headers",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -8,16 +8,19 @@
8
8
  "index.js",
9
9
  "index.d.ts",
10
10
  "README.md",
11
- "test_vector.js"
11
+ "test_vector.js",
12
+ "integration_test.js"
12
13
  ],
13
14
  "publishConfig": {
14
15
  "access": "public"
15
16
  },
16
17
  "scripts": {
17
- "test": "node test_vector.js"
18
+ "test": "node test_vector.js",
19
+ "test:integration": "node integration_test.js"
18
20
  },
19
21
  "license": "MIT",
20
22
  "dependencies": {
23
+ "oprf": "^2.0.0",
21
24
  "tweetnacl": "^1.0.3"
22
25
  }
23
26
  }