@pymthouse/builder-sdk 0.4.1-rc.2 → 0.4.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.
Files changed (60) hide show
  1. package/README.md +10 -10
  2. package/dist/env.cjs +4 -13
  3. package/dist/env.cjs.map +1 -1
  4. package/dist/env.js +4 -13
  5. package/dist/env.js.map +1 -1
  6. package/dist/{index-B0ryx942.d.cts → index-D5wdxNYy.d.cts} +1 -1
  7. package/dist/{index-CvV5syf_.d.ts → index-DFJ6qcK0.d.ts} +1 -1
  8. package/dist/index.cjs +4 -13
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +4 -13
  13. package/dist/index.js.map +1 -1
  14. package/dist/{proxy-JrT6raU_.d.cts → proxy-0wa8QZIU.d.cts} +16 -2
  15. package/dist/{proxy-U32DFNuj.d.ts → proxy-KrA1vEmh.d.ts} +16 -2
  16. package/dist/signer/server.cjs +89 -72
  17. package/dist/signer/server.cjs.map +1 -1
  18. package/dist/signer/server.d.cts +2 -2
  19. package/dist/signer/server.d.ts +2 -2
  20. package/dist/signer/server.js +89 -72
  21. package/dist/signer/server.js.map +1 -1
  22. package/dist/signer/webhook/adapters/api-key.cjs +1 -1
  23. package/dist/signer/webhook/adapters/api-key.cjs.map +1 -1
  24. package/dist/signer/webhook/adapters/api-key.d.cts +1 -1
  25. package/dist/signer/webhook/adapters/api-key.d.ts +1 -1
  26. package/dist/signer/webhook/adapters/api-key.js +1 -1
  27. package/dist/signer/webhook/adapters/api-key.js.map +1 -1
  28. package/dist/signer/webhook/adapters/composite.cjs +1 -1
  29. package/dist/signer/webhook/adapters/composite.cjs.map +1 -1
  30. package/dist/signer/webhook/adapters/composite.d.cts +1 -1
  31. package/dist/signer/webhook/adapters/composite.d.ts +1 -1
  32. package/dist/signer/webhook/adapters/composite.js +1 -1
  33. package/dist/signer/webhook/adapters/composite.js.map +1 -1
  34. package/dist/signer/webhook/adapters/oidc.cjs +6 -3
  35. package/dist/signer/webhook/adapters/oidc.cjs.map +1 -1
  36. package/dist/signer/webhook/adapters/oidc.d.cts +2 -2
  37. package/dist/signer/webhook/adapters/oidc.d.ts +2 -2
  38. package/dist/signer/webhook/adapters/oidc.js +6 -3
  39. package/dist/signer/webhook/adapters/oidc.js.map +1 -1
  40. package/dist/signer/webhook/adapters/trusted-headers.cjs +1 -1
  41. package/dist/signer/webhook/adapters/trusted-headers.cjs.map +1 -1
  42. package/dist/signer/webhook/adapters/trusted-headers.d.cts +1 -1
  43. package/dist/signer/webhook/adapters/trusted-headers.d.ts +1 -1
  44. package/dist/signer/webhook/adapters/trusted-headers.js +1 -1
  45. package/dist/signer/webhook/adapters/trusted-headers.js.map +1 -1
  46. package/dist/signer/webhook.cjs +7 -71
  47. package/dist/signer/webhook.cjs.map +1 -1
  48. package/dist/signer/webhook.d.cts +5 -14
  49. package/dist/signer/webhook.d.ts +5 -14
  50. package/dist/signer/webhook.js +8 -70
  51. package/dist/signer/webhook.js.map +1 -1
  52. package/dist/{verifier-B-WFDMz6.d.cts → verifier-Be9WAjFF.d.cts} +3 -2
  53. package/dist/{verifier-B-WFDMz6.d.ts → verifier-Be9WAjFF.d.ts} +3 -2
  54. package/package.json +2 -8
  55. package/dist/signer/webhook/adapters/oauth1.cjs +0 -18
  56. package/dist/signer/webhook/adapters/oauth1.cjs.map +0 -1
  57. package/dist/signer/webhook/adapters/oauth1.d.cts +0 -19
  58. package/dist/signer/webhook/adapters/oauth1.d.ts +0 -19
  59. package/dist/signer/webhook/adapters/oauth1.js +0 -16
  60. package/dist/signer/webhook/adapters/oauth1.js.map +0 -1
