@pymthouse/builder-sdk 0.3.0 → 0.4.1-rc.1
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 +54 -28
- package/dist/{client-BHfjDvIe.d.ts → client-CauCfGa7.d.ts} +1 -1
- package/dist/{client-CvhJEhjV.d.cts → client-D1Xz-xlx.d.cts} +1 -1
- package/dist/config.cjs +0 -21
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +1 -5
- package/dist/config.d.ts +1 -5
- package/dist/config.js +1 -20
- package/dist/config.js.map +1 -1
- package/dist/device-initiate.cjs.map +1 -1
- package/dist/device-initiate.js.map +1 -1
- package/dist/device.cjs.map +1 -1
- package/dist/device.d.cts +1 -1
- package/dist/device.d.ts +1 -1
- package/dist/device.js.map +1 -1
- package/dist/env.cjs +13 -4
- package/dist/env.cjs.map +1 -1
- package/dist/env.d.cts +2 -2
- package/dist/env.d.ts +2 -2
- package/dist/env.js +13 -4
- package/dist/env.js.map +1 -1
- package/dist/index-BTDKEorK.d.ts +64 -0
- package/dist/index-BixH4VIG.d.cts +64 -0
- package/dist/index.cjs +13 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -5
- package/dist/index.d.ts +29 -5
- package/dist/index.js +13 -4
- package/dist/index.js.map +1 -1
- package/dist/{ingest-DoKJTWU9.d.ts → proxy-JrT6raU_.d.cts} +5 -42
- package/dist/{ingest-B3Yi8Tb1.d.cts → proxy-U32DFNuj.d.ts} +5 -42
- package/dist/signer/server.cjs +799 -895
- package/dist/signer/server.cjs.map +1 -1
- package/dist/signer/server.d.cts +9 -13
- package/dist/signer/server.d.ts +9 -13
- package/dist/signer/server.js +799 -893
- package/dist/signer/server.js.map +1 -1
- package/dist/signer/webhook/adapters/api-key.cjs +78 -0
- package/dist/signer/webhook/adapters/api-key.cjs.map +1 -0
- package/dist/signer/webhook/adapters/api-key.d.cts +18 -0
- package/dist/signer/webhook/adapters/api-key.d.ts +18 -0
- package/dist/signer/webhook/adapters/api-key.js +76 -0
- package/dist/signer/webhook/adapters/api-key.js.map +1 -0
- package/dist/signer/webhook/adapters/composite.cjs +60 -0
- package/dist/signer/webhook/adapters/composite.cjs.map +1 -0
- package/dist/signer/webhook/adapters/composite.d.cts +5 -0
- package/dist/signer/webhook/adapters/composite.d.ts +5 -0
- package/dist/signer/webhook/adapters/composite.js +58 -0
- package/dist/signer/webhook/adapters/composite.js.map +1 -0
- package/dist/signer/webhook/adapters/oauth1.cjs +18 -0
- package/dist/signer/webhook/adapters/oauth1.cjs.map +1 -0
- package/dist/signer/webhook/adapters/oauth1.d.cts +19 -0
- package/dist/signer/webhook/adapters/oauth1.d.ts +19 -0
- package/dist/signer/webhook/adapters/oauth1.js +16 -0
- package/dist/signer/webhook/adapters/oauth1.js.map +1 -0
- package/dist/signer/webhook/adapters/oidc.cjs +522 -0
- package/dist/signer/webhook/adapters/oidc.cjs.map +1 -0
- package/dist/signer/webhook/adapters/oidc.d.cts +4 -0
- package/dist/signer/webhook/adapters/oidc.d.ts +4 -0
- package/dist/signer/webhook/adapters/oidc.js +515 -0
- package/dist/signer/webhook/adapters/oidc.js.map +1 -0
- package/dist/signer/webhook/adapters/trusted-headers.cjs +103 -0
- package/dist/signer/webhook/adapters/trusted-headers.cjs.map +1 -0
- package/dist/signer/webhook/adapters/trusted-headers.d.cts +18 -0
- package/dist/signer/webhook/adapters/trusted-headers.d.ts +18 -0
- package/dist/signer/webhook/adapters/trusted-headers.js +99 -0
- package/dist/signer/webhook/adapters/trusted-headers.js.map +1 -0
- package/dist/signer/webhook.cjs +747 -0
- package/dist/signer/webhook.cjs.map +1 -0
- package/dist/signer/webhook.d.cts +26 -0
- package/dist/signer/webhook.d.ts +26 -0
- package/dist/signer/webhook.js +721 -0
- package/dist/signer/webhook.js.map +1 -0
- package/dist/tokens.d.cts +1 -1
- package/dist/tokens.d.ts +1 -1
- package/dist/{types-_R1AwEZp.d.cts → types-BORaHW_x.d.cts} +5 -5
- package/dist/{types-_R1AwEZp.d.ts → types-BORaHW_x.d.ts} +5 -5
- package/dist/verifier-B-WFDMz6.d.cts +48 -0
- package/dist/verifier-B-WFDMz6.d.ts +48 -0
- package/dist/verify.cjs.map +1 -1
- package/dist/verify.d.cts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js.map +1 -1
- package/package.json +30 -30
- package/dist/gateway/client/index.cjs +0 -492
- package/dist/gateway/client/index.cjs.map +0 -1
- package/dist/gateway/client/index.d.cts +0 -63
- package/dist/gateway/client/index.d.ts +0 -63
- package/dist/gateway/client/index.js +0 -489
- package/dist/gateway/client/index.js.map +0 -1
- package/dist/gateway/index.cjs +0 -16
- package/dist/gateway/index.cjs.map +0 -1
- package/dist/gateway/index.d.cts +0 -52
- package/dist/gateway/index.d.ts +0 -52
- package/dist/gateway/index.js +0 -10
- package/dist/gateway/index.js.map +0 -1
- package/dist/gateway/server/index.cjs +0 -1248
- package/dist/gateway/server/index.cjs.map +0 -1
- package/dist/gateway/server/index.d.cts +0 -31
- package/dist/gateway/server/index.d.ts +0 -31
- package/dist/gateway/server/index.js +0 -1233
- package/dist/gateway/server/index.js.map +0 -1
- package/gateway/proto/lp_rpc.proto +0 -542
package/dist/signer/server.cjs
CHANGED
|
@@ -21,8 +21,14 @@ var PmtHouseError = class extends Error {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
// src/signer/handler-errors.ts
|
|
24
|
-
function
|
|
24
|
+
function isPmtHouseError(error) {
|
|
25
25
|
if (error instanceof PmtHouseError) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return error instanceof Error && typeof error.status === "number" && typeof error.code === "string";
|
|
29
|
+
}
|
|
30
|
+
function signerHandlerErrorResponse(error) {
|
|
31
|
+
if (isPmtHouseError(error)) {
|
|
26
32
|
return new Response(
|
|
27
33
|
JSON.stringify({
|
|
28
34
|
error: error.code,
|
|
@@ -50,17 +56,14 @@ function createSignerTokenManager(options) {
|
|
|
50
56
|
const ttlRefreshRatio = options.ttlRefreshRatio ?? 0.8;
|
|
51
57
|
const cache = /* @__PURE__ */ new Map();
|
|
52
58
|
const inflight = /* @__PURE__ */ new Map();
|
|
53
|
-
function keyFor(externalUserId) {
|
|
54
|
-
return cacheKey(options.publicClientId, externalUserId);
|
|
55
|
-
}
|
|
56
59
|
function isUsable(entry, now, forceRefresh) {
|
|
57
60
|
if (forceRefresh) return false;
|
|
58
61
|
if (now >= entry.expiresAt) return false;
|
|
59
62
|
if (now >= entry.refreshAt) return false;
|
|
60
63
|
return true;
|
|
61
64
|
}
|
|
62
|
-
async function refresh(externalUserId) {
|
|
63
|
-
const key =
|
|
65
|
+
async function refresh(publicClientId, externalUserId) {
|
|
66
|
+
const key = cacheKey(publicClientId, externalUserId);
|
|
64
67
|
const existing = inflight.get(key);
|
|
65
68
|
if (existing) {
|
|
66
69
|
return existing;
|
|
@@ -81,22 +84,22 @@ function createSignerTokenManager(options) {
|
|
|
81
84
|
return promise;
|
|
82
85
|
}
|
|
83
86
|
return {
|
|
84
|
-
peek(externalUserId) {
|
|
85
|
-
return cache.get(
|
|
87
|
+
peek(publicClientId, externalUserId) {
|
|
88
|
+
return cache.get(cacheKey(publicClientId, externalUserId));
|
|
86
89
|
},
|
|
87
|
-
invalidate(externalUserId) {
|
|
88
|
-
const key =
|
|
90
|
+
invalidate(publicClientId, externalUserId) {
|
|
91
|
+
const key = cacheKey(publicClientId, externalUserId);
|
|
89
92
|
cache.delete(key);
|
|
90
93
|
inflight.delete(key);
|
|
91
94
|
},
|
|
92
|
-
async getToken(externalUserId, getOptions = {}) {
|
|
95
|
+
async getToken(publicClientId, externalUserId, getOptions = {}) {
|
|
93
96
|
const now = Date.now();
|
|
94
|
-
const key =
|
|
97
|
+
const key = cacheKey(publicClientId, externalUserId);
|
|
95
98
|
const cached = cache.get(key);
|
|
96
99
|
if (cached && isUsable(cached, now, getOptions.forceRefresh === true)) {
|
|
97
100
|
return cached;
|
|
98
101
|
}
|
|
99
|
-
return refresh(externalUserId);
|
|
102
|
+
return refresh(publicClientId, externalUserId);
|
|
100
103
|
}
|
|
101
104
|
};
|
|
102
105
|
}
|
|
@@ -214,13 +217,6 @@ async function forwardDirectSignerRequest(options) {
|
|
|
214
217
|
return fetchImpl(target, init);
|
|
215
218
|
}
|
|
216
219
|
|
|
217
|
-
// src/encoding.ts
|
|
218
|
-
function encodeClientSecretBasic(clientId, clientSecret) {
|
|
219
|
-
const raw = `${clientId}:${clientSecret}`;
|
|
220
|
-
const b64 = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(Array.from(new TextEncoder().encode(raw), (c) => String.fromCharCode(c)).join(""));
|
|
221
|
-
return `Basic ${b64}`;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
220
|
// src/string-utils.ts
|
|
225
221
|
function stripTrailingSlashes(value) {
|
|
226
222
|
let end = value.length;
|
|
@@ -252,986 +248,896 @@ function stripIssuerOriginFromOidcUrl(issuerUrl) {
|
|
|
252
248
|
base = stripSuffixIgnoreCase(base, "/oidc");
|
|
253
249
|
return stripTrailingSlashes(base);
|
|
254
250
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
function
|
|
258
|
-
return
|
|
259
|
-
requestId: input.snapshot.requestId,
|
|
260
|
-
externalUserId: input.externalUserId,
|
|
261
|
-
networkFeeUsdMicros: input.snapshot.computedFeeUsdMicros.toString(),
|
|
262
|
-
feeWei: input.snapshot.computedFeeWei,
|
|
263
|
-
pixels: input.snapshot.pixels,
|
|
264
|
-
pipeline: input.snapshot.pipeline,
|
|
265
|
-
modelId: input.snapshot.modelId,
|
|
266
|
-
gatewayRequestId: input.gatewayRequestId,
|
|
267
|
-
ethUsdPrice: input.snapshot.ethUsdPrice,
|
|
268
|
-
ethUsdRoundId: input.snapshot.ethUsdRoundId,
|
|
269
|
-
ethUsdObservedAt: input.snapshot.ethUsdObservedAt
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
function ingestUrl(issuerUrl, publicClientId) {
|
|
273
|
-
const origin = new URL(stripTrailingSlashes(issuerUrl)).origin;
|
|
274
|
-
return `${origin}/api/v1/apps/${encodeURIComponent(publicClientId)}/usage/signed-tickets`;
|
|
251
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
252
|
+
var discoveryCache = /* @__PURE__ */ new Map();
|
|
253
|
+
function normalizedIssuerKey(issuerUrl) {
|
|
254
|
+
return stripTrailingSlashes(issuerUrl);
|
|
275
255
|
}
|
|
276
|
-
async function
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
256
|
+
async function loadAuthorizationServer(issuerUrl, fetchImpl, options = {}) {
|
|
257
|
+
const key = normalizedIssuerKey(issuerUrl);
|
|
258
|
+
const now = Date.now();
|
|
259
|
+
const cached = discoveryCache.get(key);
|
|
260
|
+
if (!options.force && cached && now - cached.fetchedAt < CACHE_TTL_MS) {
|
|
261
|
+
return cached.as;
|
|
262
|
+
}
|
|
263
|
+
const issuerIdentifier = new URL(key);
|
|
264
|
+
const discoveryOpts = {
|
|
265
|
+
algorithm: "oidc",
|
|
266
|
+
[oauth4webapi.customFetch]: fetchImpl
|
|
267
|
+
};
|
|
268
|
+
if (options.allowInsecureHttp) {
|
|
269
|
+
discoveryOpts[oauth4webapi.allowInsecureRequests] = true;
|
|
280
270
|
}
|
|
271
|
+
let response;
|
|
281
272
|
try {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return {};
|
|
273
|
+
response = await oauth4webapi.discoveryRequest(issuerIdentifier, discoveryOpts);
|
|
274
|
+
} catch (e) {
|
|
275
|
+
throw mapDiscoveryNetworkError(e);
|
|
286
276
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
method: "POST",
|
|
293
|
-
headers: {
|
|
294
|
-
Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
|
|
295
|
-
"Content-Type": "application/json",
|
|
296
|
-
Accept: "application/json"
|
|
297
|
-
},
|
|
298
|
-
body: JSON.stringify(options.ticket),
|
|
299
|
-
cache: "no-store"
|
|
300
|
-
});
|
|
301
|
-
const body = await readJsonResponse(response);
|
|
302
|
-
if (!response.ok) {
|
|
303
|
-
const message = typeof body.error === "string" ? body.error : `Signed-ticket ingest failed (${response.status})`;
|
|
304
|
-
throw new PmtHouseError(message, {
|
|
305
|
-
status: response.status,
|
|
306
|
-
code: "ingest_failed",
|
|
307
|
-
details: body
|
|
308
|
-
});
|
|
277
|
+
let as;
|
|
278
|
+
try {
|
|
279
|
+
as = await oauth4webapi.processDiscoveryResponse(issuerIdentifier, response);
|
|
280
|
+
} catch (e) {
|
|
281
|
+
throw mapOAuthDiscoveryError(e);
|
|
309
282
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
duplicate: Boolean(body.duplicate),
|
|
313
|
-
source: body.source === "openmeter" ? "openmeter" : "disabled"
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// src/signer/proxy.ts
|
|
318
|
-
var HTTP_DMZ_TOKEN_MAX_ENTRIES = 100;
|
|
319
|
-
var HTTP_DMZ_TOKEN_TTL_MS = 3.5 * 60 * 1e3;
|
|
320
|
-
var DEFAULT_PROBE_SUBJECT = "signer-reachability-probe";
|
|
321
|
-
var httpDmzTokenCache = /* @__PURE__ */ new Map();
|
|
322
|
-
function normalizeSignerBaseUrl(base) {
|
|
323
|
-
return stripTrailingSlashes(base);
|
|
283
|
+
discoveryCache.set(key, { as, fetchedAt: now });
|
|
284
|
+
return as;
|
|
324
285
|
}
|
|
325
|
-
function
|
|
326
|
-
if (
|
|
327
|
-
return
|
|
286
|
+
function mapOAuthDiscoveryError(error) {
|
|
287
|
+
if (error instanceof PmtHouseError) {
|
|
288
|
+
return error;
|
|
328
289
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
290
|
+
if (error instanceof Error) {
|
|
291
|
+
return new PmtHouseError(error.message, {
|
|
292
|
+
status: 500,
|
|
293
|
+
code: "oidc_discovery_invalid",
|
|
294
|
+
details: { cause: error.cause }
|
|
295
|
+
});
|
|
334
296
|
}
|
|
335
|
-
|
|
336
|
-
|
|
297
|
+
return new PmtHouseError("OIDC discovery failed", {
|
|
298
|
+
status: 500,
|
|
299
|
+
code: "oidc_discovery_invalid"
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function mapDiscoveryNetworkError(error) {
|
|
303
|
+
if (error instanceof PmtHouseError) {
|
|
304
|
+
return error;
|
|
337
305
|
}
|
|
338
|
-
if (
|
|
339
|
-
return
|
|
306
|
+
if (error instanceof Error) {
|
|
307
|
+
return new PmtHouseError(`Failed to load OIDC discovery: ${error.message}`, {
|
|
308
|
+
status: 502,
|
|
309
|
+
code: "oidc_discovery_failed"
|
|
310
|
+
});
|
|
340
311
|
}
|
|
341
|
-
return
|
|
312
|
+
return new PmtHouseError("Failed to load OIDC discovery", {
|
|
313
|
+
status: 502,
|
|
314
|
+
code: "oidc_discovery_failed"
|
|
315
|
+
});
|
|
342
316
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
const port = rawPort === legacyBareSignerPort ? 8080 : rawPort;
|
|
350
|
-
const base = input.envUrl?.trim() || input.storedUrl?.trim() || `http://127.0.0.1:${port}`;
|
|
351
|
-
return normalizeSignerBaseUrl(base);
|
|
317
|
+
|
|
318
|
+
// src/encoding.ts
|
|
319
|
+
function encodeClientSecretBasic(clientId, clientSecret) {
|
|
320
|
+
const raw = `${clientId}:${clientSecret}`;
|
|
321
|
+
const b64 = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(Array.from(new TextEncoder().encode(raw), (c) => String.fromCharCode(c)).join(""));
|
|
322
|
+
return `Basic ${b64}`;
|
|
352
323
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
httpDmzTokenCache.delete(cacheKey2);
|
|
359
|
-
httpDmzTokenCache.set(cacheKey2, cached);
|
|
360
|
-
return cached.token;
|
|
324
|
+
|
|
325
|
+
// src/signer/fetch-json.ts
|
|
326
|
+
function oauthFailureDescription(parsed, failureLabel, status) {
|
|
327
|
+
if (typeof parsed.error_description === "string") {
|
|
328
|
+
return parsed.error_description;
|
|
361
329
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (httpDmzTokenCache.size > HTTP_DMZ_TOKEN_MAX_ENTRIES) {
|
|
365
|
-
const oldest = httpDmzTokenCache.keys().next().value;
|
|
366
|
-
if (oldest !== void 0) {
|
|
367
|
-
httpDmzTokenCache.delete(oldest);
|
|
368
|
-
}
|
|
330
|
+
if (typeof parsed.error === "string") {
|
|
331
|
+
return parsed.error;
|
|
369
332
|
}
|
|
370
|
-
return
|
|
333
|
+
return `${failureLabel} (${status})`;
|
|
371
334
|
}
|
|
372
|
-
async function
|
|
335
|
+
async function readJsonObjectFromResponse(response, options) {
|
|
373
336
|
const text = await response.text();
|
|
374
|
-
|
|
375
|
-
return {};
|
|
376
|
-
}
|
|
337
|
+
let parsed;
|
|
377
338
|
try {
|
|
378
|
-
|
|
339
|
+
parsed = text ? JSON.parse(text) : {};
|
|
379
340
|
} catch {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
};
|
|
341
|
+
throw new PmtHouseError(options.invalidJsonMessage, {
|
|
342
|
+
status: 502,
|
|
343
|
+
code: options.invalidJsonCode,
|
|
344
|
+
details: { status: response.status }
|
|
345
|
+
});
|
|
385
346
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const conflict = values.find((entry) => entry.value !== first?.value);
|
|
394
|
-
if (first && conflict) {
|
|
395
|
-
return {
|
|
396
|
-
ok: false,
|
|
397
|
-
message: `Conflicting ${keys.join("/")} in request body`
|
|
398
|
-
};
|
|
347
|
+
if (!response.ok) {
|
|
348
|
+
const description = oauthFailureDescription(parsed, options.failureLabel, response.status);
|
|
349
|
+
throw new PmtHouseError(description, {
|
|
350
|
+
status: response.status,
|
|
351
|
+
code: typeof parsed.error === "string" ? parsed.error : options.defaultErrorCode,
|
|
352
|
+
details: parsed
|
|
353
|
+
});
|
|
399
354
|
}
|
|
400
|
-
return
|
|
355
|
+
return parsed;
|
|
401
356
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
return void 0;
|
|
412
|
-
};
|
|
413
|
-
const values = keys.map((key) => {
|
|
414
|
-
const value = parseNum(body[key]);
|
|
415
|
-
return value === void 0 ? null : { key, value };
|
|
416
|
-
}).filter((entry) => entry !== null);
|
|
417
|
-
const first = values[0];
|
|
418
|
-
const conflict = values.find((entry) => entry.value !== first?.value);
|
|
419
|
-
if (first && conflict) {
|
|
420
|
-
return {
|
|
421
|
-
ok: false,
|
|
422
|
-
message: `Conflicting ${keys.join("/")} in request body`
|
|
423
|
-
};
|
|
357
|
+
|
|
358
|
+
// src/signer/json-fields.ts
|
|
359
|
+
function readStringField(body, key, errorCode, messagePrefix = "Response") {
|
|
360
|
+
const value = body[key];
|
|
361
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
362
|
+
throw new PmtHouseError(`${messagePrefix} missing ${key}`, {
|
|
363
|
+
status: 502,
|
|
364
|
+
code: errorCode
|
|
365
|
+
});
|
|
424
366
|
}
|
|
425
|
-
return
|
|
367
|
+
return value.trim();
|
|
426
368
|
}
|
|
427
|
-
function
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
return String(Math.trunc(value));
|
|
435
|
-
}
|
|
369
|
+
function readExpiresIn(body, errorCode) {
|
|
370
|
+
const expiresIn = body.expires_in;
|
|
371
|
+
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
372
|
+
throw new PmtHouseError("Response missing expires_in", {
|
|
373
|
+
status: 502,
|
|
374
|
+
code: errorCode
|
|
375
|
+
});
|
|
436
376
|
}
|
|
437
|
-
return
|
|
377
|
+
return Math.floor(expiresIn);
|
|
438
378
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
379
|
+
|
|
380
|
+
// src/signer/mint-token.ts
|
|
381
|
+
var SIGN_MINT_USER_TOKEN_SCOPE = "sign:mint_user_token";
|
|
382
|
+
var LIVEPEER_REMOTE_SIGNER_AUDIENCE = "livepeer-remote-signer";
|
|
383
|
+
var DEFAULT_TTL_REFRESH_RATIO = 0.8;
|
|
384
|
+
var TOKEN_RESPONSE_ERROR = "invalid_token_response";
|
|
385
|
+
function signerJwtAudience(issuerUrl) {
|
|
386
|
+
return stripTrailingSlashes(issuerUrl);
|
|
387
|
+
}
|
|
388
|
+
function parseMintUserSignerTokenResponse(body, ttlRefreshRatio = DEFAULT_TTL_REFRESH_RATIO) {
|
|
389
|
+
const accessToken = readStringField(body, "access_token", TOKEN_RESPONSE_ERROR, "Token response");
|
|
390
|
+
const expiresIn = readExpiresIn(body, TOKEN_RESPONSE_ERROR);
|
|
391
|
+
const balanceUsdMicros = readStringField(
|
|
392
|
+
body,
|
|
393
|
+
"balanceUsdMicros",
|
|
394
|
+
TOKEN_RESPONSE_ERROR,
|
|
395
|
+
"Token response"
|
|
396
|
+
);
|
|
397
|
+
const lifetimeGrantedUsdMicros = readStringField(
|
|
398
|
+
body,
|
|
399
|
+
"lifetimeGrantedUsdMicros",
|
|
400
|
+
TOKEN_RESPONSE_ERROR,
|
|
401
|
+
"Token response"
|
|
402
|
+
);
|
|
403
|
+
const now = Date.now();
|
|
404
|
+
const expiresAt = now + expiresIn * 1e3;
|
|
405
|
+
const refreshAt = now + Math.floor(expiresIn * 1e3 * ttlRefreshRatio);
|
|
461
406
|
return {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
ethUsdObservedAt: pickString(usage, "eth_usd_updated_at", "ethUsdUpdatedAt", "eth_usd_observed_at") || void 0,
|
|
468
|
-
pixels: pickString(usage, "pixels") || void 0,
|
|
469
|
-
billableSecs: pickString(usage, "billable_secs", "billableSecs") || void 0,
|
|
470
|
-
pipeline: pickString(usage, "pipeline") || void 0,
|
|
471
|
-
modelId: pickString(usage, "model_id", "modelId") || void 0
|
|
407
|
+
jwt: accessToken,
|
|
408
|
+
expiresAt,
|
|
409
|
+
refreshAt,
|
|
410
|
+
balanceUsdMicros,
|
|
411
|
+
lifetimeGrantedUsdMicros
|
|
472
412
|
};
|
|
473
413
|
}
|
|
474
|
-
function
|
|
475
|
-
if (body !== null && typeof body === "object" && !Array.isArray(body)) {
|
|
476
|
-
delete body.usage;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
async function forwardToSigner(options) {
|
|
414
|
+
async function mintUserSignerToken(options) {
|
|
480
415
|
const fetchImpl = options.fetch ?? fetch;
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
"http",
|
|
492
|
-
options.getDmzToken
|
|
493
|
-
);
|
|
494
|
-
headers.Authorization = `Bearer ${token}`;
|
|
416
|
+
const issuerUrl = stripTrailingSlashes(options.issuerUrl);
|
|
417
|
+
const as = await loadAuthorizationServer(issuerUrl, fetchImpl, {
|
|
418
|
+
allowInsecureHttp: options.allowInsecureHttp
|
|
419
|
+
});
|
|
420
|
+
const tokenEndpoint = as.token_endpoint;
|
|
421
|
+
if (!tokenEndpoint) {
|
|
422
|
+
throw new PmtHouseError("OIDC discovery document is missing token_endpoint", {
|
|
423
|
+
status: 500,
|
|
424
|
+
code: "oidc_discovery_invalid"
|
|
425
|
+
});
|
|
495
426
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
427
|
+
const audience = signerJwtAudience(issuerUrl);
|
|
428
|
+
const body = new URLSearchParams({
|
|
429
|
+
grant_type: "client_credentials",
|
|
430
|
+
scope: SIGN_MINT_USER_TOKEN_SCOPE,
|
|
431
|
+
external_user_id: options.externalUserId,
|
|
432
|
+
audience
|
|
433
|
+
});
|
|
434
|
+
const response = await fetchImpl(tokenEndpoint, {
|
|
435
|
+
method: "POST",
|
|
436
|
+
headers: {
|
|
437
|
+
Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
|
|
438
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
439
|
+
Accept: "application/json"
|
|
440
|
+
},
|
|
441
|
+
body: body.toString(),
|
|
442
|
+
cache: "no-store"
|
|
443
|
+
});
|
|
444
|
+
const parsed = await readJsonObjectFromResponse(response, {
|
|
445
|
+
invalidJsonMessage: "Token endpoint returned invalid JSON",
|
|
446
|
+
invalidJsonCode: TOKEN_RESPONSE_ERROR,
|
|
447
|
+
failureLabel: "Token mint failed",
|
|
448
|
+
defaultErrorCode: "token_mint_failed"
|
|
449
|
+
});
|
|
450
|
+
return parseMintUserSignerTokenResponse(parsed);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/signer/device-exchange.ts
|
|
454
|
+
var TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange";
|
|
455
|
+
var SUBJECT_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
|
|
456
|
+
var EXCHANGE_RESPONSE_ERROR = "invalid_exchange_response";
|
|
457
|
+
function extractSignerAccessTokenFromExchangeBody(body) {
|
|
458
|
+
const tokenObj = body.token;
|
|
459
|
+
if (tokenObj !== null && typeof tokenObj === "object" && !Array.isArray(tokenObj)) {
|
|
460
|
+
const nested = tokenObj;
|
|
461
|
+
for (const key of ["accessToken", "access_token"]) {
|
|
462
|
+
const value = nested[key];
|
|
463
|
+
if (typeof value === "string" && value.trim()) {
|
|
464
|
+
return value.trim();
|
|
500
465
|
}
|
|
501
466
|
}
|
|
502
467
|
}
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
signal: controller.signal
|
|
509
|
-
});
|
|
510
|
-
return {
|
|
511
|
-
response,
|
|
512
|
-
requestUrl: url,
|
|
513
|
-
authorizationHeader: headers.Authorization
|
|
514
|
-
};
|
|
515
|
-
} finally {
|
|
516
|
-
clearTimeout(timeout);
|
|
468
|
+
for (const key of ["accessToken", "access_token"]) {
|
|
469
|
+
const value = body[key];
|
|
470
|
+
if (typeof value === "string" && value.trim()) {
|
|
471
|
+
return value.trim();
|
|
472
|
+
}
|
|
517
473
|
}
|
|
474
|
+
throw new PmtHouseError("Device exchange response missing signer access token", {
|
|
475
|
+
status: 502,
|
|
476
|
+
code: "invalid_exchange_response"
|
|
477
|
+
});
|
|
518
478
|
}
|
|
519
|
-
function
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
479
|
+
function normalizeDeviceExchangeResponse(minted, options) {
|
|
480
|
+
const scope = minted.scope.trim() || "sign:job";
|
|
481
|
+
const body = {
|
|
482
|
+
access_token: minted.access_token,
|
|
483
|
+
token_type: "Bearer",
|
|
484
|
+
expires_in: minted.expires_in,
|
|
485
|
+
scope,
|
|
486
|
+
balanceUsdMicros: minted.balanceUsdMicros,
|
|
487
|
+
lifetimeGrantedUsdMicros: minted.lifetimeGrantedUsdMicros,
|
|
488
|
+
token: {
|
|
489
|
+
accessToken: minted.access_token,
|
|
490
|
+
access_token: minted.access_token,
|
|
491
|
+
expiresIn: minted.expires_in,
|
|
492
|
+
expires_in: minted.expires_in,
|
|
493
|
+
scope,
|
|
494
|
+
balanceUsdMicros: minted.balanceUsdMicros,
|
|
495
|
+
lifetimeGrantedUsdMicros: minted.lifetimeGrantedUsdMicros
|
|
496
|
+
}
|
|
527
497
|
};
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
async function fetchSignerStatus(ctx, headers) {
|
|
533
|
-
const response = await ctx.fetchImpl(`${ctx.signerUrl}/status`, {
|
|
534
|
-
headers,
|
|
535
|
-
signal: AbortSignal.timeout(ctx.timeoutMs)
|
|
536
|
-
});
|
|
537
|
-
if (!response.ok) {
|
|
538
|
-
return { ok: false };
|
|
498
|
+
const signerUrl = options?.signerUrl?.trim();
|
|
499
|
+
if (signerUrl) {
|
|
500
|
+
body.signerUrl = signerUrl;
|
|
539
501
|
}
|
|
540
|
-
|
|
541
|
-
const ethAddress = typeof data.Address === "string" && data.Address || typeof data.address === "string" && data.address || void 0;
|
|
542
|
-
return { ok: true, ethAddress };
|
|
502
|
+
return body;
|
|
543
503
|
}
|
|
544
|
-
async function
|
|
545
|
-
|
|
546
|
-
return null;
|
|
547
|
-
}
|
|
504
|
+
async function parseDeviceExchangeRequestBody(request) {
|
|
505
|
+
let body;
|
|
548
506
|
try {
|
|
549
|
-
|
|
550
|
-
const { ok, ethAddress } = await fetchSignerStatus(ctx, {
|
|
551
|
-
Authorization: `Bearer ${token}`
|
|
552
|
-
});
|
|
553
|
-
return ok ? reachableResult(ethAddress) : null;
|
|
507
|
+
body = await request.json();
|
|
554
508
|
} catch {
|
|
555
|
-
|
|
509
|
+
throw new PmtHouseError("Request body must be JSON", {
|
|
510
|
+
status: 400,
|
|
511
|
+
code: "invalid_request"
|
|
512
|
+
});
|
|
556
513
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
} catch {
|
|
563
|
-
return null;
|
|
514
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
515
|
+
throw new PmtHouseError("Request body must be a JSON object", {
|
|
516
|
+
status: 400,
|
|
517
|
+
code: "invalid_request"
|
|
518
|
+
});
|
|
564
519
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
headers: {
|
|
572
|
-
Authorization: `Bearer ${token}`,
|
|
573
|
-
"Content-Type": "application/json"
|
|
574
|
-
},
|
|
575
|
-
body: "{}",
|
|
576
|
-
signal: AbortSignal.timeout(ctx.timeoutMs)
|
|
520
|
+
const record = body;
|
|
521
|
+
const deviceTokenRaw = record.deviceToken;
|
|
522
|
+
if (typeof deviceTokenRaw !== "string" || !deviceTokenRaw.trim()) {
|
|
523
|
+
throw new PmtHouseError("Request body must include deviceToken", {
|
|
524
|
+
status: 400,
|
|
525
|
+
code: "invalid_request"
|
|
577
526
|
});
|
|
578
|
-
return response.ok;
|
|
579
|
-
} catch {
|
|
580
|
-
return false;
|
|
581
527
|
}
|
|
528
|
+
const deviceToken = deviceTokenRaw.trim();
|
|
529
|
+
const scope = typeof record.scope === "string" && record.scope.trim() ? record.scope.trim() : void 0;
|
|
530
|
+
const clientId = typeof record.clientId === "string" && record.clientId.trim() ? record.clientId.trim() : void 0;
|
|
531
|
+
return { deviceToken, scope, clientId };
|
|
582
532
|
}
|
|
583
|
-
async function
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
533
|
+
async function mintSignerTokenFromDeviceToken(options) {
|
|
534
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
535
|
+
const issuerUrl = stripTrailingSlashes(options.issuerUrl);
|
|
536
|
+
const as = await loadAuthorizationServer(issuerUrl, fetchImpl, {
|
|
537
|
+
allowInsecureHttp: options.allowInsecureHttp
|
|
538
|
+
});
|
|
539
|
+
const tokenEndpoint = as.token_endpoint;
|
|
540
|
+
if (!tokenEndpoint) {
|
|
541
|
+
throw new PmtHouseError("OIDC discovery document is missing token_endpoint", {
|
|
542
|
+
status: 500,
|
|
543
|
+
code: "oidc_discovery_invalid"
|
|
544
|
+
});
|
|
587
545
|
}
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
return { reachable: false };
|
|
596
|
-
}
|
|
597
|
-
async function probeSignerHttpReachability(options) {
|
|
598
|
-
const ctx = createSignerProbeContext(options);
|
|
599
|
-
try {
|
|
600
|
-
const health = await ctx.fetchImpl(`${ctx.signerUrl}/healthz`, {
|
|
601
|
-
signal: AbortSignal.timeout(ctx.timeoutMs)
|
|
602
|
-
});
|
|
603
|
-
if (health.ok) {
|
|
604
|
-
return runSignerReachabilityProbes(ctx);
|
|
605
|
-
}
|
|
606
|
-
} catch {
|
|
607
|
-
}
|
|
608
|
-
return runSignerReachabilityProbes(ctx);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// src/signer/types.ts
|
|
612
|
-
function resolvesToHostedMetering(mode) {
|
|
613
|
-
return mode === "pymthouse_hosted";
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// src/signer/metering.ts
|
|
617
|
-
async function forwardWithOptionalMetering(input) {
|
|
618
|
-
const upstream = await input.forward();
|
|
619
|
-
const mode = input.config.metering?.mode;
|
|
620
|
-
if (!resolvesToHostedMetering(mode)) {
|
|
621
|
-
return upstream;
|
|
622
|
-
}
|
|
623
|
-
const body = await readSignerUpstreamBody(upstream);
|
|
624
|
-
const snapshot = upstream.ok && body !== null && typeof body === "object" ? parseSignerUsageSnapshot(body) : null;
|
|
625
|
-
if (snapshot) {
|
|
626
|
-
stripSignerUsageFromResponse(body);
|
|
627
|
-
}
|
|
628
|
-
if (upstream.ok && snapshot && snapshot.computedFeeUsdMicros > 0n) {
|
|
629
|
-
try {
|
|
630
|
-
await ingestSignedTicket({
|
|
631
|
-
issuerUrl: input.config.pymthouseIssuerUrl,
|
|
632
|
-
publicClientId: input.config.pymthouseClientId,
|
|
633
|
-
m2mClientId: input.config.pymthouseM2MClientId,
|
|
634
|
-
m2mClientSecret: input.config.pymthouseM2MClientSecret,
|
|
635
|
-
ticket: signerSnapshotToIngestPayload({
|
|
636
|
-
snapshot,
|
|
637
|
-
externalUserId: input.externalUserId
|
|
638
|
-
}),
|
|
639
|
-
fetch: input.config.fetch
|
|
640
|
-
});
|
|
641
|
-
} catch (err) {
|
|
642
|
-
console.warn("[builder-sdk] signed-ticket ingest failed:", err);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
const headers = new Headers(upstream.headers);
|
|
646
|
-
if (!headers.has("content-type")) {
|
|
647
|
-
headers.set("Content-Type", "application/json");
|
|
648
|
-
}
|
|
649
|
-
return new Response(JSON.stringify(body), {
|
|
650
|
-
status: upstream.status,
|
|
651
|
-
statusText: upstream.statusText,
|
|
652
|
-
headers
|
|
546
|
+
const audience = options.audience?.trim() || signerJwtAudience(issuerUrl);
|
|
547
|
+
const params = new URLSearchParams({
|
|
548
|
+
grant_type: TOKEN_EXCHANGE_GRANT,
|
|
549
|
+
subject_token: options.deviceToken,
|
|
550
|
+
subject_token_type: SUBJECT_ACCESS_TOKEN_TYPE,
|
|
551
|
+
audience,
|
|
552
|
+
resource: audience
|
|
653
553
|
});
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
var discoveryCache = /* @__PURE__ */ new Map();
|
|
657
|
-
function normalizedIssuerKey(issuerUrl) {
|
|
658
|
-
return stripTrailingSlashes(issuerUrl);
|
|
659
|
-
}
|
|
660
|
-
async function loadAuthorizationServer(issuerUrl, fetchImpl, options = {}) {
|
|
661
|
-
const key = normalizedIssuerKey(issuerUrl);
|
|
662
|
-
const now = Date.now();
|
|
663
|
-
const cached = discoveryCache.get(key);
|
|
664
|
-
if (!options.force && cached && now - cached.fetchedAt < CACHE_TTL_MS) {
|
|
665
|
-
return cached.as;
|
|
554
|
+
if (options.scope?.trim()) {
|
|
555
|
+
params.set("scope", options.scope.trim());
|
|
666
556
|
}
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
557
|
+
const response = await fetchImpl(tokenEndpoint, {
|
|
558
|
+
method: "POST",
|
|
559
|
+
headers: {
|
|
560
|
+
Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
|
|
561
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
562
|
+
Accept: "application/json"
|
|
563
|
+
},
|
|
564
|
+
body: params.toString(),
|
|
565
|
+
cache: "no-store"
|
|
566
|
+
});
|
|
567
|
+
const parsed = await readJsonObjectFromResponse(response, {
|
|
568
|
+
invalidJsonMessage: "Token endpoint returned invalid JSON",
|
|
569
|
+
invalidJsonCode: "invalid_token_response",
|
|
570
|
+
failureLabel: "Signer JWT exchange failed",
|
|
571
|
+
defaultErrorCode: "token_exchange_failed"
|
|
572
|
+
});
|
|
573
|
+
const cached = parseMintUserSignerTokenResponse(parsed);
|
|
574
|
+
return {
|
|
575
|
+
access_token: cached.jwt,
|
|
576
|
+
expires_in: readExpiresIn(parsed, EXCHANGE_RESPONSE_ERROR),
|
|
577
|
+
scope: readStringField(parsed, "scope", EXCHANGE_RESPONSE_ERROR),
|
|
578
|
+
balanceUsdMicros: cached.balanceUsdMicros,
|
|
579
|
+
lifetimeGrantedUsdMicros: cached.lifetimeGrantedUsdMicros
|
|
671
580
|
};
|
|
672
|
-
if (options.allowInsecureHttp) {
|
|
673
|
-
discoveryOpts[oauth4webapi.allowInsecureRequests] = true;
|
|
674
|
-
}
|
|
675
|
-
let response;
|
|
676
|
-
try {
|
|
677
|
-
response = await oauth4webapi.discoveryRequest(issuerIdentifier, discoveryOpts);
|
|
678
|
-
} catch (e) {
|
|
679
|
-
throw mapDiscoveryNetworkError(e);
|
|
680
|
-
}
|
|
681
|
-
let as;
|
|
682
|
-
try {
|
|
683
|
-
as = await oauth4webapi.processDiscoveryResponse(issuerIdentifier, response);
|
|
684
|
-
} catch (e) {
|
|
685
|
-
throw mapOAuthDiscoveryError(e);
|
|
686
|
-
}
|
|
687
|
-
discoveryCache.set(key, { as, fetchedAt: now });
|
|
688
|
-
return as;
|
|
689
581
|
}
|
|
690
|
-
function
|
|
691
|
-
|
|
692
|
-
|
|
582
|
+
async function exchangeDeviceTokenForSigner(options) {
|
|
583
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
584
|
+
const url = `${stripTrailingSlashes(options.facadeUrl)}/api/signer/device/exchange`;
|
|
585
|
+
const body = { deviceToken: options.deviceToken };
|
|
586
|
+
if (options.scope?.trim()) {
|
|
587
|
+
body.scope = options.scope.trim();
|
|
693
588
|
}
|
|
694
|
-
if (
|
|
695
|
-
|
|
696
|
-
status: 500,
|
|
697
|
-
code: "oidc_discovery_invalid",
|
|
698
|
-
details: { cause: error.cause }
|
|
699
|
-
});
|
|
589
|
+
if (options.clientId?.trim()) {
|
|
590
|
+
body.clientId = options.clientId.trim();
|
|
700
591
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
592
|
+
const response = await fetchImpl(url, {
|
|
593
|
+
method: "POST",
|
|
594
|
+
headers: {
|
|
595
|
+
"Content-Type": "application/json",
|
|
596
|
+
Accept: "application/json"
|
|
597
|
+
},
|
|
598
|
+
body: JSON.stringify(body),
|
|
599
|
+
cache: "no-store"
|
|
600
|
+
});
|
|
601
|
+
const parsed = await readJsonObjectFromResponse(response, {
|
|
602
|
+
invalidJsonMessage: "Device exchange returned invalid JSON",
|
|
603
|
+
invalidJsonCode: EXCHANGE_RESPONSE_ERROR,
|
|
604
|
+
failureLabel: "Device exchange failed",
|
|
605
|
+
defaultErrorCode: "device_exchange_failed"
|
|
704
606
|
});
|
|
607
|
+
const accessToken = extractSignerAccessTokenFromExchangeBody(parsed);
|
|
608
|
+
const signerUrlRaw = parsed.signerUrl ?? parsed.signer_url;
|
|
609
|
+
const signerUrl = typeof signerUrlRaw === "string" && signerUrlRaw.trim() ? signerUrlRaw.trim() : void 0;
|
|
610
|
+
return normalizeDeviceExchangeResponse(
|
|
611
|
+
{
|
|
612
|
+
access_token: accessToken,
|
|
613
|
+
expires_in: readExpiresIn(parsed, EXCHANGE_RESPONSE_ERROR),
|
|
614
|
+
scope: typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : "sign:job",
|
|
615
|
+
balanceUsdMicros: typeof parsed.balanceUsdMicros === "string" ? parsed.balanceUsdMicros : "0",
|
|
616
|
+
lifetimeGrantedUsdMicros: typeof parsed.lifetimeGrantedUsdMicros === "string" ? parsed.lifetimeGrantedUsdMicros : "0"
|
|
617
|
+
},
|
|
618
|
+
{ signerUrl }
|
|
619
|
+
);
|
|
705
620
|
}
|
|
706
|
-
function
|
|
707
|
-
if (
|
|
708
|
-
return
|
|
709
|
-
}
|
|
710
|
-
if (error instanceof Error) {
|
|
711
|
-
return new PmtHouseError(`Failed to load OIDC discovery: ${error.message}`, {
|
|
712
|
-
status: 502,
|
|
713
|
-
code: "oidc_discovery_failed"
|
|
714
|
-
});
|
|
621
|
+
function resolveMint(config) {
|
|
622
|
+
if ("mint" in config && typeof config.mint === "function") {
|
|
623
|
+
return config.mint;
|
|
715
624
|
}
|
|
716
|
-
return
|
|
717
|
-
|
|
718
|
-
|
|
625
|
+
return (deviceToken, context) => mintSignerTokenFromDeviceToken({
|
|
626
|
+
issuerUrl: config.issuerUrl,
|
|
627
|
+
m2mClientId: config.m2mClientId,
|
|
628
|
+
m2mClientSecret: config.m2mClientSecret,
|
|
629
|
+
deviceToken,
|
|
630
|
+
scope: context.scope,
|
|
631
|
+
audience: config.audience,
|
|
632
|
+
fetch: config.fetch,
|
|
633
|
+
allowInsecureHttp: config.allowInsecureHttp
|
|
719
634
|
});
|
|
720
635
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
if (typeof parsed.error_description === "string") {
|
|
725
|
-
return parsed.error_description;
|
|
636
|
+
function resolveSignerUrlFromConfig(config) {
|
|
637
|
+
if ("signerUrl" in config && typeof config.signerUrl === "string" && config.signerUrl.trim()) {
|
|
638
|
+
return config.signerUrl.trim();
|
|
726
639
|
}
|
|
727
|
-
if (typeof
|
|
728
|
-
return
|
|
640
|
+
if ("getSignerUrl" in config && typeof config.getSignerUrl === "function") {
|
|
641
|
+
return config.getSignerUrl();
|
|
729
642
|
}
|
|
730
|
-
return
|
|
643
|
+
return void 0;
|
|
731
644
|
}
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
|
|
645
|
+
function createDeviceExchangeHandler(config) {
|
|
646
|
+
const mint = resolveMint(config);
|
|
647
|
+
return async function deviceExchangeHandler(request) {
|
|
648
|
+
try {
|
|
649
|
+
if (request.method !== "POST") {
|
|
650
|
+
return new Response(JSON.stringify({ error: "method_not_allowed" }), {
|
|
651
|
+
status: 405,
|
|
652
|
+
headers: { "Content-Type": "application/json" }
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
const parsed = await parseDeviceExchangeRequestBody(request);
|
|
656
|
+
const minted = await mint(parsed.deviceToken, {
|
|
657
|
+
scope: parsed.scope,
|
|
658
|
+
clientId: parsed.clientId
|
|
659
|
+
});
|
|
660
|
+
const signerUrlValue = await resolveSignerUrlFromConfig(config);
|
|
661
|
+
const body = normalizeDeviceExchangeResponse(minted, {
|
|
662
|
+
signerUrl: typeof signerUrlValue === "string" ? signerUrlValue : void 0
|
|
663
|
+
});
|
|
664
|
+
return new Response(JSON.stringify(body), {
|
|
665
|
+
status: 200,
|
|
666
|
+
headers: {
|
|
667
|
+
"Content-Type": "application/json",
|
|
668
|
+
"Cache-Control": "no-store"
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
} catch (error) {
|
|
672
|
+
return signerHandlerErrorResponse(error);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/signer/api-key-exchange.ts
|
|
678
|
+
var EXCHANGE_RESPONSE_ERROR2 = "invalid_exchange_response";
|
|
679
|
+
async function parseApiKeyExchangeRequestBody(request) {
|
|
680
|
+
let body;
|
|
735
681
|
try {
|
|
736
|
-
|
|
682
|
+
body = await request.json();
|
|
737
683
|
} catch {
|
|
738
|
-
throw new PmtHouseError(
|
|
739
|
-
status:
|
|
740
|
-
code:
|
|
741
|
-
details: { status: response.status }
|
|
684
|
+
throw new PmtHouseError("Request body must be JSON", {
|
|
685
|
+
status: 400,
|
|
686
|
+
code: "invalid_request"
|
|
742
687
|
});
|
|
743
688
|
}
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
code: typeof parsed.error === "string" ? parsed.error : options.defaultErrorCode,
|
|
749
|
-
details: parsed
|
|
689
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
690
|
+
throw new PmtHouseError("Request body must be a JSON object", {
|
|
691
|
+
status: 400,
|
|
692
|
+
code: "invalid_request"
|
|
750
693
|
});
|
|
751
694
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
759
|
-
throw new PmtHouseError(`${messagePrefix} missing ${key}`, {
|
|
760
|
-
status: 502,
|
|
761
|
-
code: errorCode
|
|
695
|
+
const record = body;
|
|
696
|
+
const apiKeyRaw = record.apiKey;
|
|
697
|
+
if (typeof apiKeyRaw !== "string" || !apiKeyRaw.trim()) {
|
|
698
|
+
throw new PmtHouseError("Request body must include apiKey", {
|
|
699
|
+
status: 400,
|
|
700
|
+
code: "invalid_request"
|
|
762
701
|
});
|
|
763
702
|
}
|
|
764
|
-
|
|
703
|
+
const scope = typeof record.scope === "string" && record.scope.trim() ? record.scope.trim() : void 0;
|
|
704
|
+
const clientId = typeof record.clientId === "string" && record.clientId.trim() ? record.clientId.trim() : void 0;
|
|
705
|
+
return { apiKey: apiKeyRaw.trim(), scope, clientId };
|
|
765
706
|
}
|
|
766
|
-
function
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
707
|
+
async function mintUserAccessTokenFromApiKey(input) {
|
|
708
|
+
const fetchImpl = input.fetch ?? fetch;
|
|
709
|
+
const issuerOrigin = stripIssuerOriginFromOidcUrl(input.issuerUrl);
|
|
710
|
+
const url = `${issuerOrigin}/api/v1/apps/${encodeURIComponent(input.publicClientId)}/auth/api-key/token`;
|
|
711
|
+
const response = await fetchImpl(url, {
|
|
712
|
+
method: "POST",
|
|
713
|
+
headers: {
|
|
714
|
+
Authorization: `Bearer ${input.apiKey}`,
|
|
715
|
+
"Content-Type": "application/json",
|
|
716
|
+
Accept: "application/json"
|
|
717
|
+
},
|
|
718
|
+
body: JSON.stringify(input.scope ? { scope: input.scope } : {}),
|
|
719
|
+
cache: "no-store"
|
|
720
|
+
});
|
|
721
|
+
const parsed = await readJsonObjectFromResponse(response, {
|
|
722
|
+
invalidJsonMessage: "API key token exchange returned invalid JSON",
|
|
723
|
+
invalidJsonCode: "invalid_token_response",
|
|
724
|
+
failureLabel: "API key token exchange failed",
|
|
725
|
+
defaultErrorCode: "api_key_token_exchange_failed"
|
|
726
|
+
});
|
|
727
|
+
const accessToken = parsed.access_token;
|
|
728
|
+
if (typeof accessToken !== "string" || !accessToken.trim()) {
|
|
729
|
+
throw new PmtHouseError("API key token exchange missing access_token", {
|
|
770
730
|
status: 502,
|
|
771
|
-
code:
|
|
731
|
+
code: EXCHANGE_RESPONSE_ERROR2
|
|
772
732
|
});
|
|
773
733
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
// src/signer/mint-token.ts
|
|
778
|
-
var SIGN_MINT_USER_TOKEN_SCOPE = "sign:mint_user_token";
|
|
779
|
-
var LIVEPEER_REMOTE_SIGNER_AUDIENCE = "livepeer-remote-signer";
|
|
780
|
-
var DEFAULT_TTL_REFRESH_RATIO = 0.8;
|
|
781
|
-
var TOKEN_RESPONSE_ERROR = "invalid_token_response";
|
|
782
|
-
function parseMintUserSignerTokenResponse(body, ttlRefreshRatio = DEFAULT_TTL_REFRESH_RATIO) {
|
|
783
|
-
const accessToken = readStringField(body, "access_token", TOKEN_RESPONSE_ERROR, "Token response");
|
|
784
|
-
const expiresIn = readExpiresIn(body, TOKEN_RESPONSE_ERROR);
|
|
785
|
-
const balanceUsdMicros = readStringField(
|
|
786
|
-
body,
|
|
787
|
-
"balanceUsdMicros",
|
|
788
|
-
TOKEN_RESPONSE_ERROR,
|
|
789
|
-
"Token response"
|
|
790
|
-
);
|
|
791
|
-
const lifetimeGrantedUsdMicros = readStringField(
|
|
792
|
-
body,
|
|
793
|
-
"lifetimeGrantedUsdMicros",
|
|
794
|
-
TOKEN_RESPONSE_ERROR,
|
|
795
|
-
"Token response"
|
|
796
|
-
);
|
|
797
|
-
const now = Date.now();
|
|
798
|
-
const expiresAt = now + expiresIn * 1e3;
|
|
799
|
-
const refreshAt = now + Math.floor(expiresIn * 1e3 * ttlRefreshRatio);
|
|
734
|
+
const expiresIn = typeof parsed.expires_in === "number" && Number.isFinite(parsed.expires_in) ? parsed.expires_in : 900;
|
|
735
|
+
const scope = typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : input.scope?.trim() || "sign:job";
|
|
800
736
|
return {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
balanceUsdMicros,
|
|
805
|
-
lifetimeGrantedUsdMicros
|
|
737
|
+
access_token: accessToken.trim(),
|
|
738
|
+
expires_in: expiresIn,
|
|
739
|
+
scope
|
|
806
740
|
};
|
|
807
741
|
}
|
|
808
|
-
async function
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
742
|
+
async function mintSignerSessionFromApiKey(input) {
|
|
743
|
+
const userToken = await mintUserAccessTokenFromApiKey({
|
|
744
|
+
issuerUrl: input.issuerUrl,
|
|
745
|
+
publicClientId: input.publicClientId,
|
|
746
|
+
apiKey: input.apiKey,
|
|
747
|
+
scope: input.scope,
|
|
748
|
+
fetch: input.fetch
|
|
813
749
|
});
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
scope: SIGN_MINT_USER_TOKEN_SCOPE,
|
|
824
|
-
external_user_id: options.externalUserId,
|
|
825
|
-
audience: LIVEPEER_REMOTE_SIGNER_AUDIENCE
|
|
750
|
+
return mintSignerTokenFromDeviceToken({
|
|
751
|
+
issuerUrl: input.issuerUrl,
|
|
752
|
+
m2mClientId: input.m2mClientId,
|
|
753
|
+
m2mClientSecret: input.m2mClientSecret,
|
|
754
|
+
deviceToken: userToken.access_token,
|
|
755
|
+
scope: userToken.scope,
|
|
756
|
+
audience: input.audience,
|
|
757
|
+
fetch: input.fetch,
|
|
758
|
+
allowInsecureHttp: input.allowInsecureHttp
|
|
826
759
|
});
|
|
827
|
-
|
|
760
|
+
}
|
|
761
|
+
async function exchangeApiKeyForSigner(options) {
|
|
762
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
763
|
+
const url = `${stripTrailingSlashes(options.facadeUrl)}/api/pymthouse/keys/exchange`;
|
|
764
|
+
const body = { apiKey: options.apiKey };
|
|
765
|
+
if (options.scope?.trim()) {
|
|
766
|
+
body.scope = options.scope.trim();
|
|
767
|
+
}
|
|
768
|
+
if (options.clientId?.trim()) {
|
|
769
|
+
body.clientId = options.clientId.trim();
|
|
770
|
+
}
|
|
771
|
+
const response = await fetchImpl(url, {
|
|
828
772
|
method: "POST",
|
|
829
773
|
headers: {
|
|
830
|
-
|
|
831
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
774
|
+
"Content-Type": "application/json",
|
|
832
775
|
Accept: "application/json"
|
|
833
776
|
},
|
|
834
|
-
body:
|
|
777
|
+
body: JSON.stringify(body),
|
|
835
778
|
cache: "no-store"
|
|
836
779
|
});
|
|
837
780
|
const parsed = await readJsonObjectFromResponse(response, {
|
|
838
|
-
invalidJsonMessage: "
|
|
839
|
-
invalidJsonCode:
|
|
840
|
-
failureLabel: "
|
|
841
|
-
defaultErrorCode: "
|
|
781
|
+
invalidJsonMessage: "API key exchange returned invalid JSON",
|
|
782
|
+
invalidJsonCode: EXCHANGE_RESPONSE_ERROR2,
|
|
783
|
+
failureLabel: "API key exchange failed",
|
|
784
|
+
defaultErrorCode: "api_key_exchange_failed"
|
|
842
785
|
});
|
|
843
|
-
|
|
786
|
+
const accessToken = extractSignerAccessTokenFromExchangeBody(parsed);
|
|
787
|
+
const signerUrlRaw = parsed.signerUrl ?? parsed.signer_url;
|
|
788
|
+
const signerUrl = typeof signerUrlRaw === "string" && signerUrlRaw.trim() ? signerUrlRaw.trim() : void 0;
|
|
789
|
+
return normalizeDeviceExchangeResponse(
|
|
790
|
+
{
|
|
791
|
+
access_token: accessToken,
|
|
792
|
+
expires_in: typeof parsed.expires_in === "number" && Number.isFinite(parsed.expires_in) ? parsed.expires_in : 3600,
|
|
793
|
+
scope: typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : "sign:job",
|
|
794
|
+
balanceUsdMicros: typeof parsed.balanceUsdMicros === "string" ? parsed.balanceUsdMicros : "0",
|
|
795
|
+
lifetimeGrantedUsdMicros: typeof parsed.lifetimeGrantedUsdMicros === "string" ? parsed.lifetimeGrantedUsdMicros : "0"
|
|
796
|
+
},
|
|
797
|
+
{ signerUrl }
|
|
798
|
+
);
|
|
844
799
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
for (const key of ["accessToken", "access_token"]) {
|
|
855
|
-
const value = nested[key];
|
|
856
|
-
if (typeof value === "string" && value.trim()) {
|
|
857
|
-
return value.trim();
|
|
800
|
+
function createApiKeyExchangeHandler(config) {
|
|
801
|
+
const publicClientId = config.publicClientId.trim();
|
|
802
|
+
return async function apiKeyExchangeHandler(request) {
|
|
803
|
+
try {
|
|
804
|
+
if (request.method !== "POST") {
|
|
805
|
+
return new Response(JSON.stringify({ error: "method_not_allowed" }), {
|
|
806
|
+
status: 405,
|
|
807
|
+
headers: { "Content-Type": "application/json" }
|
|
808
|
+
});
|
|
858
809
|
}
|
|
810
|
+
const parsed = await parseApiKeyExchangeRequestBody(request);
|
|
811
|
+
const effectiveClientId = parsed.clientId?.trim() || publicClientId;
|
|
812
|
+
if (effectiveClientId !== publicClientId) {
|
|
813
|
+
throw new PmtHouseError("clientId does not match configured public client", {
|
|
814
|
+
status: 400,
|
|
815
|
+
code: "invalid_request"
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
const minted = await mintSignerSessionFromApiKey({
|
|
819
|
+
issuerUrl: config.issuerUrl,
|
|
820
|
+
publicClientId,
|
|
821
|
+
m2mClientId: config.m2mClientId,
|
|
822
|
+
m2mClientSecret: config.m2mClientSecret,
|
|
823
|
+
apiKey: parsed.apiKey,
|
|
824
|
+
scope: parsed.scope,
|
|
825
|
+
audience: config.audience,
|
|
826
|
+
fetch: config.fetch,
|
|
827
|
+
allowInsecureHttp: config.allowInsecureHttp
|
|
828
|
+
});
|
|
829
|
+
const signerUrlValue = typeof config.signerUrl === "string" && config.signerUrl.trim() ? config.signerUrl.trim() : void 0;
|
|
830
|
+
const body = normalizeDeviceExchangeResponse(minted, { signerUrl: signerUrlValue });
|
|
831
|
+
return new Response(JSON.stringify(body), {
|
|
832
|
+
status: 200,
|
|
833
|
+
headers: {
|
|
834
|
+
"Content-Type": "application/json",
|
|
835
|
+
"Cache-Control": "no-store"
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
} catch (error) {
|
|
839
|
+
return signerHandlerErrorResponse(error);
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/signer/proxy.ts
|
|
845
|
+
var HTTP_DMZ_TOKEN_MAX_ENTRIES = 100;
|
|
846
|
+
var HTTP_DMZ_TOKEN_TTL_MS = 3.5 * 60 * 1e3;
|
|
847
|
+
var DEFAULT_PROBE_SUBJECT = "signer-reachability-probe";
|
|
848
|
+
var httpDmzTokenCache = /* @__PURE__ */ new Map();
|
|
849
|
+
function normalizeSignerBaseUrl(base) {
|
|
850
|
+
return stripTrailingSlashes(base);
|
|
851
|
+
}
|
|
852
|
+
function joinSignerUrl(baseUrl, path) {
|
|
853
|
+
if (path.startsWith("/")) {
|
|
854
|
+
return `${baseUrl}${path}`;
|
|
855
|
+
}
|
|
856
|
+
return `${baseUrl}/${path}`;
|
|
857
|
+
}
|
|
858
|
+
function aliasBodyString(raw) {
|
|
859
|
+
if (raw === void 0 || raw === null) {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
if (typeof raw === "string") {
|
|
863
|
+
return raw.length > 0 ? raw : null;
|
|
864
|
+
}
|
|
865
|
+
if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint") {
|
|
866
|
+
return String(raw);
|
|
867
|
+
}
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
function resolveSignerBaseUrl(input) {
|
|
871
|
+
if (input.testSignerUrl?.trim()) {
|
|
872
|
+
return normalizeSignerBaseUrl(input.testSignerUrl);
|
|
873
|
+
}
|
|
874
|
+
const legacyBareSignerPort = 8081;
|
|
875
|
+
const rawPort = input.storedPort ?? input.defaultPort ?? 8080;
|
|
876
|
+
const port = rawPort === legacyBareSignerPort ? 8080 : rawPort;
|
|
877
|
+
const base = input.envUrl?.trim() || input.storedUrl?.trim() || `http://127.0.0.1:${port}`;
|
|
878
|
+
return normalizeSignerBaseUrl(base);
|
|
879
|
+
}
|
|
880
|
+
async function getCachedDmzBearerToken(subject, gate, getDmzToken) {
|
|
881
|
+
const cacheKey2 = `${gate}:${subject}`;
|
|
882
|
+
const now = Date.now();
|
|
883
|
+
const cached = httpDmzTokenCache.get(cacheKey2);
|
|
884
|
+
if (cached && cached.expMs > now + 15e3) {
|
|
885
|
+
httpDmzTokenCache.delete(cacheKey2);
|
|
886
|
+
httpDmzTokenCache.set(cacheKey2, cached);
|
|
887
|
+
return cached.token;
|
|
888
|
+
}
|
|
889
|
+
const token = await getDmzToken(subject, gate);
|
|
890
|
+
httpDmzTokenCache.set(cacheKey2, { token, expMs: now + HTTP_DMZ_TOKEN_TTL_MS });
|
|
891
|
+
if (httpDmzTokenCache.size > HTTP_DMZ_TOKEN_MAX_ENTRIES) {
|
|
892
|
+
const oldest = httpDmzTokenCache.keys().next().value;
|
|
893
|
+
if (oldest !== void 0) {
|
|
894
|
+
httpDmzTokenCache.delete(oldest);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return token;
|
|
898
|
+
}
|
|
899
|
+
async function readSignerUpstreamBody(response) {
|
|
900
|
+
const text = await response.text();
|
|
901
|
+
if (!text.trim()) {
|
|
902
|
+
return {};
|
|
903
|
+
}
|
|
904
|
+
try {
|
|
905
|
+
return JSON.parse(text);
|
|
906
|
+
} catch {
|
|
907
|
+
return {
|
|
908
|
+
error: "Signer DMZ returned a non-JSON body (often Apache auth failure)",
|
|
909
|
+
upstreamStatus: response.status,
|
|
910
|
+
detail: text.slice(0, 800)
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function pickConflictingStringAliases(body, ...keys) {
|
|
915
|
+
const values = keys.map((key) => {
|
|
916
|
+
const value = aliasBodyString(body[key]);
|
|
917
|
+
return value === null ? null : { key, value };
|
|
918
|
+
}).filter((entry) => entry !== null);
|
|
919
|
+
const first = values[0];
|
|
920
|
+
const conflict = values.find((entry) => entry.value !== first?.value);
|
|
921
|
+
if (first && conflict) {
|
|
922
|
+
return {
|
|
923
|
+
ok: false,
|
|
924
|
+
message: `Conflicting ${keys.join("/")} in request body`
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
return { ok: true, value: first?.value };
|
|
928
|
+
}
|
|
929
|
+
function pickConflictingNumberAliases(body, ...keys) {
|
|
930
|
+
const parseNum = (value) => {
|
|
931
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
932
|
+
return value;
|
|
933
|
+
}
|
|
934
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
935
|
+
const parsed = Number(value);
|
|
936
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
859
937
|
}
|
|
938
|
+
return void 0;
|
|
939
|
+
};
|
|
940
|
+
const values = keys.map((key) => {
|
|
941
|
+
const value = parseNum(body[key]);
|
|
942
|
+
return value === void 0 ? null : { key, value };
|
|
943
|
+
}).filter((entry) => entry !== null);
|
|
944
|
+
const first = values[0];
|
|
945
|
+
const conflict = values.find((entry) => entry.value !== first?.value);
|
|
946
|
+
if (first && conflict) {
|
|
947
|
+
return {
|
|
948
|
+
ok: false,
|
|
949
|
+
message: `Conflicting ${keys.join("/")} in request body`
|
|
950
|
+
};
|
|
860
951
|
}
|
|
861
|
-
|
|
862
|
-
|
|
952
|
+
return { ok: true, value: first?.value };
|
|
953
|
+
}
|
|
954
|
+
function pickString(obj, ...keys) {
|
|
955
|
+
for (const key of keys) {
|
|
956
|
+
const value = obj[key];
|
|
863
957
|
if (typeof value === "string" && value.trim()) {
|
|
864
958
|
return value.trim();
|
|
865
959
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
status: 502,
|
|
869
|
-
code: "invalid_exchange_response"
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
function normalizeDeviceExchangeResponse(minted, options) {
|
|
873
|
-
const scope = minted.scope.trim() || "sign:job";
|
|
874
|
-
const body = {
|
|
875
|
-
access_token: minted.access_token,
|
|
876
|
-
token_type: "Bearer",
|
|
877
|
-
expires_in: minted.expires_in,
|
|
878
|
-
scope,
|
|
879
|
-
balanceUsdMicros: minted.balanceUsdMicros,
|
|
880
|
-
lifetimeGrantedUsdMicros: minted.lifetimeGrantedUsdMicros,
|
|
881
|
-
token: {
|
|
882
|
-
accessToken: minted.access_token,
|
|
883
|
-
access_token: minted.access_token,
|
|
884
|
-
expiresIn: minted.expires_in,
|
|
885
|
-
expires_in: minted.expires_in,
|
|
886
|
-
scope,
|
|
887
|
-
balanceUsdMicros: minted.balanceUsdMicros,
|
|
888
|
-
lifetimeGrantedUsdMicros: minted.lifetimeGrantedUsdMicros
|
|
960
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
961
|
+
return String(Math.trunc(value));
|
|
889
962
|
}
|
|
890
|
-
};
|
|
891
|
-
const signerUrl = options?.signerUrl?.trim();
|
|
892
|
-
if (signerUrl) {
|
|
893
|
-
body.signerUrl = signerUrl;
|
|
894
963
|
}
|
|
895
|
-
return
|
|
964
|
+
return "";
|
|
896
965
|
}
|
|
897
|
-
|
|
898
|
-
let body;
|
|
899
|
-
try {
|
|
900
|
-
body = await request.json();
|
|
901
|
-
} catch {
|
|
902
|
-
throw new PmtHouseError("Request body must be JSON", {
|
|
903
|
-
status: 400,
|
|
904
|
-
code: "invalid_request"
|
|
905
|
-
});
|
|
906
|
-
}
|
|
966
|
+
function parseSignerUsageSnapshot(body) {
|
|
907
967
|
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
908
|
-
|
|
909
|
-
status: 400,
|
|
910
|
-
code: "invalid_request"
|
|
911
|
-
});
|
|
968
|
+
return null;
|
|
912
969
|
}
|
|
913
970
|
const record = body;
|
|
914
|
-
const
|
|
915
|
-
if (typeof
|
|
916
|
-
|
|
917
|
-
status: 400,
|
|
918
|
-
code: "invalid_request"
|
|
919
|
-
});
|
|
971
|
+
const usageRaw = record.usage;
|
|
972
|
+
if (usageRaw === null || typeof usageRaw !== "object" || Array.isArray(usageRaw)) {
|
|
973
|
+
return null;
|
|
920
974
|
}
|
|
921
|
-
const
|
|
922
|
-
const
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
const fetchImpl = options.fetch ?? fetch;
|
|
928
|
-
const issuerUrl = stripTrailingSlashes(options.issuerUrl);
|
|
929
|
-
const as = await loadAuthorizationServer(issuerUrl, fetchImpl, {
|
|
930
|
-
allowInsecureHttp: options.allowInsecureHttp
|
|
931
|
-
});
|
|
932
|
-
const tokenEndpoint = as.token_endpoint;
|
|
933
|
-
if (!tokenEndpoint) {
|
|
934
|
-
throw new PmtHouseError("OIDC discovery document is missing token_endpoint", {
|
|
935
|
-
status: 500,
|
|
936
|
-
code: "oidc_discovery_invalid"
|
|
937
|
-
});
|
|
975
|
+
const usage = usageRaw;
|
|
976
|
+
const computedFeeWei = pickString(usage, "computed_fee_wei", "computedFeeWei");
|
|
977
|
+
const usdMicrosStr = pickString(usage, "computed_fee_usd_micros", "computedFeeUsdMicros");
|
|
978
|
+
const requestId = pickString(usage, "request_id", "requestId");
|
|
979
|
+
if (!computedFeeWei || !usdMicrosStr || !requestId) {
|
|
980
|
+
return null;
|
|
938
981
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
audience,
|
|
945
|
-
resource: audience
|
|
946
|
-
});
|
|
947
|
-
if (options.scope?.trim()) {
|
|
948
|
-
params.set("scope", options.scope.trim());
|
|
982
|
+
let computedFeeUsdMicros;
|
|
983
|
+
try {
|
|
984
|
+
computedFeeUsdMicros = BigInt(usdMicrosStr);
|
|
985
|
+
} catch {
|
|
986
|
+
return null;
|
|
949
987
|
}
|
|
950
|
-
const response = await fetchImpl(tokenEndpoint, {
|
|
951
|
-
method: "POST",
|
|
952
|
-
headers: {
|
|
953
|
-
Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
|
|
954
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
955
|
-
Accept: "application/json"
|
|
956
|
-
},
|
|
957
|
-
body: params.toString(),
|
|
958
|
-
cache: "no-store"
|
|
959
|
-
});
|
|
960
|
-
const parsed = await readJsonObjectFromResponse(response, {
|
|
961
|
-
invalidJsonMessage: "Token endpoint returned invalid JSON",
|
|
962
|
-
invalidJsonCode: "invalid_token_response",
|
|
963
|
-
failureLabel: "Signer JWT exchange failed",
|
|
964
|
-
defaultErrorCode: "token_exchange_failed"
|
|
965
|
-
});
|
|
966
|
-
const cached = parseMintUserSignerTokenResponse(parsed);
|
|
967
988
|
return {
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
989
|
+
requestId,
|
|
990
|
+
computedFeeWei,
|
|
991
|
+
computedFeeUsdMicros,
|
|
992
|
+
ethUsdPrice: pickString(usage, "eth_usd_price", "ethUsdPrice") || void 0,
|
|
993
|
+
ethUsdRoundId: pickString(usage, "eth_usd_round_id", "ethUsdRoundId") || void 0,
|
|
994
|
+
ethUsdObservedAt: pickString(usage, "eth_usd_updated_at", "ethUsdUpdatedAt", "eth_usd_observed_at") || void 0,
|
|
995
|
+
pixels: pickString(usage, "pixels") || void 0,
|
|
996
|
+
billableSecs: pickString(usage, "billable_secs", "billableSecs") || void 0,
|
|
997
|
+
pipeline: pickString(usage, "pipeline") || void 0,
|
|
998
|
+
modelId: pickString(usage, "model_id", "modelId") || void 0
|
|
973
999
|
};
|
|
974
1000
|
}
|
|
975
|
-
|
|
1001
|
+
function stripSignerUsageFromResponse(body) {
|
|
1002
|
+
if (body !== null && typeof body === "object" && !Array.isArray(body)) {
|
|
1003
|
+
delete body.usage;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
async function forwardToSigner(options) {
|
|
976
1007
|
const fetchImpl = options.fetch ?? fetch;
|
|
977
|
-
const
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
1008
|
+
const baseUrl = normalizeSignerBaseUrl(options.baseUrl);
|
|
1009
|
+
const url = joinSignerUrl(baseUrl, options.path);
|
|
1010
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
1011
|
+
const controller = new AbortController();
|
|
1012
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1013
|
+
const headers = { "Content-Type": "application/json" };
|
|
1014
|
+
const explicitAuth = options.authorization?.trim();
|
|
1015
|
+
if (explicitAuth) {
|
|
1016
|
+
headers.Authorization = explicitAuth.startsWith("Bearer ") ? explicitAuth : `Bearer ${explicitAuth}`;
|
|
1017
|
+
} else {
|
|
1018
|
+
const attachJwt = options.forwardJwt ?? true;
|
|
1019
|
+
if (attachJwt) {
|
|
1020
|
+
const token = await getCachedDmzBearerToken(
|
|
1021
|
+
options.subject,
|
|
1022
|
+
"http",
|
|
1023
|
+
options.getDmzToken
|
|
1024
|
+
);
|
|
1025
|
+
headers.Authorization = `Bearer ${token}`;
|
|
1026
|
+
}
|
|
981
1027
|
}
|
|
982
|
-
if (options.
|
|
983
|
-
|
|
1028
|
+
if (options.extraHeaders) {
|
|
1029
|
+
for (const [name, value] of Object.entries(options.extraHeaders)) {
|
|
1030
|
+
if (value.trim()) {
|
|
1031
|
+
headers[name] = value.trim();
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
984
1034
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
});
|
|
1000
|
-
const accessToken = extractSignerAccessTokenFromExchangeBody(parsed);
|
|
1001
|
-
const signerUrlRaw = parsed.signerUrl ?? parsed.signer_url;
|
|
1002
|
-
const signerUrl = typeof signerUrlRaw === "string" && signerUrlRaw.trim() ? signerUrlRaw.trim() : void 0;
|
|
1003
|
-
return normalizeDeviceExchangeResponse(
|
|
1004
|
-
{
|
|
1005
|
-
access_token: accessToken,
|
|
1006
|
-
expires_in: readExpiresIn(parsed, EXCHANGE_RESPONSE_ERROR),
|
|
1007
|
-
scope: typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : "sign:job",
|
|
1008
|
-
balanceUsdMicros: typeof parsed.balanceUsdMicros === "string" ? parsed.balanceUsdMicros : "0",
|
|
1009
|
-
lifetimeGrantedUsdMicros: typeof parsed.lifetimeGrantedUsdMicros === "string" ? parsed.lifetimeGrantedUsdMicros : "0"
|
|
1010
|
-
},
|
|
1011
|
-
{ signerUrl }
|
|
1012
|
-
);
|
|
1013
|
-
}
|
|
1014
|
-
function resolveMint(config) {
|
|
1015
|
-
if ("mint" in config && typeof config.mint === "function") {
|
|
1016
|
-
return config.mint;
|
|
1035
|
+
try {
|
|
1036
|
+
const response = await fetchImpl(url, {
|
|
1037
|
+
method: options.method,
|
|
1038
|
+
headers,
|
|
1039
|
+
body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
|
|
1040
|
+
signal: controller.signal
|
|
1041
|
+
});
|
|
1042
|
+
return {
|
|
1043
|
+
response,
|
|
1044
|
+
requestUrl: url,
|
|
1045
|
+
authorizationHeader: headers.Authorization
|
|
1046
|
+
};
|
|
1047
|
+
} finally {
|
|
1048
|
+
clearTimeout(timeout);
|
|
1017
1049
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1050
|
+
}
|
|
1051
|
+
function createSignerProbeContext(options) {
|
|
1052
|
+
return {
|
|
1053
|
+
fetchImpl: options.fetch ?? fetch,
|
|
1054
|
+
signerUrl: normalizeSignerBaseUrl(options.signerUrl),
|
|
1055
|
+
timeoutMs: options.timeoutMs ?? 5e3,
|
|
1056
|
+
probeSubject: options.probeSubject ?? DEFAULT_PROBE_SUBJECT,
|
|
1057
|
+
useJwt: options.forwardJwt ?? true,
|
|
1058
|
+
getDmzToken: options.getDmzToken
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function reachableResult(ethAddress) {
|
|
1062
|
+
return { reachable: true, ethAddress };
|
|
1063
|
+
}
|
|
1064
|
+
async function fetchSignerStatus(ctx, headers) {
|
|
1065
|
+
const response = await ctx.fetchImpl(`${ctx.signerUrl}/status`, {
|
|
1066
|
+
headers,
|
|
1067
|
+
signal: AbortSignal.timeout(ctx.timeoutMs)
|
|
1027
1068
|
});
|
|
1069
|
+
if (!response.ok) {
|
|
1070
|
+
return { ok: false };
|
|
1071
|
+
}
|
|
1072
|
+
const data = await readSignerUpstreamBody(response);
|
|
1073
|
+
const ethAddress = typeof data.Address === "string" && data.Address || typeof data.address === "string" && data.address || void 0;
|
|
1074
|
+
return { ok: true, ethAddress };
|
|
1028
1075
|
}
|
|
1029
|
-
function
|
|
1030
|
-
if (
|
|
1031
|
-
return
|
|
1076
|
+
async function tryJwtStatus(ctx) {
|
|
1077
|
+
if (!ctx.useJwt) {
|
|
1078
|
+
return null;
|
|
1032
1079
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1080
|
+
try {
|
|
1081
|
+
const token = await ctx.getDmzToken(ctx.probeSubject, "http");
|
|
1082
|
+
const { ok, ethAddress } = await fetchSignerStatus(ctx, {
|
|
1083
|
+
Authorization: `Bearer ${token}`
|
|
1084
|
+
});
|
|
1085
|
+
return ok ? reachableResult(ethAddress) : null;
|
|
1086
|
+
} catch {
|
|
1087
|
+
return null;
|
|
1035
1088
|
}
|
|
1036
|
-
return void 0;
|
|
1037
|
-
}
|
|
1038
|
-
function createDeviceExchangeHandler(config) {
|
|
1039
|
-
const mint = resolveMint(config);
|
|
1040
|
-
return async function deviceExchangeHandler(request) {
|
|
1041
|
-
try {
|
|
1042
|
-
if (request.method !== "POST") {
|
|
1043
|
-
return new Response(JSON.stringify({ error: "method_not_allowed" }), {
|
|
1044
|
-
status: 405,
|
|
1045
|
-
headers: { "Content-Type": "application/json" }
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1048
|
-
const parsed = await parseDeviceExchangeRequestBody(request);
|
|
1049
|
-
const minted = await mint(parsed.deviceToken, {
|
|
1050
|
-
scope: parsed.scope,
|
|
1051
|
-
clientId: parsed.clientId
|
|
1052
|
-
});
|
|
1053
|
-
const signerUrlValue = await resolveSignerUrlFromConfig(config);
|
|
1054
|
-
const body = normalizeDeviceExchangeResponse(minted, {
|
|
1055
|
-
signerUrl: typeof signerUrlValue === "string" ? signerUrlValue : void 0
|
|
1056
|
-
});
|
|
1057
|
-
return new Response(JSON.stringify(body), {
|
|
1058
|
-
status: 200,
|
|
1059
|
-
headers: {
|
|
1060
|
-
"Content-Type": "application/json",
|
|
1061
|
-
"Cache-Control": "no-store"
|
|
1062
|
-
}
|
|
1063
|
-
});
|
|
1064
|
-
} catch (error) {
|
|
1065
|
-
return signerHandlerErrorResponse(error);
|
|
1066
|
-
}
|
|
1067
|
-
};
|
|
1068
1089
|
}
|
|
1069
|
-
|
|
1070
|
-
// src/signer/api-key-exchange.ts
|
|
1071
|
-
var EXCHANGE_RESPONSE_ERROR2 = "invalid_exchange_response";
|
|
1072
|
-
async function parseApiKeyExchangeRequestBody(request) {
|
|
1073
|
-
let body;
|
|
1090
|
+
async function tryPlainStatus(ctx) {
|
|
1074
1091
|
try {
|
|
1075
|
-
|
|
1092
|
+
const { ok, ethAddress } = await fetchSignerStatus(ctx, {});
|
|
1093
|
+
return ok ? reachableResult(ethAddress) : null;
|
|
1076
1094
|
} catch {
|
|
1077
|
-
|
|
1078
|
-
status: 400,
|
|
1079
|
-
code: "invalid_request"
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
1083
|
-
throw new PmtHouseError("Request body must be a JSON object", {
|
|
1084
|
-
status: 400,
|
|
1085
|
-
code: "invalid_request"
|
|
1086
|
-
});
|
|
1087
|
-
}
|
|
1088
|
-
const record = body;
|
|
1089
|
-
const apiKeyRaw = record.apiKey;
|
|
1090
|
-
if (typeof apiKeyRaw !== "string" || !apiKeyRaw.trim()) {
|
|
1091
|
-
throw new PmtHouseError("Request body must include apiKey", {
|
|
1092
|
-
status: 400,
|
|
1093
|
-
code: "invalid_request"
|
|
1094
|
-
});
|
|
1095
|
+
return null;
|
|
1095
1096
|
}
|
|
1096
|
-
const scope = typeof record.scope === "string" && record.scope.trim() ? record.scope.trim() : void 0;
|
|
1097
|
-
const clientId = typeof record.clientId === "string" && record.clientId.trim() ? record.clientId.trim() : void 0;
|
|
1098
|
-
return { apiKey: apiKeyRaw.trim(), scope, clientId };
|
|
1099
1097
|
}
|
|
1100
|
-
async function
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
body: JSON.stringify(input.scope ? { scope: input.scope } : {}),
|
|
1112
|
-
cache: "no-store"
|
|
1113
|
-
});
|
|
1114
|
-
const parsed = await readJsonObjectFromResponse(response, {
|
|
1115
|
-
invalidJsonMessage: "API key token exchange returned invalid JSON",
|
|
1116
|
-
invalidJsonCode: "invalid_token_response",
|
|
1117
|
-
failureLabel: "API key token exchange failed",
|
|
1118
|
-
defaultErrorCode: "api_key_token_exchange_failed"
|
|
1119
|
-
});
|
|
1120
|
-
const accessToken = parsed.access_token;
|
|
1121
|
-
if (typeof accessToken !== "string" || !accessToken.trim()) {
|
|
1122
|
-
throw new PmtHouseError("API key token exchange missing access_token", {
|
|
1123
|
-
status: 502,
|
|
1124
|
-
code: EXCHANGE_RESPONSE_ERROR2
|
|
1098
|
+
async function trySigningProbe(ctx) {
|
|
1099
|
+
try {
|
|
1100
|
+
const token = await ctx.getDmzToken(ctx.probeSubject, "http");
|
|
1101
|
+
const response = await ctx.fetchImpl(`${ctx.signerUrl}/sign-orchestrator-info`, {
|
|
1102
|
+
method: "POST",
|
|
1103
|
+
headers: {
|
|
1104
|
+
Authorization: `Bearer ${token}`,
|
|
1105
|
+
"Content-Type": "application/json"
|
|
1106
|
+
},
|
|
1107
|
+
body: "{}",
|
|
1108
|
+
signal: AbortSignal.timeout(ctx.timeoutMs)
|
|
1125
1109
|
});
|
|
1110
|
+
return response.ok;
|
|
1111
|
+
} catch {
|
|
1112
|
+
return false;
|
|
1126
1113
|
}
|
|
1127
|
-
const expiresIn = typeof parsed.expires_in === "number" && Number.isFinite(parsed.expires_in) ? parsed.expires_in : 900;
|
|
1128
|
-
const scope = typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : input.scope?.trim() || "sign:job";
|
|
1129
|
-
return {
|
|
1130
|
-
access_token: accessToken.trim(),
|
|
1131
|
-
expires_in: expiresIn,
|
|
1132
|
-
scope
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
async function mintSignerSessionFromApiKey(input) {
|
|
1136
|
-
const userToken = await mintUserAccessTokenFromApiKey({
|
|
1137
|
-
issuerUrl: input.issuerUrl,
|
|
1138
|
-
publicClientId: input.publicClientId,
|
|
1139
|
-
apiKey: input.apiKey,
|
|
1140
|
-
scope: input.scope,
|
|
1141
|
-
fetch: input.fetch
|
|
1142
|
-
});
|
|
1143
|
-
return mintSignerTokenFromDeviceToken({
|
|
1144
|
-
issuerUrl: input.issuerUrl,
|
|
1145
|
-
m2mClientId: input.m2mClientId,
|
|
1146
|
-
m2mClientSecret: input.m2mClientSecret,
|
|
1147
|
-
deviceToken: userToken.access_token,
|
|
1148
|
-
scope: userToken.scope,
|
|
1149
|
-
audience: input.audience,
|
|
1150
|
-
fetch: input.fetch,
|
|
1151
|
-
allowInsecureHttp: input.allowInsecureHttp
|
|
1152
|
-
});
|
|
1153
1114
|
}
|
|
1154
|
-
async function
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
if (options.scope?.trim()) {
|
|
1159
|
-
body.scope = options.scope.trim();
|
|
1115
|
+
async function runSignerReachabilityProbes(ctx) {
|
|
1116
|
+
const jwtResult = await tryJwtStatus(ctx);
|
|
1117
|
+
if (jwtResult) {
|
|
1118
|
+
return jwtResult;
|
|
1160
1119
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1120
|
+
const plainResult = await tryPlainStatus(ctx);
|
|
1121
|
+
if (plainResult) {
|
|
1122
|
+
return plainResult;
|
|
1163
1123
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
Accept: "application/json"
|
|
1169
|
-
},
|
|
1170
|
-
body: JSON.stringify(body),
|
|
1171
|
-
cache: "no-store"
|
|
1172
|
-
});
|
|
1173
|
-
const parsed = await readJsonObjectFromResponse(response, {
|
|
1174
|
-
invalidJsonMessage: "API key exchange returned invalid JSON",
|
|
1175
|
-
invalidJsonCode: EXCHANGE_RESPONSE_ERROR2,
|
|
1176
|
-
failureLabel: "API key exchange failed",
|
|
1177
|
-
defaultErrorCode: "api_key_exchange_failed"
|
|
1178
|
-
});
|
|
1179
|
-
const accessToken = extractSignerAccessTokenFromExchangeBody(parsed);
|
|
1180
|
-
const signerUrlRaw = parsed.signerUrl ?? parsed.signer_url;
|
|
1181
|
-
const signerUrl = typeof signerUrlRaw === "string" && signerUrlRaw.trim() ? signerUrlRaw.trim() : void 0;
|
|
1182
|
-
return normalizeDeviceExchangeResponse(
|
|
1183
|
-
{
|
|
1184
|
-
access_token: accessToken,
|
|
1185
|
-
expires_in: typeof parsed.expires_in === "number" && Number.isFinite(parsed.expires_in) ? parsed.expires_in : 3600,
|
|
1186
|
-
scope: typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : "sign:job",
|
|
1187
|
-
balanceUsdMicros: typeof parsed.balanceUsdMicros === "string" ? parsed.balanceUsdMicros : "0",
|
|
1188
|
-
lifetimeGrantedUsdMicros: typeof parsed.lifetimeGrantedUsdMicros === "string" ? parsed.lifetimeGrantedUsdMicros : "0"
|
|
1189
|
-
},
|
|
1190
|
-
{ signerUrl }
|
|
1191
|
-
);
|
|
1124
|
+
if (await trySigningProbe(ctx)) {
|
|
1125
|
+
return reachableResult();
|
|
1126
|
+
}
|
|
1127
|
+
return { reachable: false };
|
|
1192
1128
|
}
|
|
1193
|
-
function
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
const parsed = await parseApiKeyExchangeRequestBody(request);
|
|
1204
|
-
const effectiveClientId = parsed.clientId?.trim() || publicClientId;
|
|
1205
|
-
if (effectiveClientId !== publicClientId) {
|
|
1206
|
-
throw new PmtHouseError("clientId does not match configured public client", {
|
|
1207
|
-
status: 400,
|
|
1208
|
-
code: "invalid_request"
|
|
1209
|
-
});
|
|
1210
|
-
}
|
|
1211
|
-
const minted = await mintSignerSessionFromApiKey({
|
|
1212
|
-
issuerUrl: config.issuerUrl,
|
|
1213
|
-
publicClientId,
|
|
1214
|
-
m2mClientId: config.m2mClientId,
|
|
1215
|
-
m2mClientSecret: config.m2mClientSecret,
|
|
1216
|
-
apiKey: parsed.apiKey,
|
|
1217
|
-
scope: parsed.scope,
|
|
1218
|
-
audience: config.audience,
|
|
1219
|
-
fetch: config.fetch,
|
|
1220
|
-
allowInsecureHttp: config.allowInsecureHttp
|
|
1221
|
-
});
|
|
1222
|
-
const signerUrlValue = typeof config.signerUrl === "string" && config.signerUrl.trim() ? config.signerUrl.trim() : void 0;
|
|
1223
|
-
const body = normalizeDeviceExchangeResponse(minted, { signerUrl: signerUrlValue });
|
|
1224
|
-
return new Response(JSON.stringify(body), {
|
|
1225
|
-
status: 200,
|
|
1226
|
-
headers: {
|
|
1227
|
-
"Content-Type": "application/json",
|
|
1228
|
-
"Cache-Control": "no-store"
|
|
1229
|
-
}
|
|
1230
|
-
});
|
|
1231
|
-
} catch (error) {
|
|
1232
|
-
return signerHandlerErrorResponse(error);
|
|
1129
|
+
async function probeSignerHttpReachability(options) {
|
|
1130
|
+
const ctx = createSignerProbeContext(options);
|
|
1131
|
+
try {
|
|
1132
|
+
const health = await ctx.fetchImpl(`${ctx.signerUrl}/healthz`, {
|
|
1133
|
+
signal: AbortSignal.timeout(ctx.timeoutMs)
|
|
1134
|
+
});
|
|
1135
|
+
if (health.ok) {
|
|
1136
|
+
return runSignerReachabilityProbes(ctx);
|
|
1233
1137
|
}
|
|
1234
|
-
}
|
|
1138
|
+
} catch {
|
|
1139
|
+
}
|
|
1140
|
+
return runSignerReachabilityProbes(ctx);
|
|
1235
1141
|
}
|
|
1236
1142
|
|
|
1237
1143
|
// src/signer/server.ts
|
|
@@ -1252,7 +1158,6 @@ function toResponse(result) {
|
|
|
1252
1158
|
}
|
|
1253
1159
|
function createDirectSignerProxyHandler(config) {
|
|
1254
1160
|
const tokenManager = createSignerTokenManager({
|
|
1255
|
-
publicClientId: config.pymthouseClientId,
|
|
1256
1161
|
mint: (externalUserId) => mintUserSignerToken({
|
|
1257
1162
|
issuerUrl: config.pymthouseIssuerUrl,
|
|
1258
1163
|
m2mClientId: config.pymthouseM2MClientId,
|
|
@@ -1295,36 +1200,37 @@ function createDirectSignerProxyHandler(config) {
|
|
|
1295
1200
|
code: "invalid_external_user_id"
|
|
1296
1201
|
});
|
|
1297
1202
|
}
|
|
1298
|
-
|
|
1203
|
+
const publicClientId = config.resolvePublicClientId ? (await config.resolvePublicClientId(session)).trim() : config.pymthouseClientId.trim();
|
|
1204
|
+
if (!publicClientId) {
|
|
1205
|
+
throw new PmtHouseError("resolvePublicClientId returned an empty id", {
|
|
1206
|
+
status: 500,
|
|
1207
|
+
code: "invalid_client_id"
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
let token = await tokenManager.getToken(publicClientId, externalUserId);
|
|
1299
1211
|
const blocked = await runBeforeSign(token, externalUserId, request);
|
|
1300
1212
|
if (blocked) {
|
|
1301
1213
|
return blocked;
|
|
1302
1214
|
}
|
|
1303
|
-
let upstream = await
|
|
1304
|
-
config,
|
|
1305
|
-
externalUserId,
|
|
1306
|
-
forward: () => forwardOnce(token, request)
|
|
1307
|
-
});
|
|
1215
|
+
let upstream = await forwardOnce(token, request);
|
|
1308
1216
|
if (upstream.status === 401) {
|
|
1309
|
-
tokenManager.invalidate(externalUserId);
|
|
1310
|
-
token = await tokenManager.getToken(externalUserId, {
|
|
1217
|
+
tokenManager.invalidate(publicClientId, externalUserId);
|
|
1218
|
+
token = await tokenManager.getToken(publicClientId, externalUserId, {
|
|
1219
|
+
forceRefresh: true
|
|
1220
|
+
});
|
|
1311
1221
|
const retryBlocked = await runBeforeSign(token, externalUserId, request);
|
|
1312
1222
|
if (retryBlocked) {
|
|
1313
1223
|
return retryBlocked;
|
|
1314
1224
|
}
|
|
1315
|
-
upstream = await
|
|
1316
|
-
config,
|
|
1317
|
-
externalUserId,
|
|
1318
|
-
forward: () => forwardOnce(token, request)
|
|
1319
|
-
});
|
|
1225
|
+
upstream = await forwardOnce(token, request);
|
|
1320
1226
|
}
|
|
1321
1227
|
return upstream;
|
|
1322
1228
|
} catch (error) {
|
|
1323
1229
|
return signerHandlerErrorResponse(error);
|
|
1324
1230
|
}
|
|
1325
1231
|
};
|
|
1326
|
-
handler.getCachedUsage = (externalUserId) => tokenManager.peek(externalUserId);
|
|
1327
|
-
handler.invalidateToken = (externalUserId) => tokenManager.invalidate(externalUserId);
|
|
1232
|
+
handler.getCachedUsage = (externalUserId) => tokenManager.peek(config.pymthouseClientId, externalUserId);
|
|
1233
|
+
handler.invalidateToken = (externalUserId) => tokenManager.invalidate(config.pymthouseClientId, externalUserId);
|
|
1328
1234
|
return handler;
|
|
1329
1235
|
}
|
|
1330
1236
|
|
|
@@ -1340,10 +1246,8 @@ exports.exchangeDeviceTokenForSigner = exchangeDeviceTokenForSigner;
|
|
|
1340
1246
|
exports.extractSignerAccessTokenFromExchangeBody = extractSignerAccessTokenFromExchangeBody;
|
|
1341
1247
|
exports.forwardDirectSignerRequest = forwardDirectSignerRequest;
|
|
1342
1248
|
exports.forwardToSigner = forwardToSigner;
|
|
1343
|
-
exports.forwardWithOptionalMetering = forwardWithOptionalMetering;
|
|
1344
1249
|
exports.getCachedDmzBearerToken = getCachedDmzBearerToken;
|
|
1345
1250
|
exports.identityFromJwtPayload = identityFromJwtPayload;
|
|
1346
|
-
exports.ingestSignedTicket = ingestSignedTicket;
|
|
1347
1251
|
exports.livepeerIdentityHeaders = livepeerIdentityHeaders;
|
|
1348
1252
|
exports.mintSignerSessionFromApiKey = mintSignerSessionFromApiKey;
|
|
1349
1253
|
exports.mintSignerTokenFromDeviceToken = mintSignerTokenFromDeviceToken;
|
|
@@ -1360,7 +1264,7 @@ exports.pickConflictingStringAliases = pickConflictingStringAliases;
|
|
|
1360
1264
|
exports.probeSignerHttpReachability = probeSignerHttpReachability;
|
|
1361
1265
|
exports.readSignerUpstreamBody = readSignerUpstreamBody;
|
|
1362
1266
|
exports.resolveSignerBaseUrl = resolveSignerBaseUrl;
|
|
1363
|
-
exports.
|
|
1267
|
+
exports.signerJwtAudience = signerJwtAudience;
|
|
1364
1268
|
exports.stripSignerUsageFromResponse = stripSignerUsageFromResponse;
|
|
1365
1269
|
//# sourceMappingURL=server.cjs.map
|
|
1366
1270
|
//# sourceMappingURL=server.cjs.map
|