@pymthouse/builder-sdk 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/device.cjs +246 -0
- package/dist/device.cjs.map +1 -0
- package/dist/device.d.cts +27 -0
- package/dist/device.d.ts +27 -0
- package/dist/device.js +244 -0
- package/dist/device.js.map +1 -0
- package/dist/env-4YmzarGJ.d.ts +68 -0
- package/dist/env-CZczUMzR.d.cts +68 -0
- package/dist/env.cjs +615 -0
- package/dist/env.cjs.map +1 -0
- package/dist/env.d.cts +2 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.js +612 -0
- package/dist/env.js.map +1 -0
- package/dist/format.cjs +27 -0
- package/dist/format.cjs.map +1 -0
- package/dist/format.d.cts +4 -0
- package/dist/format.d.ts +4 -0
- package/dist/format.js +24 -0
- package/dist/format.js.map +1 -0
- package/dist/index.cjs +685 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +670 -0
- package/dist/index.js.map +1 -0
- package/dist/types-W9PJAspR.d.cts +136 -0
- package/dist/types-W9PJAspR.d.ts +136 -0
- package/dist/verify.cjs +181 -0
- package/dist/verify.cjs.map +1 -0
- package/dist/verify.d.cts +18 -0
- package/dist/verify.d.ts +18 -0
- package/dist/verify.js +179 -0
- package/dist/verify.js.map +1 -0
- package/package.json +86 -0
package/dist/env.cjs
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var oauth4webapi = require('oauth4webapi');
|
|
4
|
+
|
|
5
|
+
// src/client.ts
|
|
6
|
+
|
|
7
|
+
// src/encoding.ts
|
|
8
|
+
function encodeClientSecretBasic(clientId, clientSecret) {
|
|
9
|
+
const raw = `${clientId}:${clientSecret}`;
|
|
10
|
+
const b64 = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(Array.from(new TextEncoder().encode(raw), (c) => String.fromCharCode(c)).join(""));
|
|
11
|
+
return `Basic ${b64}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/errors.ts
|
|
15
|
+
var PmtHouseError = class extends Error {
|
|
16
|
+
status;
|
|
17
|
+
code;
|
|
18
|
+
details;
|
|
19
|
+
constructor(message, {
|
|
20
|
+
status = 500,
|
|
21
|
+
code = "pymthouse_error",
|
|
22
|
+
details
|
|
23
|
+
} = {}) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "PmtHouseError";
|
|
26
|
+
this.status = status;
|
|
27
|
+
this.code = code;
|
|
28
|
+
this.details = details;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/string-utils.ts
|
|
33
|
+
function stripTrailingSlashes(value) {
|
|
34
|
+
let end = value.length;
|
|
35
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
36
|
+
end--;
|
|
37
|
+
}
|
|
38
|
+
return value.slice(0, end);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/discovery.ts
|
|
42
|
+
function authorizationServerToOidcDocument(as) {
|
|
43
|
+
const tokenEndpoint = as.token_endpoint;
|
|
44
|
+
const jwksUri = as.jwks_uri;
|
|
45
|
+
if (!tokenEndpoint || !jwksUri) {
|
|
46
|
+
throw new PmtHouseError("OIDC discovery document is missing token_endpoint or jwks_uri", {
|
|
47
|
+
status: 500,
|
|
48
|
+
code: "oidc_discovery_invalid"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
issuer: as.issuer,
|
|
53
|
+
authorization_endpoint: as.authorization_endpoint ?? "",
|
|
54
|
+
token_endpoint: tokenEndpoint,
|
|
55
|
+
jwks_uri: jwksUri,
|
|
56
|
+
userinfo_endpoint: as.userinfo_endpoint,
|
|
57
|
+
device_authorization_endpoint: as.device_authorization_endpoint
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
61
|
+
var discoveryCache = /* @__PURE__ */ new Map();
|
|
62
|
+
function normalizedIssuerKey(issuerUrl) {
|
|
63
|
+
return stripTrailingSlashes(issuerUrl);
|
|
64
|
+
}
|
|
65
|
+
async function loadAuthorizationServer(issuerUrl, fetchImpl, options = {}) {
|
|
66
|
+
const key = normalizedIssuerKey(issuerUrl);
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const cached = discoveryCache.get(key);
|
|
69
|
+
if (!options.force && cached && now - cached.fetchedAt < CACHE_TTL_MS) {
|
|
70
|
+
return cached.as;
|
|
71
|
+
}
|
|
72
|
+
const issuerIdentifier = new URL(key);
|
|
73
|
+
const discoveryOpts = {
|
|
74
|
+
algorithm: "oidc",
|
|
75
|
+
[oauth4webapi.customFetch]: fetchImpl
|
|
76
|
+
};
|
|
77
|
+
if (options.allowInsecureHttp) {
|
|
78
|
+
discoveryOpts[oauth4webapi.allowInsecureRequests] = true;
|
|
79
|
+
}
|
|
80
|
+
let response;
|
|
81
|
+
try {
|
|
82
|
+
response = await oauth4webapi.discoveryRequest(issuerIdentifier, discoveryOpts);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
throw mapDiscoveryNetworkError(e);
|
|
85
|
+
}
|
|
86
|
+
let as;
|
|
87
|
+
try {
|
|
88
|
+
as = await oauth4webapi.processDiscoveryResponse(issuerIdentifier, response);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
throw mapOAuthDiscoveryError(e);
|
|
91
|
+
}
|
|
92
|
+
discoveryCache.set(key, { as, fetchedAt: now });
|
|
93
|
+
return as;
|
|
94
|
+
}
|
|
95
|
+
function mapOAuthDiscoveryError(error) {
|
|
96
|
+
if (error instanceof PmtHouseError) {
|
|
97
|
+
return error;
|
|
98
|
+
}
|
|
99
|
+
if (error instanceof Error) {
|
|
100
|
+
return new PmtHouseError(error.message, {
|
|
101
|
+
status: 500,
|
|
102
|
+
code: "oidc_discovery_invalid",
|
|
103
|
+
details: { cause: error.cause }
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return new PmtHouseError("OIDC discovery failed", {
|
|
107
|
+
status: 500,
|
|
108
|
+
code: "oidc_discovery_invalid"
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function mapDiscoveryNetworkError(error) {
|
|
112
|
+
if (error instanceof PmtHouseError) {
|
|
113
|
+
return error;
|
|
114
|
+
}
|
|
115
|
+
if (error instanceof Error) {
|
|
116
|
+
return new PmtHouseError(`Failed to load OIDC discovery: ${error.message}`, {
|
|
117
|
+
status: 502,
|
|
118
|
+
code: "oidc_discovery_failed"
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return new PmtHouseError("Failed to load OIDC discovery", {
|
|
122
|
+
status: 502,
|
|
123
|
+
code: "oidc_discovery_failed"
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
var ACCEPTED_ISSUED_TOKEN_TYPES = /* @__PURE__ */ new Set([
|
|
127
|
+
"urn:ietf:params:oauth:token-type:access_token",
|
|
128
|
+
"urn:pmth:token-type:remote-signer-session"
|
|
129
|
+
]);
|
|
130
|
+
function mapOAuthError(error) {
|
|
131
|
+
if (error instanceof PmtHouseError) {
|
|
132
|
+
return error;
|
|
133
|
+
}
|
|
134
|
+
if (error instanceof oauth4webapi.ResponseBodyError) {
|
|
135
|
+
const cause = error.cause;
|
|
136
|
+
const description = typeof error.error_description === "string" ? error.error_description : error.message;
|
|
137
|
+
const details = { ...cause };
|
|
138
|
+
if (typeof cause.error_uri === "string") {
|
|
139
|
+
details.error_uri = cause.error_uri;
|
|
140
|
+
}
|
|
141
|
+
return new PmtHouseError(description, {
|
|
142
|
+
status: error.status,
|
|
143
|
+
code: error.error,
|
|
144
|
+
details
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (error instanceof oauth4webapi.OperationProcessingError) {
|
|
148
|
+
return new PmtHouseError(error.message, {
|
|
149
|
+
status: 502,
|
|
150
|
+
code: error.code ?? "oauth_processing_error",
|
|
151
|
+
details: { cause: error.cause }
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (error instanceof Error) {
|
|
155
|
+
return new PmtHouseError(error.message, {
|
|
156
|
+
status: 500,
|
|
157
|
+
code: "unexpected_error"
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return new PmtHouseError("Unexpected error", {
|
|
161
|
+
status: 500,
|
|
162
|
+
code: "unexpected_error"
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function tokenEndpointResponseToExchange(tr) {
|
|
166
|
+
const issued = tr.issued_token_type;
|
|
167
|
+
if (typeof issued !== "string" || !ACCEPTED_ISSUED_TOKEN_TYPES.has(issued)) {
|
|
168
|
+
throw new PmtHouseError("Token exchange returned an unexpected issued_token_type", {
|
|
169
|
+
status: 502,
|
|
170
|
+
code: "invalid_token_response",
|
|
171
|
+
details: { issued_token_type: issued }
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const tt = tr.token_type;
|
|
175
|
+
if (typeof tt !== "string" || tt.toLowerCase() !== "bearer") {
|
|
176
|
+
throw new PmtHouseError("Token endpoint returned a non-Bearer token_type", {
|
|
177
|
+
status: 502,
|
|
178
|
+
code: "invalid_token_response",
|
|
179
|
+
details: { token_type: tt }
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const expiresIn = tr.expires_in;
|
|
183
|
+
if (typeof expiresIn !== "number") {
|
|
184
|
+
throw new PmtHouseError("Token response missing expires_in", {
|
|
185
|
+
status: 502,
|
|
186
|
+
code: "invalid_token_response"
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
const scope = typeof tr.scope === "string" ? tr.scope : "";
|
|
190
|
+
return {
|
|
191
|
+
access_token: tr.access_token,
|
|
192
|
+
token_type: "Bearer",
|
|
193
|
+
expires_in: expiresIn,
|
|
194
|
+
scope,
|
|
195
|
+
issued_token_type: issued
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function tokenEndpointResponseToClientCredentials(tr) {
|
|
199
|
+
const tt = tr.token_type;
|
|
200
|
+
if (typeof tt !== "string" || tt.toLowerCase() !== "bearer") {
|
|
201
|
+
throw new PmtHouseError("Token endpoint returned a non-Bearer token_type", {
|
|
202
|
+
status: 502,
|
|
203
|
+
code: "invalid_token_response",
|
|
204
|
+
details: { token_type: tt }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
access_token: tr.access_token,
|
|
209
|
+
token_type: "Bearer",
|
|
210
|
+
expires_in: tr.expires_in,
|
|
211
|
+
scope: typeof tr.scope === "string" ? tr.scope : void 0
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function m2mClient(clientId) {
|
|
215
|
+
return { client_id: clientId };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/client.ts
|
|
219
|
+
var TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange";
|
|
220
|
+
var SUBJECT_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
|
|
221
|
+
var REQUESTED_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
|
|
222
|
+
var DEVICE_RESOURCE_PREFIX = "urn:pmth:device_code:";
|
|
223
|
+
function normalizeUserCode(value) {
|
|
224
|
+
return value.replace(/[a-z]/g, (char) => char.toUpperCase()).replace(/\W/g, "");
|
|
225
|
+
}
|
|
226
|
+
function buildDeviceCodeResource(userCode) {
|
|
227
|
+
return `${DEVICE_RESOURCE_PREFIX}${normalizeUserCode(userCode)}`;
|
|
228
|
+
}
|
|
229
|
+
var PmtHouseClient = class {
|
|
230
|
+
issuerUrl;
|
|
231
|
+
publicClientId;
|
|
232
|
+
m2mClientId;
|
|
233
|
+
m2mClientSecret;
|
|
234
|
+
fetchImpl;
|
|
235
|
+
logger;
|
|
236
|
+
allowInsecureHttp;
|
|
237
|
+
constructor(options) {
|
|
238
|
+
this.issuerUrl = stripTrailingSlashes(options.issuerUrl);
|
|
239
|
+
this.publicClientId = options.publicClientId;
|
|
240
|
+
this.m2mClientId = options.m2mClientId;
|
|
241
|
+
this.m2mClientSecret = options.m2mClientSecret;
|
|
242
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
243
|
+
this.logger = options.logger;
|
|
244
|
+
this.allowInsecureHttp = options.allowInsecureHttp ?? false;
|
|
245
|
+
}
|
|
246
|
+
async getDiscovery(options = {}) {
|
|
247
|
+
const as = await loadAuthorizationServer(this.issuerUrl, this.fetchImpl, {
|
|
248
|
+
force: options.force,
|
|
249
|
+
allowInsecureHttp: this.allowInsecureHttp
|
|
250
|
+
});
|
|
251
|
+
return authorizationServerToOidcDocument(as);
|
|
252
|
+
}
|
|
253
|
+
verifyIssuer(iss) {
|
|
254
|
+
const candidate = stripTrailingSlashes(iss.trim());
|
|
255
|
+
return candidate === this.issuerUrl;
|
|
256
|
+
}
|
|
257
|
+
parseDeviceApprovalRedirect(searchParams) {
|
|
258
|
+
const issuer = searchParams.get("iss")?.trim() ?? "";
|
|
259
|
+
const targetLinkUri = searchParams.get("target_link_uri")?.trim() ?? "";
|
|
260
|
+
if (!issuer || !targetLinkUri) {
|
|
261
|
+
throw new PmtHouseError("Missing iss or target_link_uri", {
|
|
262
|
+
status: 400,
|
|
263
|
+
code: "invalid_request"
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
if (!this.verifyIssuer(issuer)) {
|
|
267
|
+
throw new PmtHouseError("Issuer mismatch for initiate login", {
|
|
268
|
+
status: 400,
|
|
269
|
+
code: "invalid_issuer"
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
let targetUrl;
|
|
273
|
+
try {
|
|
274
|
+
targetUrl = new URL(targetLinkUri);
|
|
275
|
+
} catch {
|
|
276
|
+
throw new PmtHouseError("target_link_uri is not a valid URL", {
|
|
277
|
+
status: 400,
|
|
278
|
+
code: "invalid_target"
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
const issuerOrigin = new URL(this.issuerUrl).origin;
|
|
282
|
+
if (targetUrl.origin !== issuerOrigin || targetUrl.pathname !== "/oidc/device") {
|
|
283
|
+
throw new PmtHouseError(
|
|
284
|
+
"target_link_uri does not point to the issuer device path",
|
|
285
|
+
{
|
|
286
|
+
status: 400,
|
|
287
|
+
code: "invalid_target"
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
const userCode = normalizeUserCode(targetUrl.searchParams.get("user_code") ?? "");
|
|
292
|
+
const clientId = targetUrl.searchParams.get("client_id")?.trim() ?? "";
|
|
293
|
+
if (!userCode || !clientId) {
|
|
294
|
+
throw new PmtHouseError("target_link_uri is missing user_code or client_id", {
|
|
295
|
+
status: 400,
|
|
296
|
+
code: "invalid_target"
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
issuer,
|
|
301
|
+
targetLinkUri,
|
|
302
|
+
userCode,
|
|
303
|
+
clientId
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
async listAppUsers() {
|
|
307
|
+
const url = `${this.getAppsBaseUrl()}/users`;
|
|
308
|
+
return this.requestJson(url, {
|
|
309
|
+
method: "GET",
|
|
310
|
+
headers: this.builderHeaders(),
|
|
311
|
+
cache: "no-store"
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
async upsertAppUser(input) {
|
|
315
|
+
const payload = {
|
|
316
|
+
externalUserId: input.externalUserId
|
|
317
|
+
};
|
|
318
|
+
if (input.email) payload.email = input.email;
|
|
319
|
+
if (input.status) payload.status = input.status;
|
|
320
|
+
const url = `${this.getAppsBaseUrl()}/users`;
|
|
321
|
+
return this.requestJson(url, {
|
|
322
|
+
method: "POST",
|
|
323
|
+
headers: this.builderHeaders(),
|
|
324
|
+
body: JSON.stringify(payload),
|
|
325
|
+
cache: "no-store"
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
async deleteAppUser(params) {
|
|
329
|
+
const url = new URL(`${this.getAppsBaseUrl()}/users`);
|
|
330
|
+
url.searchParams.set("externalUserId", params.externalUserId);
|
|
331
|
+
return this.requestJson(url.toString(), {
|
|
332
|
+
method: "DELETE",
|
|
333
|
+
headers: this.builderHeaders(),
|
|
334
|
+
cache: "no-store"
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
async mintUserAccessToken(input) {
|
|
338
|
+
const url = `${this.getAppsBaseUrl()}/users/${encodeURIComponent(input.externalUserId)}/token`;
|
|
339
|
+
const body = input.scope ? { scope: input.scope } : {};
|
|
340
|
+
return this.requestJson(url, {
|
|
341
|
+
method: "POST",
|
|
342
|
+
headers: this.builderHeaders(),
|
|
343
|
+
body: JSON.stringify(body),
|
|
344
|
+
cache: "no-store"
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
async completeDeviceApproval(input) {
|
|
348
|
+
const as = await loadAuthorizationServer(this.issuerUrl, this.fetchImpl, {
|
|
349
|
+
allowInsecureHttp: this.allowInsecureHttp
|
|
350
|
+
});
|
|
351
|
+
const client = m2mClient(this.m2mClientId);
|
|
352
|
+
const clientAuth = this.m2mClientAuth();
|
|
353
|
+
const params = new URLSearchParams();
|
|
354
|
+
params.set("subject_token", input.userJwt);
|
|
355
|
+
params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE);
|
|
356
|
+
params.set("resource", buildDeviceCodeResource(input.userCode));
|
|
357
|
+
try {
|
|
358
|
+
const response = await oauth4webapi.genericTokenEndpointRequest(
|
|
359
|
+
as,
|
|
360
|
+
client,
|
|
361
|
+
clientAuth,
|
|
362
|
+
TOKEN_EXCHANGE_GRANT,
|
|
363
|
+
params,
|
|
364
|
+
this.tokenEndpointFetchOptions()
|
|
365
|
+
);
|
|
366
|
+
const tr = await oauth4webapi.processGenericTokenEndpointResponse(
|
|
367
|
+
as,
|
|
368
|
+
client,
|
|
369
|
+
response
|
|
370
|
+
);
|
|
371
|
+
return tokenEndpointResponseToExchange(tr);
|
|
372
|
+
} catch (e) {
|
|
373
|
+
throw mapOAuthError(e);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async issueMachineAccessToken(scope = "sign:job") {
|
|
377
|
+
const as = await loadAuthorizationServer(this.issuerUrl, this.fetchImpl, {
|
|
378
|
+
allowInsecureHttp: this.allowInsecureHttp
|
|
379
|
+
});
|
|
380
|
+
const client = m2mClient(this.m2mClientId);
|
|
381
|
+
const clientAuth = this.m2mClientAuth();
|
|
382
|
+
const params = new URLSearchParams();
|
|
383
|
+
params.set("scope", scope);
|
|
384
|
+
try {
|
|
385
|
+
const response = await oauth4webapi.clientCredentialsGrantRequest(
|
|
386
|
+
as,
|
|
387
|
+
client,
|
|
388
|
+
clientAuth,
|
|
389
|
+
params,
|
|
390
|
+
this.tokenEndpointFetchOptions()
|
|
391
|
+
);
|
|
392
|
+
const tr = await oauth4webapi.processClientCredentialsResponse(
|
|
393
|
+
as,
|
|
394
|
+
client,
|
|
395
|
+
response
|
|
396
|
+
);
|
|
397
|
+
return tokenEndpointResponseToClientCredentials(tr);
|
|
398
|
+
} catch (e) {
|
|
399
|
+
throw mapOAuthError(e);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async exchangeForSignerSession(input) {
|
|
403
|
+
const as = await loadAuthorizationServer(this.issuerUrl, this.fetchImpl, {
|
|
404
|
+
allowInsecureHttp: this.allowInsecureHttp
|
|
405
|
+
});
|
|
406
|
+
const client = m2mClient(this.m2mClientId);
|
|
407
|
+
const clientAuth = this.m2mClientAuth();
|
|
408
|
+
const params = new URLSearchParams();
|
|
409
|
+
params.set("subject_token", input.userJwt);
|
|
410
|
+
params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE);
|
|
411
|
+
params.set("requested_token_type", REQUESTED_ACCESS_TOKEN_TYPE);
|
|
412
|
+
const resourceCandidate = typeof input.resource === "string" && input.resource.trim() !== "" ? input.resource.trim() : this.issuerUrl;
|
|
413
|
+
params.set("resource", stripTrailingSlashes(resourceCandidate));
|
|
414
|
+
try {
|
|
415
|
+
const response = await oauth4webapi.genericTokenEndpointRequest(
|
|
416
|
+
as,
|
|
417
|
+
client,
|
|
418
|
+
clientAuth,
|
|
419
|
+
TOKEN_EXCHANGE_GRANT,
|
|
420
|
+
params,
|
|
421
|
+
this.tokenEndpointFetchOptions()
|
|
422
|
+
);
|
|
423
|
+
const tr = await oauth4webapi.processGenericTokenEndpointResponse(
|
|
424
|
+
as,
|
|
425
|
+
client,
|
|
426
|
+
response
|
|
427
|
+
);
|
|
428
|
+
return tokenEndpointResponseToExchange(tr);
|
|
429
|
+
} catch (e) {
|
|
430
|
+
throw mapOAuthError(e);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Mint a short-lived per-user JWT with the Builder API, then exchange it for
|
|
435
|
+
* a long-lived opaque signer session token at the PymtHouse OIDC token endpoint.
|
|
436
|
+
*/
|
|
437
|
+
async mintUserSignerSessionToken(input) {
|
|
438
|
+
const userToken = await this.mintUserAccessToken({
|
|
439
|
+
externalUserId: input.externalUserId,
|
|
440
|
+
scope: input.scope ?? "sign:job"
|
|
441
|
+
});
|
|
442
|
+
return this.exchangeForSignerSession({
|
|
443
|
+
userJwt: userToken.access_token,
|
|
444
|
+
resource: input.resource
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
async createSignerSessionToken(params) {
|
|
448
|
+
if (params.userJwt) {
|
|
449
|
+
try {
|
|
450
|
+
return await this.exchangeForSignerSession({ userJwt: params.userJwt });
|
|
451
|
+
} catch (error) {
|
|
452
|
+
const err = this.asError(error);
|
|
453
|
+
this.logger?.warn?.("User JWT exchange failed, falling back to machine exchange", {
|
|
454
|
+
code: err.code,
|
|
455
|
+
status: err.status
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const machineToken = await this.issueMachineAccessToken("sign:job");
|
|
460
|
+
if (!machineToken.access_token) {
|
|
461
|
+
throw new PmtHouseError("Client credentials flow did not return access_token", {
|
|
462
|
+
status: 502,
|
|
463
|
+
code: "invalid_token_response"
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
return this.exchangeForSignerSession({ userJwt: machineToken.access_token });
|
|
467
|
+
}
|
|
468
|
+
async getUsage(input = {}) {
|
|
469
|
+
const url = new URL(`${this.getAppsBaseUrl()}/usage`);
|
|
470
|
+
if (input.startDate) url.searchParams.set("startDate", input.startDate);
|
|
471
|
+
if (input.endDate) url.searchParams.set("endDate", input.endDate);
|
|
472
|
+
if (input.groupBy) url.searchParams.set("groupBy", input.groupBy);
|
|
473
|
+
if (input.userId) url.searchParams.set("userId", input.userId);
|
|
474
|
+
if (input.gatewayRequestId) url.searchParams.set("gatewayRequestId", input.gatewayRequestId);
|
|
475
|
+
return this.requestJson(url.toString(), {
|
|
476
|
+
method: "GET",
|
|
477
|
+
headers: this.builderHeaders(),
|
|
478
|
+
cache: "no-store"
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
tokenEndpointFetchOptions() {
|
|
482
|
+
const o = {
|
|
483
|
+
[oauth4webapi.customFetch]: this.fetchImpl
|
|
484
|
+
};
|
|
485
|
+
if (this.allowInsecureHttp) {
|
|
486
|
+
o[oauth4webapi.allowInsecureRequests] = true;
|
|
487
|
+
}
|
|
488
|
+
return o;
|
|
489
|
+
}
|
|
490
|
+
getAppsBaseUrl() {
|
|
491
|
+
return `${this.getIssuerOrigin()}/api/v1/apps/${encodeURIComponent(this.publicClientId)}`;
|
|
492
|
+
}
|
|
493
|
+
getIssuerOrigin() {
|
|
494
|
+
return new URL(this.issuerUrl).origin;
|
|
495
|
+
}
|
|
496
|
+
builderHeaders() {
|
|
497
|
+
return {
|
|
498
|
+
Authorization: encodeClientSecretBasic(this.m2mClientId, this.m2mClientSecret),
|
|
499
|
+
"Content-Type": "application/json",
|
|
500
|
+
Accept: "application/json"
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
m2mClientAuth() {
|
|
504
|
+
return (_as, _client, _body, headers) => {
|
|
505
|
+
headers.set("Authorization", encodeClientSecretBasic(this.m2mClientId, this.m2mClientSecret));
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
async requestJson(url, init) {
|
|
509
|
+
this.logger?.debug?.("PmtHouse request", {
|
|
510
|
+
method: init.method ?? "GET",
|
|
511
|
+
url
|
|
512
|
+
});
|
|
513
|
+
const response = await this.fetchImpl(url, init);
|
|
514
|
+
const raw = await response.text();
|
|
515
|
+
const ct = response.headers.get("content-type") ?? "";
|
|
516
|
+
const looksJson = ct.includes("application/json") || ct.includes("json");
|
|
517
|
+
const parsed = raw && looksJson ? this.safeParseJson(raw) : raw ? null : null;
|
|
518
|
+
if (!response.ok) {
|
|
519
|
+
const details = parsed ?? {};
|
|
520
|
+
const description = typeof details.error_description === "string" ? details.error_description : typeof details.error === "string" ? details.error : `Request failed (${response.status})`;
|
|
521
|
+
throw new PmtHouseError(description, {
|
|
522
|
+
status: response.status,
|
|
523
|
+
code: typeof details.error === "string" ? details.error : "pymthouse_http_error",
|
|
524
|
+
details
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
if (!looksJson || parsed === null) {
|
|
528
|
+
throw new PmtHouseError("Expected JSON response from Builder or Usage API", {
|
|
529
|
+
status: 502,
|
|
530
|
+
code: "invalid_response",
|
|
531
|
+
details: { contentType: ct, preview: raw.slice(0, 200) }
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (!parsed) {
|
|
535
|
+
return {};
|
|
536
|
+
}
|
|
537
|
+
return parsed;
|
|
538
|
+
}
|
|
539
|
+
safeParseJson(value) {
|
|
540
|
+
try {
|
|
541
|
+
return JSON.parse(value);
|
|
542
|
+
} catch {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
asError(error) {
|
|
547
|
+
if (error instanceof PmtHouseError) {
|
|
548
|
+
return error;
|
|
549
|
+
}
|
|
550
|
+
if (error instanceof Error) {
|
|
551
|
+
return new PmtHouseError(error.message, {
|
|
552
|
+
code: "unexpected_error",
|
|
553
|
+
status: 500
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
return new PmtHouseError("Unexpected error", {
|
|
557
|
+
code: "unexpected_error",
|
|
558
|
+
status: 500
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// src/env.ts
|
|
564
|
+
function assertEnvModuleServerOnly() {
|
|
565
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined") {
|
|
566
|
+
throw new Error(
|
|
567
|
+
"@pymthouse/builder-sdk/env is server-only: do not import createPmtHouseClientFromEnv or getPymthouseBaseUrl in client-side code. Use a Route Handler, Server Action, or other server/runtime; keep M2M credentials out of the browser bundle."
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
assertEnvModuleServerOnly();
|
|
572
|
+
var cachedClient = null;
|
|
573
|
+
function requiredEnv(name) {
|
|
574
|
+
const value = process.env[name];
|
|
575
|
+
if (value && value.trim()) {
|
|
576
|
+
return value.trim();
|
|
577
|
+
}
|
|
578
|
+
throw new PmtHouseError(`Missing required environment variable: ${name}`, {
|
|
579
|
+
status: 500,
|
|
580
|
+
code: "missing_env"
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
function getPymthouseBaseUrl() {
|
|
584
|
+
const issuerUrl = requiredEnv("PYMTHOUSE_ISSUER_URL");
|
|
585
|
+
return new URL(stripTrailingSlashes(issuerUrl)).origin;
|
|
586
|
+
}
|
|
587
|
+
function createPmtHouseClientFromEnv() {
|
|
588
|
+
if (cachedClient) {
|
|
589
|
+
return cachedClient;
|
|
590
|
+
}
|
|
591
|
+
const issuerUrl = requiredEnv("PYMTHOUSE_ISSUER_URL");
|
|
592
|
+
cachedClient = new PmtHouseClient({
|
|
593
|
+
issuerUrl,
|
|
594
|
+
publicClientId: requiredEnv("PYMTHOUSE_PUBLIC_CLIENT_ID"),
|
|
595
|
+
m2mClientId: requiredEnv("PYMTHOUSE_M2M_CLIENT_ID"),
|
|
596
|
+
m2mClientSecret: requiredEnv("PYMTHOUSE_M2M_CLIENT_SECRET"),
|
|
597
|
+
allowInsecureHttp: issuerUrl.startsWith("http:"),
|
|
598
|
+
logger: {
|
|
599
|
+
debug: (message, details) => {
|
|
600
|
+
if (process.env.NODE_ENV !== "production") {
|
|
601
|
+
console.debug(`[pymthouse] ${message}`, details ?? {});
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
warn: (message, details) => {
|
|
605
|
+
console.warn(`[pymthouse] ${message}`, details ?? {});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
return cachedClient;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
exports.createPmtHouseClientFromEnv = createPmtHouseClientFromEnv;
|
|
613
|
+
exports.getPymthouseBaseUrl = getPymthouseBaseUrl;
|
|
614
|
+
//# sourceMappingURL=env.cjs.map
|
|
615
|
+
//# sourceMappingURL=env.cjs.map
|