@@ -1,6 +1,10 @@
1
1
  import { F as FetchLike } from './types-BORaHW_x.cjs';
2
2
 
3
3
  type SignerDmzGate = "http" | "cli";
4
+ interface M2MClientCredentials {
5
+ m2mClientId: string;
6
+ m2mClientSecret: string;
7
+ }
4
8
  interface DirectSignerProxyConfig {
5
9
  pymthouseIssuerUrl: string;
6
10
  /** Public Builder app client id (`app_…`); used for cache keys and JWT `client_id`. */
@@ -10,6 +14,16 @@ interface DirectSignerProxyConfig {
10
14
  remoteSignerUrl: string | URL;
11
15
  fetch?: FetchLike;
12
16
  allowInsecureHttp?: boolean;
17
+ /**
18
+ * Multi-tenant: resolve the M2M credentials used to mint signer JWTs for a
19
+ * given `publicClientId`. The PymtHouse issuer binds the minted JWT's
20
+ * `client_id` to the developer app linked to these M2M credentials, so each
21
+ * tenant's `publicClientId` (from {@link DirectSignerProxyConfig.resolvePublicClientId})
22
+ * must map to the M2M credentials whose app uses it. The token manager
23
+ * validates that the minted `client_id` matches the requested `publicClientId`.
24
+ * Defaults to `pymthouseM2MClientId` / `pymthouseM2MClientSecret`.
25
+ */
26
+ resolveM2MCredentials?: (publicClientId: string) => M2MClientCredentials | Promise<M2MClientCredentials>;
13
27
  /**
14
28
  * When set, incoming request paths matching this prefix are rewritten to the remote signer base.
15
29
  * Example: `/api/signer/proxy` → remote `/generate-live-payment` when the suffix is empty.
@@ -53,7 +67,7 @@ interface MintUserSignerTokenResponse {
53
67
  lifetimeGrantedUsdMicros: string;
54
68
  }
55
69
  interface SignerTokenManagerOptions {
56
- mint: (externalUserId: string) => Promise<CachedSignerToken>;
70
+ mint: (publicClientId: string, externalUserId: string) => Promise<CachedSignerToken>;
57
71
  /** Fraction of TTL after which a proactive refresh runs. Defaults to `0.8`. */
58
72
  ttlRefreshRatio?: number;
59
73
  fetch?: FetchLike;
@@ -231,4 +245,4 @@ declare function probeSignerHttpReachability(options: ProbeSignerHttpReachabilit
231
245
  ethAddress?: string;
232
246
  }>;
233
247
 
234
- export { type ApiKeyExchangeHandlerConfig as A, resolveSignerBaseUrl as B, type CachedSignerToken as C, type DeviceExchangeHandlerConfig as D, type ExchangeDeviceTokenForSignerOptions as E, type ForwardDirectSignerRequestOptions as F, stripSignerUsageFromResponse as G, type MintUserSignerTokenOptions as M, type ProbeSignerHttpReachabilityOptions as P, type SignerUsageSnapshot as S, type SignerTokenManagerOptions as a, type SignerJwtIdentity as b, type DeviceExchangeHandlerConfigRemote as c, type DeviceExchangeResponse as d, type MintSignerTokenFromDeviceTokenOptions as e, type DeviceExchangeMintResult as f, type DeviceExchangeRequestBody as g, type ExchangeApiKeyForSignerOptions as h, type ApiKeyExchangeMintResult as i, type ApiKeyExchangeRequestBody as j, type DirectSignerProxyConfig as k, type DeviceExchangeMintContext as l, type DirectSignerBeforeSignContext as m, type DirectSignerBeforeSignResult as n, type ForwardToSignerOptions as o, type ForwardToSignerResult as p, type MintUserSignerTokenResponse as q, type SignerDmzGate as r, forwardToSigner as s, getCachedDmzBearerToken as t, normalizeSignerBaseUrl as u, parseSignerUsageSnapshot as v, pickConflictingNumberAliases as w, pickConflictingStringAliases as x, probeSignerHttpReachability as y, readSignerUpstreamBody as z };
248
+ export { type ApiKeyExchangeHandlerConfig as A, readSignerUpstreamBody as B, type CachedSignerToken as C, type DeviceExchangeHandlerConfig as D, type ExchangeDeviceTokenForSignerOptions as E, type ForwardDirectSignerRequestOptions as F, resolveSignerBaseUrl as G, stripSignerUsageFromResponse as H, type MintUserSignerTokenOptions as M, type ProbeSignerHttpReachabilityOptions as P, type SignerUsageSnapshot as S, type SignerTokenManagerOptions as a, type SignerJwtIdentity as b, type DeviceExchangeHandlerConfigRemote as c, type DeviceExchangeResponse as d, type MintSignerTokenFromDeviceTokenOptions as e, type DeviceExchangeMintResult as f, type DeviceExchangeRequestBody as g, type ExchangeApiKeyForSignerOptions as h, type ApiKeyExchangeMintResult as i, type ApiKeyExchangeRequestBody as j, type DirectSignerProxyConfig as k, type DeviceExchangeMintContext as l, type DirectSignerBeforeSignContext as m, type DirectSignerBeforeSignResult as n, type ForwardToSignerOptions as o, type ForwardToSignerResult as p, type M2MClientCredentials as q, type MintUserSignerTokenResponse as r, type SignerDmzGate as s, forwardToSigner as t, getCachedDmzBearerToken as u, normalizeSignerBaseUrl as v, parseSignerUsageSnapshot as w, pickConflictingNumberAliases as x, pickConflictingStringAliases as y, probeSignerHttpReachability as z };
@@ -1,6 +1,10 @@
1
1
  import { F as FetchLike } from './types-BORaHW_x.js';
2
2
 
3
3
  type SignerDmzGate = "http" | "cli";
4
+ interface M2MClientCredentials {
5
+ m2mClientId: string;
6
+ m2mClientSecret: string;
7
+ }
4
8
  interface DirectSignerProxyConfig {
5
9
  pymthouseIssuerUrl: string;
6
10
  /** Public Builder app client id (`app_…`); used for cache keys and JWT `client_id`. */
@@ -10,6 +14,16 @@ interface DirectSignerProxyConfig {
10
14
  remoteSignerUrl: string | URL;
11
15
  fetch?: FetchLike;
12
16
  allowInsecureHttp?: boolean;
17
+ /**
18
+ * Multi-tenant: resolve the M2M credentials used to mint signer JWTs for a
19
+ * given `publicClientId`. The PymtHouse issuer binds the minted JWT's
20
+ * `client_id` to the developer app linked to these M2M credentials, so each
21
+ * tenant's `publicClientId` (from {@link DirectSignerProxyConfig.resolvePublicClientId})
22
+ * must map to the M2M credentials whose app uses it. The token manager
23
+ * validates that the minted `client_id` matches the requested `publicClientId`.
24
+ * Defaults to `pymthouseM2MClientId` / `pymthouseM2MClientSecret`.
25
+ */
26
+ resolveM2MCredentials?: (publicClientId: string) => M2MClientCredentials | Promise<M2MClientCredentials>;
13
27
  /**
14
28
  * When set, incoming request paths matching this prefix are rewritten to the remote signer base.
15
29
  * Example: `/api/signer/proxy` → remote `/generate-live-payment` when the suffix is empty.
@@ -53,7 +67,7 @@ interface MintUserSignerTokenResponse {
53
67
  lifetimeGrantedUsdMicros: string;
54
68
  }
55
69
  interface SignerTokenManagerOptions {
56
- mint: (externalUserId: string) => Promise<CachedSignerToken>;
70
+ mint: (publicClientId: string, externalUserId: string) => Promise<CachedSignerToken>;
57
71
  /** Fraction of TTL after which a proactive refresh runs. Defaults to `0.8`. */
58
72
  ttlRefreshRatio?: number;
59
73
  fetch?: FetchLike;
@@ -231,4 +245,4 @@ declare function probeSignerHttpReachability(options: ProbeSignerHttpReachabilit
231
245
  ethAddress?: string;
232
246
  }>;
233
247
 
234
- export { type ApiKeyExchangeHandlerConfig as A, resolveSignerBaseUrl as B, type CachedSignerToken as C, type DeviceExchangeHandlerConfig as D, type ExchangeDeviceTokenForSignerOptions as E, type ForwardDirectSignerRequestOptions as F, stripSignerUsageFromResponse as G, type MintUserSignerTokenOptions as M, type ProbeSignerHttpReachabilityOptions as P, type SignerUsageSnapshot as S, type SignerTokenManagerOptions as a, type SignerJwtIdentity as b, type DeviceExchangeHandlerConfigRemote as c, type DeviceExchangeResponse as d, type MintSignerTokenFromDeviceTokenOptions as e, type DeviceExchangeMintResult as f, type DeviceExchangeRequestBody as g, type ExchangeApiKeyForSignerOptions as h, type ApiKeyExchangeMintResult as i, type ApiKeyExchangeRequestBody as j, type DirectSignerProxyConfig as k, type DeviceExchangeMintContext as l, type DirectSignerBeforeSignContext as m, type DirectSignerBeforeSignResult as n, type ForwardToSignerOptions as o, type ForwardToSignerResult as p, type MintUserSignerTokenResponse as q, type SignerDmzGate as r, forwardToSigner as s, getCachedDmzBearerToken as t, normalizeSignerBaseUrl as u, parseSignerUsageSnapshot as v, pickConflictingNumberAliases as w, pickConflictingStringAliases as x, probeSignerHttpReachability as y, readSignerUpstreamBody as z };
248
+ export { type ApiKeyExchangeHandlerConfig as A, readSignerUpstreamBody as B, type CachedSignerToken as C, type DeviceExchangeHandlerConfig as D, type ExchangeDeviceTokenForSignerOptions as E, type ForwardDirectSignerRequestOptions as F, resolveSignerBaseUrl as G, stripSignerUsageFromResponse as H, type MintUserSignerTokenOptions as M, type ProbeSignerHttpReachabilityOptions as P, type SignerUsageSnapshot as S, type SignerTokenManagerOptions as a, type SignerJwtIdentity as b, type DeviceExchangeHandlerConfigRemote as c, type DeviceExchangeResponse as d, type MintSignerTokenFromDeviceTokenOptions as e, type DeviceExchangeMintResult as f, type DeviceExchangeRequestBody as g, type ExchangeApiKeyForSignerOptions as h, type ApiKeyExchangeMintResult as i, type ApiKeyExchangeRequestBody as j, type DirectSignerProxyConfig as k, type DeviceExchangeMintContext as l, type DirectSignerBeforeSignContext as m, type DirectSignerBeforeSignResult as n, type ForwardToSignerOptions as o, type ForwardToSignerResult as p, type M2MClientCredentials as q, type MintUserSignerTokenResponse as r, type SignerDmzGate as s, forwardToSigner as t, getCachedDmzBearerToken as u, normalizeSignerBaseUrl as v, parseSignerUsageSnapshot as w, pickConflictingNumberAliases as x, pickConflictingStringAliases as y, probeSignerHttpReachability as z };
@@ -21,14 +21,8 @@ var PmtHouseError = class extends Error {
21
21
  };
22
22
 
23
23
  // src/signer/handler-errors.ts
24
- function isPmtHouseError(error) {
25
- if (error instanceof PmtHouseError) {
26
- return true;
27
- }
28
- return error instanceof Error && typeof error.status === "number" && typeof error.code === "string";
29
- }
30
24
  function signerHandlerErrorResponse(error) {
31
- if (isPmtHouseError(error)) {
25
+ if (error instanceof PmtHouseError) {
32
26
  return new Response(
33
27
  JSON.stringify({
34
28
  error: error.code,
@@ -48,62 +42,6 @@ function signerHandlerErrorResponse(error) {
48
42
  });
49
43
  }
50
44
 
51
- // src/signer/token-manager.ts
52
- function cacheKey(clientId, externalUserId) {
53
- return `${clientId}\0${externalUserId}`;
54
- }
55
- function createSignerTokenManager(options) {
56
- const ttlRefreshRatio = options.ttlRefreshRatio ?? 0.8;
57
- const cache = /* @__PURE__ */ new Map();
58
- const inflight = /* @__PURE__ */ new Map();
59
- function isUsable(entry, now, forceRefresh) {
60
- if (forceRefresh) return false;
61
- if (now >= entry.expiresAt) return false;
62
- if (now >= entry.refreshAt) return false;
63
- return true;
64
- }
65
- async function refresh(publicClientId, externalUserId) {
66
- const key = cacheKey(publicClientId, externalUserId);
67
- const existing = inflight.get(key);
68
- if (existing) {
69
- return existing;
70
- }
71
- const promise = options.mint(externalUserId).then((token) => {
72
- const normalized = {
73
- ...token,
74
- refreshAt: token.refreshAt || Date.now() + Math.floor((token.expiresAt - Date.now()) * ttlRefreshRatio)
75
- };
76
- cache.set(key, normalized);
77
- inflight.delete(key);
78
- return normalized;
79
- }).catch((error) => {
80
- inflight.delete(key);
81
- throw error;
82
- });
83
- inflight.set(key, promise);
84
- return promise;
85
- }
86
- return {
87
- peek(publicClientId, externalUserId) {
88
- return cache.get(cacheKey(publicClientId, externalUserId));
89
- },
90
- invalidate(publicClientId, externalUserId) {
91
- const key = cacheKey(publicClientId, externalUserId);
92
- cache.delete(key);
93
- inflight.delete(key);
94
- },
95
- async getToken(publicClientId, externalUserId, getOptions = {}) {
96
- const now = Date.now();
97
- const key = cacheKey(publicClientId, externalUserId);
98
- const cached = cache.get(key);
99
- if (cached && isUsable(cached, now, getOptions.forceRefresh === true)) {
100
- return cached;
101
- }
102
- return refresh(publicClientId, externalUserId);
103
- }
104
- };
105
- }
106
-
107
45
  // src/signer/forward.ts
108
46
  function base64UrlPayloadToUtf8(payloadB64) {
109
47
  const normalized = payloadB64.replaceAll("-", "+").replaceAll("_", "/");
@@ -217,6 +155,70 @@ async function forwardDirectSignerRequest(options) {
217
155
  return fetchImpl(target, init);
218
156
  }
219
157
 
158
+ // src/signer/token-manager.ts
159
+ function cacheKey(clientId, externalUserId) {
160
+ return `${clientId}\0${externalUserId}`;
161
+ }
162
+ function createSignerTokenManager(options) {
163
+ const ttlRefreshRatio = options.ttlRefreshRatio ?? 0.8;
164
+ const cache = /* @__PURE__ */ new Map();
165
+ const inflight = /* @__PURE__ */ new Map();
166
+ function isUsable(entry, now, forceRefresh) {
167
+ if (forceRefresh) return false;
168
+ if (now >= entry.expiresAt) return false;
169
+ if (now >= entry.refreshAt) return false;
170
+ return true;
171
+ }
172
+ async function refresh(publicClientId, externalUserId) {
173
+ const key = cacheKey(publicClientId, externalUserId);
174
+ const existing = inflight.get(key);
175
+ if (existing) {
176
+ return existing;
177
+ }
178
+ const promise = options.mint(publicClientId, externalUserId).then((token) => {
179
+ const identity = identityFromJwtPayload(decodeJwtPayload(token.jwt));
180
+ if (identity.clientId !== publicClientId) {
181
+ throw new PmtHouseError("minted JWT client_id does not match public client id", {
182
+ status: 500,
183
+ code: "invalid_client_id",
184
+ details: { expected: publicClientId, actual: identity.clientId }
185
+ });
186
+ }
187
+ const normalized = {
188
+ ...token,
189
+ refreshAt: token.refreshAt || Date.now() + Math.floor((token.expiresAt - Date.now()) * ttlRefreshRatio)
190
+ };
191
+ cache.set(key, normalized);
192
+ inflight.delete(key);
193
+ return normalized;
194
+ }).catch((error) => {
195
+ inflight.delete(key);
196
+ throw error;
197
+ });
198
+ inflight.set(key, promise);
199
+ return promise;
200
+ }
201
+ return {
202
+ peek(publicClientId, externalUserId) {
203
+ return cache.get(cacheKey(publicClientId, externalUserId));
204
+ },
205
+ invalidate(publicClientId, externalUserId) {
206
+ const key = cacheKey(publicClientId, externalUserId);
207
+ cache.delete(key);
208
+ inflight.delete(key);
209
+ },
210
+ async getToken(publicClientId, externalUserId, getOptions = {}) {
211
+ const now = Date.now();
212
+ const key = cacheKey(publicClientId, externalUserId);
213
+ const cached = cache.get(key);
214
+ if (cached && isUsable(cached, now, getOptions.forceRefresh === true)) {
215
+ return cached;
216
+ }
217
+ return refresh(publicClientId, externalUserId);
218
+ }
219
+ };
220
+ }
221
+
220
222
  // src/string-utils.ts
221
223
  function stripTrailingSlashes(value) {
222
224
  let end = value.length;
@@ -543,7 +545,7 @@ async function mintSignerTokenFromDeviceToken(options) {
543
545
  code: "oidc_discovery_invalid"
544
546
  });
545
547
  }
546
- const audience = options.audience?.trim() || signerJwtAudience(issuerUrl);
548
+ const audience = options.audience?.trim() || LIVEPEER_REMOTE_SIGNER_AUDIENCE;
547
549
  const params = new URLSearchParams({
548
550
  grant_type: TOKEN_EXCHANGE_GRANT,
549
551
  subject_token: options.deviceToken,
@@ -1157,15 +1159,30 @@ function toResponse(result) {
1157
1159
  });
1158
1160
  }
1159
1161
  function createDirectSignerProxyHandler(config) {
1160
- const tokenManager = createSignerTokenManager({
1161
- mint: (externalUserId) => mintUserSignerToken({
1162
- issuerUrl: config.pymthouseIssuerUrl,
1162
+ async function resolveM2MCredentials(publicClientId) {
1163
+ if (config.resolveM2MCredentials) {
1164
+ return config.resolveM2MCredentials(publicClientId);
1165
+ }
1166
+ return {
1163
1167
  m2mClientId: config.pymthouseM2MClientId,
1164
- m2mClientSecret: config.pymthouseM2MClientSecret,
1165
- externalUserId,
1166
- fetch: config.fetch,
1167
- allowInsecureHttp: config.allowInsecureHttp
1168
- })
1168
+ m2mClientSecret: config.pymthouseM2MClientSecret
1169
+ };
1170
+ }
1171
+ const tokenManager = createSignerTokenManager({
1172
+ // `publicClientId` selects the M2M credentials so the minted JWT's
1173
+ // `client_id` matches the cache partition key. The token manager rejects any
1174
+ // minted token whose `client_id` diverges from `publicClientId`.
1175
+ mint: async (publicClientId, externalUserId) => {
1176
+ const { m2mClientId, m2mClientSecret } = await resolveM2MCredentials(publicClientId);
1177
+ return mintUserSignerToken({
1178
+ issuerUrl: config.pymthouseIssuerUrl,
1179
+ m2mClientId,
1180
+ m2mClientSecret,
1181
+ externalUserId,
1182
+ fetch: config.fetch,
1183
+ allowInsecureHttp: config.allowInsecureHttp
1184
+ });
1185
+ }
1169
1186
  });
1170
1187
  async function runBeforeSign(token, externalUserId, request) {
1171
1188
  if (!config.beforeSign) {