@pymthouse/builder-sdk 0.4.4 → 0.4.6
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 +120 -5
- package/dist/{client-C0HgAugK.d.cts → client-CEBVgCD7.d.cts} +28 -4
- package/dist/{client-zCskUJag.d.ts → client-D-p6v8ju.d.ts} +28 -4
- package/dist/device.d.cts +1 -1
- package/dist/device.d.ts +1 -1
- package/dist/env.cjs +64 -9
- 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 +64 -9
- package/dist/env.js.map +1 -1
- package/dist/{index-CAIAYJv7.d.cts → index-M0tsyotJ.d.cts} +1 -1
- package/dist/{index-BL1wpOki.d.ts → index-rC8smShg.d.ts} +1 -1
- package/dist/index.cjs +64 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +64 -9
- package/dist/index.js.map +1 -1
- package/dist/{proxy-KrA1vEmh.d.ts → proxy-CZLY0IfL.d.cts} +5 -2
- package/dist/{proxy-0wa8QZIU.d.cts → proxy-D36SpZ6k.d.ts} +5 -2
- package/dist/signer/gateway.cjs +542 -0
- package/dist/signer/gateway.cjs.map +1 -0
- package/dist/signer/gateway.d.cts +81 -0
- package/dist/signer/gateway.d.ts +81 -0
- package/dist/signer/gateway.js +538 -0
- package/dist/signer/gateway.js.map +1 -0
- package/dist/signer/server.cjs +225 -0
- package/dist/signer/server.cjs.map +1 -1
- package/dist/signer/server.d.cts +35 -4
- package/dist/signer/server.d.ts +35 -4
- package/dist/signer/server.js +219 -1
- package/dist/signer/server.js.map +1 -1
- package/dist/signer/webhook/adapters/oidc.d.cts +2 -2
- package/dist/signer/webhook/adapters/oidc.d.ts +2 -2
- package/dist/signer/webhook.d.cts +3 -3
- package/dist/signer/webhook.d.ts +3 -3
- package/dist/tokens.d.cts +1 -1
- package/dist/tokens.d.ts +1 -1
- package/dist/{types-BORaHW_x.d.cts → types-CcP67AZm.d.cts} +2 -0
- package/dist/{types-BORaHW_x.d.ts → types-CcP67AZm.d.ts} +2 -0
- package/dist/verify.d.cts +1 -1
- package/dist/verify.d.ts +1 -1
- package/package.json +6 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FetchLike } from './types-
|
|
1
|
+
import { F as FetchLike } from './types-CcP67AZm.cjs';
|
|
2
2
|
|
|
3
3
|
type SignerDmzGate = "http" | "cli";
|
|
4
4
|
interface M2MClientCredentials {
|
|
@@ -26,7 +26,8 @@ interface DirectSignerProxyConfig {
|
|
|
26
26
|
resolveM2MCredentials?: (publicClientId: string) => M2MClientCredentials | Promise<M2MClientCredentials>;
|
|
27
27
|
/**
|
|
28
28
|
* When set, incoming request paths matching this prefix are rewritten to the remote signer base.
|
|
29
|
-
* Example: `/api/signer
|
|
29
|
+
* Example: `/api/my-bff/signer` → remote `/generate-live-payment` when the suffix is empty.
|
|
30
|
+
* This is for integrator-hosted BFF routes, not dashboard `/api/signer/*` proxy paths.
|
|
30
31
|
*/
|
|
31
32
|
proxyPathPrefix?: string;
|
|
32
33
|
/** Remote path used when the proxied suffix is empty. Defaults to `/generate-live-payment`. */
|
|
@@ -139,6 +140,7 @@ interface DeviceExchangeResponse {
|
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
interface ExchangeDeviceTokenForSignerOptions {
|
|
143
|
+
/** Platform/dashboard origin for JWT exchange only (`POST .../api/signer/device/exchange`). */
|
|
142
144
|
facadeUrl: string;
|
|
143
145
|
deviceToken: string;
|
|
144
146
|
scope?: string;
|
|
@@ -194,6 +196,7 @@ interface ApiKeyExchangeHandlerConfig {
|
|
|
194
196
|
allowInsecureHttp?: boolean;
|
|
195
197
|
}
|
|
196
198
|
interface ExchangeApiKeyForSignerOptions {
|
|
199
|
+
/** Platform/dashboard origin for JWT exchange only (`POST .../api/pymthouse/keys/exchange`). */
|
|
197
200
|
facadeUrl: string;
|
|
198
201
|
apiKey: string;
|
|
199
202
|
scope?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FetchLike } from './types-
|
|
1
|
+
import { F as FetchLike } from './types-CcP67AZm.js';
|
|
2
2
|
|
|
3
3
|
type SignerDmzGate = "http" | "cli";
|
|
4
4
|
interface M2MClientCredentials {
|
|
@@ -26,7 +26,8 @@ interface DirectSignerProxyConfig {
|
|
|
26
26
|
resolveM2MCredentials?: (publicClientId: string) => M2MClientCredentials | Promise<M2MClientCredentials>;
|
|
27
27
|
/**
|
|
28
28
|
* When set, incoming request paths matching this prefix are rewritten to the remote signer base.
|
|
29
|
-
* Example: `/api/signer
|
|
29
|
+
* Example: `/api/my-bff/signer` → remote `/generate-live-payment` when the suffix is empty.
|
|
30
|
+
* This is for integrator-hosted BFF routes, not dashboard `/api/signer/*` proxy paths.
|
|
30
31
|
*/
|
|
31
32
|
proxyPathPrefix?: string;
|
|
32
33
|
/** Remote path used when the proxied suffix is empty. Defaults to `/generate-live-payment`. */
|
|
@@ -139,6 +140,7 @@ interface DeviceExchangeResponse {
|
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
interface ExchangeDeviceTokenForSignerOptions {
|
|
143
|
+
/** Platform/dashboard origin for JWT exchange only (`POST .../api/signer/device/exchange`). */
|
|
142
144
|
facadeUrl: string;
|
|
143
145
|
deviceToken: string;
|
|
144
146
|
scope?: string;
|
|
@@ -194,6 +196,7 @@ interface ApiKeyExchangeHandlerConfig {
|
|
|
194
196
|
allowInsecureHttp?: boolean;
|
|
195
197
|
}
|
|
196
198
|
interface ExchangeApiKeyForSignerOptions {
|
|
199
|
+
/** Platform/dashboard origin for JWT exchange only (`POST .../api/pymthouse/keys/exchange`). */
|
|
197
200
|
facadeUrl: string;
|
|
198
201
|
apiKey: string;
|
|
199
202
|
scope?: string;
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var oauth4webapi = require('oauth4webapi');
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var PmtHouseError = class extends Error {
|
|
7
|
+
status;
|
|
8
|
+
code;
|
|
9
|
+
details;
|
|
10
|
+
constructor(message, {
|
|
11
|
+
status = 500,
|
|
12
|
+
code = "pymthouse_error",
|
|
13
|
+
details
|
|
14
|
+
} = {}) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "PmtHouseError";
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.details = details;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/string-utils.ts
|
|
24
|
+
function stripTrailingSlashes(value) {
|
|
25
|
+
let end = value.length;
|
|
26
|
+
while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {
|
|
27
|
+
end--;
|
|
28
|
+
}
|
|
29
|
+
return value.slice(0, end);
|
|
30
|
+
}
|
|
31
|
+
function endsWithIgnoreCase(value, suffix) {
|
|
32
|
+
if (suffix.length > value.length) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const start = value.length - suffix.length;
|
|
36
|
+
for (let i = 0; i < suffix.length; i++) {
|
|
37
|
+
const a = value.codePointAt(start + i) ?? 0;
|
|
38
|
+
const b = suffix.codePointAt(i) ?? 0;
|
|
39
|
+
if (a !== b && (a | 32) !== (b | 32)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
function stripSuffixIgnoreCase(value, suffix) {
|
|
46
|
+
return endsWithIgnoreCase(value, suffix) ? value.slice(0, value.length - suffix.length) : value;
|
|
47
|
+
}
|
|
48
|
+
function stripIssuerOriginFromOidcUrl(issuerUrl) {
|
|
49
|
+
let base = stripTrailingSlashes(issuerUrl.trim());
|
|
50
|
+
base = stripSuffixIgnoreCase(base, "/api/v1/oidc");
|
|
51
|
+
base = stripSuffixIgnoreCase(base, "/oidc");
|
|
52
|
+
return stripTrailingSlashes(base);
|
|
53
|
+
}
|
|
54
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
55
|
+
var discoveryCache = /* @__PURE__ */ new Map();
|
|
56
|
+
function normalizedIssuerKey(issuerUrl) {
|
|
57
|
+
return stripTrailingSlashes(issuerUrl);
|
|
58
|
+
}
|
|
59
|
+
async function loadAuthorizationServer(issuerUrl, fetchImpl, options = {}) {
|
|
60
|
+
const key = normalizedIssuerKey(issuerUrl);
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const cached = discoveryCache.get(key);
|
|
63
|
+
if (!options.force && cached && now - cached.fetchedAt < CACHE_TTL_MS) {
|
|
64
|
+
return cached.as;
|
|
65
|
+
}
|
|
66
|
+
const issuerIdentifier = new URL(key);
|
|
67
|
+
const discoveryOpts = {
|
|
68
|
+
algorithm: "oidc",
|
|
69
|
+
[oauth4webapi.customFetch]: fetchImpl
|
|
70
|
+
};
|
|
71
|
+
if (options.allowInsecureHttp) {
|
|
72
|
+
discoveryOpts[oauth4webapi.allowInsecureRequests] = true;
|
|
73
|
+
}
|
|
74
|
+
let response;
|
|
75
|
+
try {
|
|
76
|
+
response = await oauth4webapi.discoveryRequest(issuerIdentifier, discoveryOpts);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
throw mapDiscoveryNetworkError(e);
|
|
79
|
+
}
|
|
80
|
+
let as;
|
|
81
|
+
try {
|
|
82
|
+
as = await oauth4webapi.processDiscoveryResponse(issuerIdentifier, response);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
throw mapOAuthDiscoveryError(e);
|
|
85
|
+
}
|
|
86
|
+
discoveryCache.set(key, { as, fetchedAt: now });
|
|
87
|
+
return as;
|
|
88
|
+
}
|
|
89
|
+
function mapOAuthDiscoveryError(error) {
|
|
90
|
+
if (error instanceof PmtHouseError) {
|
|
91
|
+
return error;
|
|
92
|
+
}
|
|
93
|
+
if (error instanceof Error) {
|
|
94
|
+
return new PmtHouseError(error.message, {
|
|
95
|
+
status: 500,
|
|
96
|
+
code: "oidc_discovery_invalid",
|
|
97
|
+
details: { cause: error.cause }
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return new PmtHouseError("OIDC discovery failed", {
|
|
101
|
+
status: 500,
|
|
102
|
+
code: "oidc_discovery_invalid"
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function mapDiscoveryNetworkError(error) {
|
|
106
|
+
if (error instanceof PmtHouseError) {
|
|
107
|
+
return error;
|
|
108
|
+
}
|
|
109
|
+
if (error instanceof Error) {
|
|
110
|
+
return new PmtHouseError(`Failed to load OIDC discovery: ${error.message}`, {
|
|
111
|
+
status: 502,
|
|
112
|
+
code: "oidc_discovery_failed"
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return new PmtHouseError("Failed to load OIDC discovery", {
|
|
116
|
+
status: 502,
|
|
117
|
+
code: "oidc_discovery_failed"
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/encoding.ts
|
|
122
|
+
function encodeClientSecretBasic(clientId, clientSecret) {
|
|
123
|
+
const raw = `${clientId}:${clientSecret}`;
|
|
124
|
+
const b64 = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(Array.from(new TextEncoder().encode(raw), (c) => String.fromCharCode(c)).join(""));
|
|
125
|
+
return `Basic ${b64}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/signer/fetch-json.ts
|
|
129
|
+
function oauthFailureDescription(parsed, failureLabel, status) {
|
|
130
|
+
if (typeof parsed.error_description === "string") {
|
|
131
|
+
return parsed.error_description;
|
|
132
|
+
}
|
|
133
|
+
if (typeof parsed.error === "string") {
|
|
134
|
+
return parsed.error;
|
|
135
|
+
}
|
|
136
|
+
return `${failureLabel} (${status})`;
|
|
137
|
+
}
|
|
138
|
+
async function readJsonObjectFromResponse(response, options) {
|
|
139
|
+
const text = await response.text();
|
|
140
|
+
let parsed;
|
|
141
|
+
try {
|
|
142
|
+
parsed = text ? JSON.parse(text) : {};
|
|
143
|
+
} catch {
|
|
144
|
+
throw new PmtHouseError(options.invalidJsonMessage, {
|
|
145
|
+
status: 502,
|
|
146
|
+
code: options.invalidJsonCode,
|
|
147
|
+
details: { status: response.status }
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
const description = oauthFailureDescription(parsed, options.failureLabel, response.status);
|
|
152
|
+
throw new PmtHouseError(description, {
|
|
153
|
+
status: response.status,
|
|
154
|
+
code: typeof parsed.error === "string" ? parsed.error : options.defaultErrorCode,
|
|
155
|
+
details: parsed
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return parsed;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/signer/json-fields.ts
|
|
162
|
+
function readStringField(body, key, errorCode, messagePrefix = "Response") {
|
|
163
|
+
const value = body[key];
|
|
164
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
165
|
+
throw new PmtHouseError(`${messagePrefix} missing ${key}`, {
|
|
166
|
+
status: 502,
|
|
167
|
+
code: errorCode
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return value.trim();
|
|
171
|
+
}
|
|
172
|
+
function readExpiresIn(body, errorCode) {
|
|
173
|
+
const expiresIn = body.expires_in;
|
|
174
|
+
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
175
|
+
throw new PmtHouseError("Response missing expires_in", {
|
|
176
|
+
status: 502,
|
|
177
|
+
code: errorCode
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return Math.floor(expiresIn);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/signer/mint-token.ts
|
|
184
|
+
var SIGN_MINT_USER_TOKEN_SCOPE = "sign:mint_user_token";
|
|
185
|
+
var DEFAULT_TTL_REFRESH_RATIO = 0.8;
|
|
186
|
+
var TOKEN_RESPONSE_ERROR = "invalid_token_response";
|
|
187
|
+
function signerJwtAudience(issuerUrl) {
|
|
188
|
+
return stripTrailingSlashes(issuerUrl);
|
|
189
|
+
}
|
|
190
|
+
function parseMintUserSignerTokenResponse(body, ttlRefreshRatio = DEFAULT_TTL_REFRESH_RATIO) {
|
|
191
|
+
const accessToken = readStringField(body, "access_token", TOKEN_RESPONSE_ERROR, "Token response");
|
|
192
|
+
const expiresIn = readExpiresIn(body, TOKEN_RESPONSE_ERROR);
|
|
193
|
+
const balanceUsdMicros = readStringField(
|
|
194
|
+
body,
|
|
195
|
+
"balanceUsdMicros",
|
|
196
|
+
TOKEN_RESPONSE_ERROR,
|
|
197
|
+
"Token response"
|
|
198
|
+
);
|
|
199
|
+
const lifetimeGrantedUsdMicros = readStringField(
|
|
200
|
+
body,
|
|
201
|
+
"lifetimeGrantedUsdMicros",
|
|
202
|
+
TOKEN_RESPONSE_ERROR,
|
|
203
|
+
"Token response"
|
|
204
|
+
);
|
|
205
|
+
const now = Date.now();
|
|
206
|
+
const expiresAt = now + expiresIn * 1e3;
|
|
207
|
+
const refreshAt = now + Math.floor(expiresIn * 1e3 * ttlRefreshRatio);
|
|
208
|
+
return {
|
|
209
|
+
jwt: accessToken,
|
|
210
|
+
expiresAt,
|
|
211
|
+
refreshAt,
|
|
212
|
+
balanceUsdMicros,
|
|
213
|
+
lifetimeGrantedUsdMicros
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
async function mintUserSignerToken(options) {
|
|
217
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
218
|
+
const issuerUrl = stripTrailingSlashes(options.issuerUrl);
|
|
219
|
+
const as = await loadAuthorizationServer(issuerUrl, fetchImpl, {
|
|
220
|
+
allowInsecureHttp: options.allowInsecureHttp
|
|
221
|
+
});
|
|
222
|
+
const tokenEndpoint = as.token_endpoint;
|
|
223
|
+
if (!tokenEndpoint) {
|
|
224
|
+
throw new PmtHouseError("OIDC discovery document is missing token_endpoint", {
|
|
225
|
+
status: 500,
|
|
226
|
+
code: "oidc_discovery_invalid"
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const audience = signerJwtAudience(issuerUrl);
|
|
230
|
+
const body = new URLSearchParams({
|
|
231
|
+
grant_type: "client_credentials",
|
|
232
|
+
scope: SIGN_MINT_USER_TOKEN_SCOPE,
|
|
233
|
+
external_user_id: options.externalUserId,
|
|
234
|
+
audience
|
|
235
|
+
});
|
|
236
|
+
const response = await fetchImpl(tokenEndpoint, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: {
|
|
239
|
+
Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
|
|
240
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
241
|
+
Accept: "application/json"
|
|
242
|
+
},
|
|
243
|
+
body: body.toString(),
|
|
244
|
+
cache: "no-store"
|
|
245
|
+
});
|
|
246
|
+
const parsed = await readJsonObjectFromResponse(response, {
|
|
247
|
+
invalidJsonMessage: "Token endpoint returned invalid JSON",
|
|
248
|
+
invalidJsonCode: TOKEN_RESPONSE_ERROR,
|
|
249
|
+
failureLabel: "Token mint failed",
|
|
250
|
+
defaultErrorCode: "token_mint_failed"
|
|
251
|
+
});
|
|
252
|
+
return parseMintUserSignerTokenResponse(parsed);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/signer/device-exchange.ts
|
|
256
|
+
var TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange";
|
|
257
|
+
var SUBJECT_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
|
|
258
|
+
var EXCHANGE_RESPONSE_ERROR = "invalid_exchange_response";
|
|
259
|
+
async function mintSignerTokenFromDeviceToken(options) {
|
|
260
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
261
|
+
const issuerUrl = stripTrailingSlashes(options.issuerUrl);
|
|
262
|
+
const as = await loadAuthorizationServer(issuerUrl, fetchImpl, {
|
|
263
|
+
allowInsecureHttp: options.allowInsecureHttp
|
|
264
|
+
});
|
|
265
|
+
const tokenEndpoint = as.token_endpoint;
|
|
266
|
+
if (!tokenEndpoint) {
|
|
267
|
+
throw new PmtHouseError("OIDC discovery document is missing token_endpoint", {
|
|
268
|
+
status: 500,
|
|
269
|
+
code: "oidc_discovery_invalid"
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const audience = options.audience?.trim() || issuerUrl;
|
|
273
|
+
const params = new URLSearchParams({
|
|
274
|
+
grant_type: TOKEN_EXCHANGE_GRANT,
|
|
275
|
+
subject_token: options.deviceToken,
|
|
276
|
+
subject_token_type: SUBJECT_ACCESS_TOKEN_TYPE,
|
|
277
|
+
audience,
|
|
278
|
+
resource: audience
|
|
279
|
+
});
|
|
280
|
+
if (options.scope?.trim()) {
|
|
281
|
+
params.set("scope", options.scope.trim());
|
|
282
|
+
}
|
|
283
|
+
const response = await fetchImpl(tokenEndpoint, {
|
|
284
|
+
method: "POST",
|
|
285
|
+
headers: {
|
|
286
|
+
Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
|
|
287
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
288
|
+
Accept: "application/json"
|
|
289
|
+
},
|
|
290
|
+
body: params.toString(),
|
|
291
|
+
cache: "no-store"
|
|
292
|
+
});
|
|
293
|
+
const parsed = await readJsonObjectFromResponse(response, {
|
|
294
|
+
invalidJsonMessage: "Token endpoint returned invalid JSON",
|
|
295
|
+
invalidJsonCode: "invalid_token_response",
|
|
296
|
+
failureLabel: "Signer JWT exchange failed",
|
|
297
|
+
defaultErrorCode: "token_exchange_failed"
|
|
298
|
+
});
|
|
299
|
+
const cached = parseMintUserSignerTokenResponse(parsed);
|
|
300
|
+
return {
|
|
301
|
+
access_token: cached.jwt,
|
|
302
|
+
expires_in: readExpiresIn(parsed, EXCHANGE_RESPONSE_ERROR),
|
|
303
|
+
scope: readStringField(parsed, "scope", EXCHANGE_RESPONSE_ERROR),
|
|
304
|
+
balanceUsdMicros: cached.balanceUsdMicros,
|
|
305
|
+
lifetimeGrantedUsdMicros: cached.lifetimeGrantedUsdMicros
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/signer/api-key-exchange.ts
|
|
310
|
+
var EXCHANGE_RESPONSE_ERROR2 = "invalid_exchange_response";
|
|
311
|
+
async function mintUserAccessTokenFromApiKey(input) {
|
|
312
|
+
const fetchImpl = input.fetch ?? fetch;
|
|
313
|
+
const issuerOrigin = stripIssuerOriginFromOidcUrl(input.issuerUrl);
|
|
314
|
+
const url = `${issuerOrigin}/api/v1/apps/${encodeURIComponent(input.publicClientId)}/auth/api-key/token`;
|
|
315
|
+
const response = await fetchImpl(url, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
headers: {
|
|
318
|
+
Authorization: `Bearer ${input.apiKey}`,
|
|
319
|
+
"Content-Type": "application/json",
|
|
320
|
+
Accept: "application/json"
|
|
321
|
+
},
|
|
322
|
+
body: JSON.stringify(input.scope ? { scope: input.scope } : {}),
|
|
323
|
+
cache: "no-store"
|
|
324
|
+
});
|
|
325
|
+
const parsed = await readJsonObjectFromResponse(response, {
|
|
326
|
+
invalidJsonMessage: "API key token exchange returned invalid JSON",
|
|
327
|
+
invalidJsonCode: "invalid_token_response",
|
|
328
|
+
failureLabel: "API key token exchange failed",
|
|
329
|
+
defaultErrorCode: "api_key_token_exchange_failed"
|
|
330
|
+
});
|
|
331
|
+
const accessToken = parsed.access_token;
|
|
332
|
+
if (typeof accessToken !== "string" || !accessToken.trim()) {
|
|
333
|
+
throw new PmtHouseError("API key token exchange missing access_token", {
|
|
334
|
+
status: 502,
|
|
335
|
+
code: EXCHANGE_RESPONSE_ERROR2
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
const expiresIn = typeof parsed.expires_in === "number" && Number.isFinite(parsed.expires_in) ? parsed.expires_in : 900;
|
|
339
|
+
const scope = typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : input.scope?.trim() || "sign:job";
|
|
340
|
+
return {
|
|
341
|
+
access_token: accessToken.trim(),
|
|
342
|
+
expires_in: expiresIn,
|
|
343
|
+
scope
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
async function mintSignerSessionFromApiKey(input) {
|
|
347
|
+
const userToken = await mintUserAccessTokenFromApiKey({
|
|
348
|
+
issuerUrl: input.issuerUrl,
|
|
349
|
+
publicClientId: input.publicClientId,
|
|
350
|
+
apiKey: input.apiKey,
|
|
351
|
+
scope: input.scope,
|
|
352
|
+
fetch: input.fetch
|
|
353
|
+
});
|
|
354
|
+
return mintSignerTokenFromDeviceToken({
|
|
355
|
+
issuerUrl: input.issuerUrl,
|
|
356
|
+
m2mClientId: input.m2mClientId,
|
|
357
|
+
m2mClientSecret: input.m2mClientSecret,
|
|
358
|
+
deviceToken: userToken.access_token,
|
|
359
|
+
scope: userToken.scope,
|
|
360
|
+
audience: input.audience,
|
|
361
|
+
fetch: input.fetch,
|
|
362
|
+
allowInsecureHttp: input.allowInsecureHttp
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/signer/gateway-token.ts
|
|
367
|
+
function requireString(value, label) {
|
|
368
|
+
if (typeof value !== "string") {
|
|
369
|
+
throw new PmtHouseError(`${label} must be a string`, {
|
|
370
|
+
status: 400,
|
|
371
|
+
code: "invalid_gateway_token"
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return value.trim();
|
|
375
|
+
}
|
|
376
|
+
function optionalString(value, label) {
|
|
377
|
+
if (value === void 0 || value === null) {
|
|
378
|
+
return void 0;
|
|
379
|
+
}
|
|
380
|
+
return requireString(value, label) || void 0;
|
|
381
|
+
}
|
|
382
|
+
function encodeBase64Json(value) {
|
|
383
|
+
const json = JSON.stringify(value);
|
|
384
|
+
if (typeof Buffer === "undefined") {
|
|
385
|
+
const binary = Array.from(
|
|
386
|
+
new TextEncoder().encode(json),
|
|
387
|
+
(c) => String.fromCodePoint(c)
|
|
388
|
+
).join("");
|
|
389
|
+
return btoa(binary);
|
|
390
|
+
}
|
|
391
|
+
return Buffer.from(json, "utf8").toString("base64");
|
|
392
|
+
}
|
|
393
|
+
function decodeBase64Json(token) {
|
|
394
|
+
const trimmed = requireString(token, "gateway token");
|
|
395
|
+
let json;
|
|
396
|
+
try {
|
|
397
|
+
if (typeof Buffer === "undefined") {
|
|
398
|
+
json = new TextDecoder().decode(
|
|
399
|
+
Uint8Array.from(atob(trimmed), (c) => c.codePointAt(0) ?? 0)
|
|
400
|
+
);
|
|
401
|
+
} else {
|
|
402
|
+
json = Buffer.from(trimmed, "base64").toString("utf8");
|
|
403
|
+
}
|
|
404
|
+
} catch {
|
|
405
|
+
throw new PmtHouseError("Invalid gateway token: expected base64-encoded JSON", {
|
|
406
|
+
status: 400,
|
|
407
|
+
code: "invalid_gateway_token"
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
return JSON.parse(json);
|
|
412
|
+
} catch {
|
|
413
|
+
throw new PmtHouseError("Invalid gateway token: expected UTF-8 JSON payload", {
|
|
414
|
+
status: 400,
|
|
415
|
+
code: "invalid_gateway_token"
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function normalizeStringMap(map) {
|
|
420
|
+
if (!map) {
|
|
421
|
+
return void 0;
|
|
422
|
+
}
|
|
423
|
+
const entries = Object.entries(map).filter(
|
|
424
|
+
([key, value]) => key.trim() && typeof value === "string"
|
|
425
|
+
);
|
|
426
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
427
|
+
}
|
|
428
|
+
function buildGatewayToken(input) {
|
|
429
|
+
if (input === null || typeof input !== "object") {
|
|
430
|
+
throw new PmtHouseError("buildGatewayToken requires an input object", {
|
|
431
|
+
status: 400,
|
|
432
|
+
code: "invalid_gateway_token"
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
const signer = requireString(input.signer, "signer URL");
|
|
436
|
+
if (!signer) {
|
|
437
|
+
throw new PmtHouseError("buildGatewayToken requires a non-empty signer URL", {
|
|
438
|
+
status: 400,
|
|
439
|
+
code: "invalid_gateway_token"
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
const signerHeaders = { ...input.signerHeaders };
|
|
443
|
+
const bundle = { signer };
|
|
444
|
+
const discovery = optionalString(input.discovery, "discovery URL");
|
|
445
|
+
if (discovery) {
|
|
446
|
+
bundle.discovery = discovery;
|
|
447
|
+
}
|
|
448
|
+
const rawOrchestrators = input.orchestrators ?? [];
|
|
449
|
+
if (!Array.isArray(rawOrchestrators)) {
|
|
450
|
+
throw new PmtHouseError("orchestrators must be an array of strings", {
|
|
451
|
+
status: 400,
|
|
452
|
+
code: "invalid_gateway_token"
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
const orchestrators = rawOrchestrators.map((entry) => requireString(entry, "orchestrator entry")).filter((entry) => entry.length > 0);
|
|
456
|
+
if (orchestrators.length > 0) {
|
|
457
|
+
bundle.orchestrators = orchestrators;
|
|
458
|
+
}
|
|
459
|
+
if (input.auth?.kind === "signerJwt") {
|
|
460
|
+
const accessToken = requireString(input.auth.accessToken, "signerJwt accessToken");
|
|
461
|
+
if (!accessToken) {
|
|
462
|
+
throw new PmtHouseError("signerJwt auth requires a non-empty accessToken", {
|
|
463
|
+
status: 400,
|
|
464
|
+
code: "invalid_gateway_token"
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
signerHeaders.Authorization = `Bearer ${accessToken}`;
|
|
468
|
+
} else if (input.auth?.kind === "pmthApiKey") {
|
|
469
|
+
const apiKey = requireString(input.auth.apiKey, "pmthApiKey apiKey");
|
|
470
|
+
if (!apiKey) {
|
|
471
|
+
throw new PmtHouseError("pmthApiKey auth requires a non-empty apiKey", {
|
|
472
|
+
status: 400,
|
|
473
|
+
code: "invalid_gateway_token"
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
bundle.api_key = apiKey;
|
|
477
|
+
const billing = optionalString(input.auth.billing, "pmthApiKey billing URL");
|
|
478
|
+
if (billing) {
|
|
479
|
+
bundle.billing = billing;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const normalizedSignerHeaders = normalizeStringMap(signerHeaders);
|
|
483
|
+
if (normalizedSignerHeaders) {
|
|
484
|
+
bundle.signer_headers = normalizedSignerHeaders;
|
|
485
|
+
}
|
|
486
|
+
const normalizedDiscoveryHeaders = normalizeStringMap(input.discoveryHeaders);
|
|
487
|
+
if (normalizedDiscoveryHeaders) {
|
|
488
|
+
bundle.discovery_headers = normalizedDiscoveryHeaders;
|
|
489
|
+
}
|
|
490
|
+
return encodeBase64Json(bundle);
|
|
491
|
+
}
|
|
492
|
+
function decodeGatewayToken(token) {
|
|
493
|
+
const payload = decodeBase64Json(token);
|
|
494
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
495
|
+
throw new PmtHouseError("Invalid gateway token: payload must be a JSON object", {
|
|
496
|
+
status: 400,
|
|
497
|
+
code: "invalid_gateway_token"
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return payload;
|
|
501
|
+
}
|
|
502
|
+
async function mintGatewayToken(options) {
|
|
503
|
+
let accessToken;
|
|
504
|
+
if (options.source === "m2m") {
|
|
505
|
+
const minted = await mintUserSignerToken({
|
|
506
|
+
issuerUrl: options.issuerUrl,
|
|
507
|
+
m2mClientId: options.m2mClientId,
|
|
508
|
+
m2mClientSecret: options.m2mClientSecret,
|
|
509
|
+
externalUserId: options.externalUserId,
|
|
510
|
+
fetch: options.fetch,
|
|
511
|
+
allowInsecureHttp: options.allowInsecureHttp
|
|
512
|
+
});
|
|
513
|
+
accessToken = minted.jwt;
|
|
514
|
+
} else {
|
|
515
|
+
const minted = await mintSignerSessionFromApiKey({
|
|
516
|
+
issuerUrl: options.issuerUrl,
|
|
517
|
+
publicClientId: options.publicClientId,
|
|
518
|
+
m2mClientId: options.m2mClientId,
|
|
519
|
+
m2mClientSecret: options.m2mClientSecret,
|
|
520
|
+
apiKey: options.apiKey,
|
|
521
|
+
scope: options.scope,
|
|
522
|
+
audience: options.audience,
|
|
523
|
+
fetch: options.fetch,
|
|
524
|
+
allowInsecureHttp: options.allowInsecureHttp
|
|
525
|
+
});
|
|
526
|
+
accessToken = minted.access_token;
|
|
527
|
+
}
|
|
528
|
+
return buildGatewayToken({
|
|
529
|
+
signer: options.signer,
|
|
530
|
+
discovery: options.discovery,
|
|
531
|
+
orchestrators: options.orchestrators,
|
|
532
|
+
signerHeaders: options.signerHeaders,
|
|
533
|
+
discoveryHeaders: options.discoveryHeaders,
|
|
534
|
+
auth: { kind: "signerJwt", accessToken }
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
exports.buildGatewayToken = buildGatewayToken;
|
|
539
|
+
exports.decodeGatewayToken = decodeGatewayToken;
|
|
540
|
+
exports.mintGatewayToken = mintGatewayToken;
|
|
541
|
+
//# sourceMappingURL=gateway.cjs.map
|
|
542
|
+
//# sourceMappingURL=gateway.cjs.map
|