@pymthouse/builder-sdk 0.3.1 → 0.4.1-rc.2

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 (103) hide show
  1. package/README.md +54 -28
  2. package/dist/{client-BHfjDvIe.d.ts → client-CauCfGa7.d.ts} +1 -1
  3. package/dist/{client-CvhJEhjV.d.cts → client-D1Xz-xlx.d.cts} +1 -1
  4. package/dist/config.cjs +0 -21
  5. package/dist/config.cjs.map +1 -1
  6. package/dist/config.d.cts +1 -5
  7. package/dist/config.d.ts +1 -5
  8. package/dist/config.js +1 -20
  9. package/dist/config.js.map +1 -1
  10. package/dist/device-initiate.cjs.map +1 -1
  11. package/dist/device-initiate.js.map +1 -1
  12. package/dist/device.cjs.map +1 -1
  13. package/dist/device.d.cts +1 -1
  14. package/dist/device.d.ts +1 -1
  15. package/dist/device.js.map +1 -1
  16. package/dist/env.cjs +13 -4
  17. package/dist/env.cjs.map +1 -1
  18. package/dist/env.d.cts +2 -2
  19. package/dist/env.d.ts +2 -2
  20. package/dist/env.js +13 -4
  21. package/dist/env.js.map +1 -1
  22. package/dist/index-B0ryx942.d.cts +66 -0
  23. package/dist/index-CvV5syf_.d.ts +66 -0
  24. package/dist/index.cjs +13 -4
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +29 -5
  27. package/dist/index.d.ts +29 -5
  28. package/dist/index.js +13 -4
  29. package/dist/index.js.map +1 -1
  30. package/dist/{ingest-DoKJTWU9.d.ts → proxy-JrT6raU_.d.cts} +5 -42
  31. package/dist/{ingest-B3Yi8Tb1.d.cts → proxy-U32DFNuj.d.ts} +5 -42
  32. package/dist/signer/server.cjs +799 -895
  33. package/dist/signer/server.cjs.map +1 -1
  34. package/dist/signer/server.d.cts +11 -15
  35. package/dist/signer/server.d.ts +11 -15
  36. package/dist/signer/server.js +799 -893
  37. package/dist/signer/server.js.map +1 -1
  38. package/dist/signer/webhook/adapters/api-key.cjs +82 -0
  39. package/dist/signer/webhook/adapters/api-key.cjs.map +1 -0
  40. package/dist/signer/webhook/adapters/api-key.d.cts +18 -0
  41. package/dist/signer/webhook/adapters/api-key.d.ts +18 -0
  42. package/dist/signer/webhook/adapters/api-key.js +80 -0
  43. package/dist/signer/webhook/adapters/api-key.js.map +1 -0
  44. package/dist/signer/webhook/adapters/composite.cjs +60 -0
  45. package/dist/signer/webhook/adapters/composite.cjs.map +1 -0
  46. package/dist/signer/webhook/adapters/composite.d.cts +5 -0
  47. package/dist/signer/webhook/adapters/composite.d.ts +5 -0
  48. package/dist/signer/webhook/adapters/composite.js +58 -0
  49. package/dist/signer/webhook/adapters/composite.js.map +1 -0
  50. package/dist/signer/webhook/adapters/oauth1.cjs +18 -0
  51. package/dist/signer/webhook/adapters/oauth1.cjs.map +1 -0
  52. package/dist/signer/webhook/adapters/oauth1.d.cts +19 -0
  53. package/dist/signer/webhook/adapters/oauth1.d.ts +19 -0
  54. package/dist/signer/webhook/adapters/oauth1.js +16 -0
  55. package/dist/signer/webhook/adapters/oauth1.js.map +1 -0
  56. package/dist/signer/webhook/adapters/oidc.cjs +533 -0
  57. package/dist/signer/webhook/adapters/oidc.cjs.map +1 -0
  58. package/dist/signer/webhook/adapters/oidc.d.cts +4 -0
  59. package/dist/signer/webhook/adapters/oidc.d.ts +4 -0
  60. package/dist/signer/webhook/adapters/oidc.js +526 -0
  61. package/dist/signer/webhook/adapters/oidc.js.map +1 -0
  62. package/dist/signer/webhook/adapters/trusted-headers.cjs +110 -0
  63. package/dist/signer/webhook/adapters/trusted-headers.cjs.map +1 -0
  64. package/dist/signer/webhook/adapters/trusted-headers.d.cts +18 -0
  65. package/dist/signer/webhook/adapters/trusted-headers.d.ts +18 -0
  66. package/dist/signer/webhook/adapters/trusted-headers.js +106 -0
  67. package/dist/signer/webhook/adapters/trusted-headers.js.map +1 -0
  68. package/dist/signer/webhook.cjs +759 -0
  69. package/dist/signer/webhook.cjs.map +1 -0
  70. package/dist/signer/webhook.d.cts +26 -0
  71. package/dist/signer/webhook.d.ts +26 -0
  72. package/dist/signer/webhook.js +732 -0
  73. package/dist/signer/webhook.js.map +1 -0
  74. package/dist/tokens.d.cts +1 -1
  75. package/dist/tokens.d.ts +1 -1
  76. package/dist/{types-_R1AwEZp.d.cts → types-BORaHW_x.d.cts} +5 -5
  77. package/dist/{types-_R1AwEZp.d.ts → types-BORaHW_x.d.ts} +5 -5
  78. package/dist/verifier-B-WFDMz6.d.cts +48 -0
  79. package/dist/verifier-B-WFDMz6.d.ts +48 -0
  80. package/dist/verify.cjs.map +1 -1
  81. package/dist/verify.d.cts +1 -1
  82. package/dist/verify.d.ts +1 -1
  83. package/dist/verify.js.map +1 -1
  84. package/package.json +30 -30
  85. package/dist/gateway/client/index.cjs +0 -492
  86. package/dist/gateway/client/index.cjs.map +0 -1
  87. package/dist/gateway/client/index.d.cts +0 -63
  88. package/dist/gateway/client/index.d.ts +0 -63
  89. package/dist/gateway/client/index.js +0 -489
  90. package/dist/gateway/client/index.js.map +0 -1
  91. package/dist/gateway/index.cjs +0 -16
  92. package/dist/gateway/index.cjs.map +0 -1
  93. package/dist/gateway/index.d.cts +0 -52
  94. package/dist/gateway/index.d.ts +0 -52
  95. package/dist/gateway/index.js +0 -10
  96. package/dist/gateway/index.js.map +0 -1
  97. package/dist/gateway/server/index.cjs +0 -1248
  98. package/dist/gateway/server/index.cjs.map +0 -1
  99. package/dist/gateway/server/index.d.cts +0 -31
  100. package/dist/gateway/server/index.d.ts +0 -31
  101. package/dist/gateway/server/index.js +0 -1233
  102. package/dist/gateway/server/index.js.map +0 -1
  103. package/gateway/proto/lp_rpc.proto +0 -542
package/README.md CHANGED
@@ -84,32 +84,6 @@ const session = await client.exchangeApiKeyForSignerSession({
84
84
 
85
85
  See `examples/stream-with-api-key.mjs` for a minimal Node script.
86
86
 
87
- ## Browser gateway (optional module)
88
-
89
- Live Video-to-Video streaming from the browser uses a **same-origin HTTP segment relay**
90
- implemented in optional subpaths (not exported from the main entry):
91
-
92
- | Subpath | Use |
93
- |---------|-----|
94
- | `@pymthouse/builder-sdk/gateway` | Shared types |
95
- | `@pymthouse/builder-sdk/gateway/client` | `BrowserGatewayClient` for dashboard / browser apps |
96
- | `@pymthouse/builder-sdk/gateway/server` | Route Handler factories for Next.js |
97
-
98
- Install peer dependencies when using the server module:
99
-
100
- ```bash
101
- pnpm add @grpc/grpc-js @grpc/proto-loader
102
- ```
103
-
104
- Auth flow (same signer bearer as Python `livepeer-python-gateway`):
105
-
106
- 1. `exchangeApiKeyForSignerSession({ apiKey, facadeUrl: dashboardOrigin })` or `POST /api/pymthouse/keys/exchange`
107
- 2. `Authorization: Bearer <signer_token>` on `POST /api/gateway/sessions`
108
-
109
- Enable relay on the dashboard with `GATEWAY_ENABLED=1` and `NEXT_PUBLIC_GATEWAY_ENABLED=1`.
110
-
111
- See `examples/gateway-session-smoke.mjs` for a headless session start test.
112
-
113
87
  Integrators can use the higher-level workflow helpers:
114
88
 
115
89
  ```ts
@@ -145,11 +119,63 @@ const { manifest, etag, notModified } = await client.getAppManifest({
145
119
  });
146
120
  ```
147
121
 
122
+ ## Remote signer identity webhook
123
+
124
+ For go-livepeer `-remoteSignerWebhookUrl` deployments, builder-sdk provides the
125
+ reference **integration security** webhook that validates end-user credentials and
126
+ returns `UsageIdentity` to the signer (`POST /authorize`).
127
+
128
+ Transport (signer shared-secret auth, wire protocol) is separate from **end-user
129
+ auth strategies** (`EndUserAuthVerifier`). OIDC/JWT is the default MVP; OAuth 1.0
130
+ has a stub verifier for future integrations.
131
+
132
+ ```ts
133
+ import {
134
+ createOidcRemoteSignerWebhookConfig,
135
+ createRemoteSignerAuthorizeHandler,
136
+ readOidcRemoteSignerWebhookConfigFromEnv,
137
+ startRemoteSignerWebhookServer,
138
+ type EndUserAuthVerifier,
139
+ } from "@pymthouse/builder-sdk/signer/webhook";
140
+
141
+ // Standalone sidecar (loads JWT_ISSUER, JWT_AUDIENCE, WEBHOOK_SECRET from env)
142
+ startRemoteSignerWebhookServer();
143
+
144
+ // OIDC (default): Auth0, pymthouse issuer, etc.
145
+ const authorize = createRemoteSignerAuthorizeHandler(
146
+ createOidcRemoteSignerWebhookConfig({
147
+ webhookSecret: process.env.WEBHOOK_SECRET!,
148
+ jwtIssuer: process.env.JWT_ISSUER!,
149
+ jwtAudience: process.env.JWT_AUDIENCE!,
150
+ claimMapping: { claimClientId: "azp", usageSubjectType: "auth0_user_id" },
151
+ }),
152
+ );
153
+
154
+ // Custom provider: implement EndUserAuthVerifier
155
+ const customConfig = {
156
+ webhookSecret: process.env.WEBHOOK_SECRET!,
157
+ endUserAuth: {
158
+ kind: "custom",
159
+ verify: async ({ authorization, payload, request }) => {
160
+ // validate provider credentials, return UsageIdentity
161
+ return { identity: { ... }, expiry: Math.trunc(Date.now() / 1000) + 300 };
162
+ },
163
+ } satisfies EndUserAuthVerifier,
164
+ };
165
+ ```
166
+
167
+ Env vars align with `auth0-livepeer` bootstrap output (`.env.livepeer`). For Auth0,
168
+ set `CLAIM_CLIENT_ID=azp` and `USAGE_SUBJECT_TYPE=auth0_user_id`.
169
+
170
+ OAuth 1.0: `createOAuth1EndUserVerifier` exists as a stub; signature verification
171
+ is not implemented yet.
172
+
148
173
  ## Subpath exports
149
174
 
150
175
  | Import | Purpose |
151
176
  |--------|---------|
152
177
  | `@pymthouse/builder-sdk` | `PmtHouseClient`, usage helpers, manifest parsers, token helpers |
178
+ | `@pymthouse/builder-sdk/signer/webhook` | Identity webhook for `-remoteSignerWebhookUrl` |
153
179
  | `@pymthouse/builder-sdk/config` | `isPymthouseConfigured`, `readPymthouseEnv` (Edge/middleware-safe) |
154
180
  | `@pymthouse/builder-sdk/tokens` | Signer session TTL, JWT shape helpers, `parseSignerSessionExchange` |
155
181
  | `@pymthouse/builder-sdk/format` | Wei formatting for Usage API |
@@ -178,9 +204,9 @@ const summary = summarizeUsageForExternalUser(usage, externalUserId);
178
204
 
179
205
  **Retail estimates:** `getUsage({ includeRetail: true, groupBy: "pipeline_model" })` adds `endUserBillableUsdMicros` / fiat rows when the active plan has retail rates.
180
206
 
181
- **Signed-ticket ingest (platform metering):** after a signer proxy response, call `ingestSignedTicket` or use `forwardWithOptionalMetering` with `metering: { mode: "pymthouse_hosted" }` on `createSignerProxyServer` usage is stripped from the client response and POSTed to `POST /api/v1/apps/{id}/usage/signed-tickets`.
207
+ **Metering:** sign directly against the remote signer DMZ with `createDirectSignerProxyHandler` or `forwardDirectSignerRequest`. Usage is emitted asynchronously by go-livepeer to Kafka and ingested by the OpenMeter collector. The PymtHouse `/api/signer/*` HTTP proxy and synchronous HTTP signed-ticket ingest are removed.
182
208
 
183
- **Routing:** `getSignerRouting()` returns `signerApiUrl`, `remoteDmzUrl`, `meteringMode`, and pattern hints for hosted vs platform-ingest vs BYO OpenMeter.
209
+ **Routing:** `getSignerRouting()` returns the remote DMZ URL, webhook URL, and migration hints (`directDmz` / `deprecatedHostedFacade`).
184
210
 
185
211
  **Allowances (OpenMeter):** Trial and manual USD micros allowance use OpenMeter entitlements — not a Postgres wei ledger.
186
212
 
@@ -1,5 +1,5 @@
1
1
  import { SignerSessionToken } from './tokens.js';
2
- import { P as PmtHouseClientOptions, G as GetDiscoveryOptions, O as OidcDiscoveryDocument, a as ParsedDeviceApprovalRedirect, A as AppUserRecord, U as UpsertAppUserInput, M as MintUserAccessTokenInput, b as MintUserAccessTokenResponse, T as TokenExchangeResponse, D as DeviceApprovalInput, C as ClientCredentialsTokenResponse, c as MintUserSignerSessionTokenInput, d as UsageQueryInput, e as UsageApiResponse, S as SignedTicketIngestInput, f as SignedTicketIngestResult, g as SignerRoutingResponse, L as ListBillingProductsResult, h as PlanSyncResult, i as UsageBalanceResponse, j as UserAllowancesResponse, k as UserAllowanceGrantInput, l as GrantSource, m as UserSubscriptionResponse, n as MeScopeUsagePayload, o as GetAppManifestResult, p as MintSignerSessionForExternalUserInput, q as ApproveDeviceLoginInput } from './types-_R1AwEZp.js';
2
+ import { P as PmtHouseClientOptions, G as GetDiscoveryOptions, O as OidcDiscoveryDocument, a as ParsedDeviceApprovalRedirect, A as AppUserRecord, U as UpsertAppUserInput, M as MintUserAccessTokenInput, b as MintUserAccessTokenResponse, T as TokenExchangeResponse, D as DeviceApprovalInput, C as ClientCredentialsTokenResponse, c as MintUserSignerSessionTokenInput, d as UsageQueryInput, e as UsageApiResponse, S as SignedTicketIngestInput, f as SignedTicketIngestResult, g as SignerRoutingResponse, L as ListBillingProductsResult, h as PlanSyncResult, i as UsageBalanceResponse, j as UserAllowancesResponse, k as UserAllowanceGrantInput, l as GrantSource, m as UserSubscriptionResponse, n as MeScopeUsagePayload, o as GetAppManifestResult, p as MintSignerSessionForExternalUserInput, q as ApproveDeviceLoginInput } from './types-BORaHW_x.js';
3
3
 
4
4
  /**
5
5
  * Normalize RFC 8628 user codes for comparison and resource URIs (uppercase, strip separators).
@@ -1,5 +1,5 @@
1
1
  import { SignerSessionToken } from './tokens.cjs';
2
- import { P as PmtHouseClientOptions, G as GetDiscoveryOptions, O as OidcDiscoveryDocument, a as ParsedDeviceApprovalRedirect, A as AppUserRecord, U as UpsertAppUserInput, M as MintUserAccessTokenInput, b as MintUserAccessTokenResponse, T as TokenExchangeResponse, D as DeviceApprovalInput, C as ClientCredentialsTokenResponse, c as MintUserSignerSessionTokenInput, d as UsageQueryInput, e as UsageApiResponse, S as SignedTicketIngestInput, f as SignedTicketIngestResult, g as SignerRoutingResponse, L as ListBillingProductsResult, h as PlanSyncResult, i as UsageBalanceResponse, j as UserAllowancesResponse, k as UserAllowanceGrantInput, l as GrantSource, m as UserSubscriptionResponse, n as MeScopeUsagePayload, o as GetAppManifestResult, p as MintSignerSessionForExternalUserInput, q as ApproveDeviceLoginInput } from './types-_R1AwEZp.cjs';
2
+ import { P as PmtHouseClientOptions, G as GetDiscoveryOptions, O as OidcDiscoveryDocument, a as ParsedDeviceApprovalRedirect, A as AppUserRecord, U as UpsertAppUserInput, M as MintUserAccessTokenInput, b as MintUserAccessTokenResponse, T as TokenExchangeResponse, D as DeviceApprovalInput, C as ClientCredentialsTokenResponse, c as MintUserSignerSessionTokenInput, d as UsageQueryInput, e as UsageApiResponse, S as SignedTicketIngestInput, f as SignedTicketIngestResult, g as SignerRoutingResponse, L as ListBillingProductsResult, h as PlanSyncResult, i as UsageBalanceResponse, j as UserAllowancesResponse, k as UserAllowanceGrantInput, l as GrantSource, m as UserSubscriptionResponse, n as MeScopeUsagePayload, o as GetAppManifestResult, p as MintSignerSessionForExternalUserInput, q as ApproveDeviceLoginInput } from './types-BORaHW_x.cjs';
3
3
 
4
4
  /**
5
5
  * Normalize RFC 8628 user codes for comparison and resource URIs (uppercase, strip separators).
package/dist/config.cjs CHANGED
@@ -30,19 +30,6 @@ function stripOidcPathSuffix(issuerUrl) {
30
30
  base = stripSuffixIgnoreCase(base, "/oidc");
31
31
  return stripTrailingSlashes(base);
32
32
  }
33
- function isSafePathSegment(value) {
34
- if (typeof value !== "string" || value.length === 0 || value.length > 128) {
35
- return false;
36
- }
37
- for (let i = 0; i < value.length; i++) {
38
- const c = value.codePointAt(i) ?? 0;
39
- const ok = c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95 || c === 45;
40
- if (!ok) {
41
- return false;
42
- }
43
- }
44
- return true;
45
- }
46
33
  function parseHttpOrigin(raw, fallback) {
47
34
  const trimmed = (raw ?? fallback).trim();
48
35
  let parsed;
@@ -56,12 +43,6 @@ function parseHttpOrigin(raw, fallback) {
56
43
  }
57
44
  return parsed.origin;
58
45
  }
59
- function buildGatewaySessionDeleteUrl(origin, sessionId) {
60
- if (!isSafePathSegment(sessionId)) {
61
- throw new TypeError("Invalid gateway session id");
62
- }
63
- return new URL(`/api/gateway/sessions/${encodeURIComponent(sessionId)}`, origin);
64
- }
65
46
 
66
47
  // src/config.ts
67
48
  var PYMTHOUSE_NOT_CONFIGURED_MESSAGE = "PymtHouse is not configured. Set PYMTHOUSE_ISSUER_URL, PYMTHOUSE_PUBLIC_CLIENT_ID, PYMTHOUSE_M2M_CLIENT_ID, and PYMTHOUSE_M2M_CLIENT_SECRET, then restart.";
@@ -109,13 +90,11 @@ function getPymthouseIssuerOrigin(issuerUrl) {
109
90
  }
110
91
 
111
92
  exports.PYMTHOUSE_NOT_CONFIGURED_MESSAGE = PYMTHOUSE_NOT_CONFIGURED_MESSAGE;
112
- exports.buildGatewaySessionDeleteUrl = buildGatewaySessionDeleteUrl;
113
93
  exports.getBuilderApiV1BaseFromIssuerUrl = getBuilderApiV1BaseFromIssuerUrl;
114
94
  exports.getPymthouseIssuerOrigin = getPymthouseIssuerOrigin;
115
95
  exports.getPymthouseIssuerUrlFromEnv = getPymthouseIssuerUrlFromEnv;
116
96
  exports.getPymthousePublicClientIdFromEnv = getPymthousePublicClientIdFromEnv;
117
97
  exports.isPymthouseConfigured = isPymthouseConfigured;
118
- exports.isSafePathSegment = isSafePathSegment;
119
98
  exports.parseHttpOrigin = parseHttpOrigin;
120
99
  exports.readPymthouseEnv = readPymthouseEnv;
121
100
  //# sourceMappingURL=config.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/string-utils.ts","../src/config.ts"],"names":[],"mappings":"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","file":"config.cjs","sourcesContent":["/** Removes trailing `/` without regex (linear time). */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {\n end--;\n }\n return value.slice(0, end);\n}\n\nfunction endsWithIgnoreCase(value: string, suffix: string): boolean {\n if (suffix.length > value.length) {\n return false;\n }\n const start = value.length - suffix.length;\n for (let i = 0; i < suffix.length; i++) {\n const a = value.codePointAt(start + i) ?? 0;\n const b = suffix.codePointAt(i) ?? 0;\n if (a !== b && (a | 32) !== (b | 32)) {\n return false;\n }\n }\n return true;\n}\n\nfunction stripSuffixIgnoreCase(value: string, suffix: string): string {\n return endsWithIgnoreCase(value, suffix)\n ? value.slice(0, value.length - suffix.length)\n : value;\n}\n\n/** Issuer URL (`…/oidc`) → Builder API base (`…/api/v1`). Linear-time; no regex. */\nexport function stripOidcPathSuffix(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Issuer URL (`…/api/v1/oidc`) → host origin for signer/API-key routes. Linear-time; no regex. */\nexport function stripIssuerOriginFromOidcUrl(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/api/v1/oidc\");\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Validate gateway session ids before embedding in request URLs. */\nexport function isSafePathSegment(value: unknown): value is string {\n if (typeof value !== \"string\" || value.length === 0 || value.length > 128) {\n return false;\n }\n for (let i = 0; i < value.length; i++) {\n const c = value.codePointAt(i) ?? 0;\n const ok =\n (c >= 48 && c <= 57) ||\n (c >= 65 && c <= 90) ||\n (c >= 97 && c <= 122) ||\n c === 95 ||\n c === 45;\n if (!ok) {\n return false;\n }\n }\n return true;\n}\n\n/** Parse and validate an http(s) facade origin (no path). */\nexport function parseHttpOrigin(raw: string | undefined, fallback: string): string {\n const trimmed = (raw ?? fallback).trim();\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new TypeError(\"Origin must be a valid http(s) URL\");\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new TypeError(\"Origin must use http or https\");\n }\n return parsed.origin;\n}\n\n/** Build a validated DELETE URL for `/api/gateway/sessions/:id`. */\nexport function buildGatewaySessionDeleteUrl(origin: string, sessionId: string): URL {\n if (!isSafePathSegment(sessionId)) {\n throw new TypeError(\"Invalid gateway session id\");\n }\n return new URL(`/api/gateway/sessions/${encodeURIComponent(sessionId)}`, origin);\n}\n","import {\n buildGatewaySessionDeleteUrl,\n isSafePathSegment,\n parseHttpOrigin,\n stripOidcPathSuffix,\n stripTrailingSlashes,\n} from \"./string-utils.js\";\n\nexport { buildGatewaySessionDeleteUrl, isSafePathSegment, parseHttpOrigin };\n\n/** Operator hint when Builder / Usage cannot run. */\nexport const PYMTHOUSE_NOT_CONFIGURED_MESSAGE =\n \"PymtHouse is not configured. Set PYMTHOUSE_ISSUER_URL, PYMTHOUSE_PUBLIC_CLIENT_ID, PYMTHOUSE_M2M_CLIENT_ID, and PYMTHOUSE_M2M_CLIENT_SECRET, then restart.\";\n\nexport interface PymthouseEnvConfig {\n issuerUrl: string;\n publicClientId: string;\n m2mClientId: string;\n m2mClientSecret: string;\n}\n\nfunction trimEnv(name: string): string | null {\n const value = process.env[name];\n if (!value) return null;\n const trimmed = value.trim();\n return trimmed || null;\n}\n\n/** Read `PYMTHOUSE_*` env vars without throwing. Returns null when incomplete. */\nexport function readPymthouseEnv(): PymthouseEnvConfig | null {\n const issuerUrl = trimEnv(\"PYMTHOUSE_ISSUER_URL\");\n const publicClientId = trimEnv(\"PYMTHOUSE_PUBLIC_CLIENT_ID\");\n const m2mClientId = trimEnv(\"PYMTHOUSE_M2M_CLIENT_ID\");\n const m2mClientSecret = trimEnv(\"PYMTHOUSE_M2M_CLIENT_SECRET\");\n if (!issuerUrl || !publicClientId || !m2mClientId || !m2mClientSecret) {\n return null;\n }\n return {\n issuerUrl: stripTrailingSlashes(issuerUrl),\n publicClientId,\n m2mClientId,\n m2mClientSecret,\n };\n}\n\n/** Read `PYMTHOUSE_ISSUER_URL` without requiring full M2M configuration. */\nexport function getPymthouseIssuerUrlFromEnv(): string | null {\n const raw = trimEnv(\"PYMTHOUSE_ISSUER_URL\");\n if (!raw) return null;\n try {\n return stripTrailingSlashes(new URL(raw).href);\n } catch {\n return null;\n }\n}\n\n/** Read `PYMTHOUSE_PUBLIC_CLIENT_ID` without requiring full M2M configuration. */\nexport function getPymthousePublicClientIdFromEnv(): string | null {\n return trimEnv(\"PYMTHOUSE_PUBLIC_CLIENT_ID\");\n}\n\n/** True when all vars required by `createPmtHouseClientFromEnv` are present. */\nexport function isPymthouseConfigured(): boolean {\n return readPymthouseEnv() !== null;\n}\n\n/** Resolve Builder API base (`…/api/v1`) from issuer URL (`…/api/v1/oidc`). */\nexport function getBuilderApiV1BaseFromIssuerUrl(issuerUrl: string): string {\n return stripOidcPathSuffix(issuerUrl);\n}\n\n/** Origin of the OIDC issuer host (e.g. `https://pymthouse.com`). */\nexport function getPymthouseIssuerOrigin(issuerUrl: string): string {\n return new URL(stripTrailingSlashes(issuerUrl.trim())).origin;\n}\n"]}
1
+ {"version":3,"sources":["../src/string-utils.ts","../src/config.ts"],"names":[],"mappings":"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","file":"config.cjs","sourcesContent":["/** Removes trailing `/` without regex (linear time). */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {\n end--;\n }\n return value.slice(0, end);\n}\n\nfunction endsWithIgnoreCase(value: string, suffix: string): boolean {\n if (suffix.length > value.length) {\n return false;\n }\n const start = value.length - suffix.length;\n for (let i = 0; i < suffix.length; i++) {\n const a = value.codePointAt(start + i) ?? 0;\n const b = suffix.codePointAt(i) ?? 0;\n if (a !== b && (a | 32) !== (b | 32)) {\n return false;\n }\n }\n return true;\n}\n\nfunction stripSuffixIgnoreCase(value: string, suffix: string): string {\n return endsWithIgnoreCase(value, suffix)\n ? value.slice(0, value.length - suffix.length)\n : value;\n}\n\n/** Issuer URL (`…/oidc`) → Builder API base (`…/api/v1`). Linear-time; no regex. */\nexport function stripOidcPathSuffix(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Issuer URL (`…/api/v1/oidc`) → host origin for signer/API-key routes. Linear-time; no regex. */\nexport function stripIssuerOriginFromOidcUrl(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/api/v1/oidc\");\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Parse and validate an http(s) facade origin (no path). */\nexport function parseHttpOrigin(raw: string | undefined, fallback: string): string {\n const trimmed = (raw ?? fallback).trim();\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new TypeError(\"Origin must be a valid http(s) URL\");\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new TypeError(\"Origin must use http or https\");\n }\n return parsed.origin;\n}\n\n","import {\n stripOidcPathSuffix,\n stripTrailingSlashes,\n} from \"./string-utils.js\";\n\nexport { parseHttpOrigin } from \"./string-utils.js\";\n\n/** Operator hint when Builder / Usage cannot run. */\nexport const PYMTHOUSE_NOT_CONFIGURED_MESSAGE =\n \"PymtHouse is not configured. Set PYMTHOUSE_ISSUER_URL, PYMTHOUSE_PUBLIC_CLIENT_ID, PYMTHOUSE_M2M_CLIENT_ID, and PYMTHOUSE_M2M_CLIENT_SECRET, then restart.\";\n\nexport interface PymthouseEnvConfig {\n issuerUrl: string;\n publicClientId: string;\n m2mClientId: string;\n m2mClientSecret: string;\n}\n\nfunction trimEnv(name: string): string | null {\n const value = process.env[name];\n if (!value) return null;\n const trimmed = value.trim();\n return trimmed || null;\n}\n\n/** Read `PYMTHOUSE_*` env vars without throwing. Returns null when incomplete. */\nexport function readPymthouseEnv(): PymthouseEnvConfig | null {\n const issuerUrl = trimEnv(\"PYMTHOUSE_ISSUER_URL\");\n const publicClientId = trimEnv(\"PYMTHOUSE_PUBLIC_CLIENT_ID\");\n const m2mClientId = trimEnv(\"PYMTHOUSE_M2M_CLIENT_ID\");\n const m2mClientSecret = trimEnv(\"PYMTHOUSE_M2M_CLIENT_SECRET\");\n if (!issuerUrl || !publicClientId || !m2mClientId || !m2mClientSecret) {\n return null;\n }\n return {\n issuerUrl: stripTrailingSlashes(issuerUrl),\n publicClientId,\n m2mClientId,\n m2mClientSecret,\n };\n}\n\n/** Read `PYMTHOUSE_ISSUER_URL` without requiring full M2M configuration. */\nexport function getPymthouseIssuerUrlFromEnv(): string | null {\n const raw = trimEnv(\"PYMTHOUSE_ISSUER_URL\");\n if (!raw) return null;\n try {\n return stripTrailingSlashes(new URL(raw).href);\n } catch {\n return null;\n }\n}\n\n/** Read `PYMTHOUSE_PUBLIC_CLIENT_ID` without requiring full M2M configuration. */\nexport function getPymthousePublicClientIdFromEnv(): string | null {\n return trimEnv(\"PYMTHOUSE_PUBLIC_CLIENT_ID\");\n}\n\n/** True when all vars required by `createPmtHouseClientFromEnv` are present. */\nexport function isPymthouseConfigured(): boolean {\n return readPymthouseEnv() !== null;\n}\n\n/** Resolve Builder API base (`…/api/v1`) from issuer URL (`…/api/v1/oidc`). */\nexport function getBuilderApiV1BaseFromIssuerUrl(issuerUrl: string): string {\n return stripOidcPathSuffix(issuerUrl);\n}\n\n/** Origin of the OIDC issuer host (e.g. `https://pymthouse.com`). */\nexport function getPymthouseIssuerOrigin(issuerUrl: string): string {\n return new URL(stripTrailingSlashes(issuerUrl.trim())).origin;\n}\n"]}
package/dist/config.d.cts CHANGED
@@ -1,9 +1,5 @@
1
- /** Validate gateway session ids before embedding in request URLs. */
2
- declare function isSafePathSegment(value: unknown): value is string;
3
1
  /** Parse and validate an http(s) facade origin (no path). */
4
2
  declare function parseHttpOrigin(raw: string | undefined, fallback: string): string;
5
- /** Build a validated DELETE URL for `/api/gateway/sessions/:id`. */
6
- declare function buildGatewaySessionDeleteUrl(origin: string, sessionId: string): URL;
7
3
 
8
4
  /** Operator hint when Builder / Usage cannot run. */
9
5
  declare const PYMTHOUSE_NOT_CONFIGURED_MESSAGE = "PymtHouse is not configured. Set PYMTHOUSE_ISSUER_URL, PYMTHOUSE_PUBLIC_CLIENT_ID, PYMTHOUSE_M2M_CLIENT_ID, and PYMTHOUSE_M2M_CLIENT_SECRET, then restart.";
@@ -26,4 +22,4 @@ declare function getBuilderApiV1BaseFromIssuerUrl(issuerUrl: string): string;
26
22
  /** Origin of the OIDC issuer host (e.g. `https://pymthouse.com`). */
27
23
  declare function getPymthouseIssuerOrigin(issuerUrl: string): string;
28
24
 
29
- export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, type PymthouseEnvConfig, buildGatewaySessionDeleteUrl, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, isSafePathSegment, parseHttpOrigin, readPymthouseEnv };
25
+ export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, type PymthouseEnvConfig, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, parseHttpOrigin, readPymthouseEnv };
package/dist/config.d.ts CHANGED
@@ -1,9 +1,5 @@
1
- /** Validate gateway session ids before embedding in request URLs. */
2
- declare function isSafePathSegment(value: unknown): value is string;
3
1
  /** Parse and validate an http(s) facade origin (no path). */
4
2
  declare function parseHttpOrigin(raw: string | undefined, fallback: string): string;
5
- /** Build a validated DELETE URL for `/api/gateway/sessions/:id`. */
6
- declare function buildGatewaySessionDeleteUrl(origin: string, sessionId: string): URL;
7
3
 
8
4
  /** Operator hint when Builder / Usage cannot run. */
9
5
  declare const PYMTHOUSE_NOT_CONFIGURED_MESSAGE = "PymtHouse is not configured. Set PYMTHOUSE_ISSUER_URL, PYMTHOUSE_PUBLIC_CLIENT_ID, PYMTHOUSE_M2M_CLIENT_ID, and PYMTHOUSE_M2M_CLIENT_SECRET, then restart.";
@@ -26,4 +22,4 @@ declare function getBuilderApiV1BaseFromIssuerUrl(issuerUrl: string): string;
26
22
  /** Origin of the OIDC issuer host (e.g. `https://pymthouse.com`). */
27
23
  declare function getPymthouseIssuerOrigin(issuerUrl: string): string;
28
24
 
29
- export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, type PymthouseEnvConfig, buildGatewaySessionDeleteUrl, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, isSafePathSegment, parseHttpOrigin, readPymthouseEnv };
25
+ export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, type PymthouseEnvConfig, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, parseHttpOrigin, readPymthouseEnv };
package/dist/config.js CHANGED
@@ -28,19 +28,6 @@ function stripOidcPathSuffix(issuerUrl) {
28
28
  base = stripSuffixIgnoreCase(base, "/oidc");
29
29
  return stripTrailingSlashes(base);
30
30
  }
31
- function isSafePathSegment(value) {
32
- if (typeof value !== "string" || value.length === 0 || value.length > 128) {
33
- return false;
34
- }
35
- for (let i = 0; i < value.length; i++) {
36
- const c = value.codePointAt(i) ?? 0;
37
- const ok = c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95 || c === 45;
38
- if (!ok) {
39
- return false;
40
- }
41
- }
42
- return true;
43
- }
44
31
  function parseHttpOrigin(raw, fallback) {
45
32
  const trimmed = (raw ?? fallback).trim();
46
33
  let parsed;
@@ -54,12 +41,6 @@ function parseHttpOrigin(raw, fallback) {
54
41
  }
55
42
  return parsed.origin;
56
43
  }
57
- function buildGatewaySessionDeleteUrl(origin, sessionId) {
58
- if (!isSafePathSegment(sessionId)) {
59
- throw new TypeError("Invalid gateway session id");
60
- }
61
- return new URL(`/api/gateway/sessions/${encodeURIComponent(sessionId)}`, origin);
62
- }
63
44
 
64
45
  // src/config.ts
65
46
  var PYMTHOUSE_NOT_CONFIGURED_MESSAGE = "PymtHouse is not configured. Set PYMTHOUSE_ISSUER_URL, PYMTHOUSE_PUBLIC_CLIENT_ID, PYMTHOUSE_M2M_CLIENT_ID, and PYMTHOUSE_M2M_CLIENT_SECRET, then restart.";
@@ -106,6 +87,6 @@ function getPymthouseIssuerOrigin(issuerUrl) {
106
87
  return new URL(stripTrailingSlashes(issuerUrl.trim())).origin;
107
88
  }
108
89
 
109
- export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, buildGatewaySessionDeleteUrl, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, isSafePathSegment, parseHttpOrigin, readPymthouseEnv };
90
+ export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, parseHttpOrigin, readPymthouseEnv };
110
91
  //# sourceMappingURL=config.js.map
111
92
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/string-utils.ts","../src/config.ts"],"names":[],"mappings":"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","file":"config.js","sourcesContent":["/** Removes trailing `/` without regex (linear time). */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {\n end--;\n }\n return value.slice(0, end);\n}\n\nfunction endsWithIgnoreCase(value: string, suffix: string): boolean {\n if (suffix.length > value.length) {\n return false;\n }\n const start = value.length - suffix.length;\n for (let i = 0; i < suffix.length; i++) {\n const a = value.codePointAt(start + i) ?? 0;\n const b = suffix.codePointAt(i) ?? 0;\n if (a !== b && (a | 32) !== (b | 32)) {\n return false;\n }\n }\n return true;\n}\n\nfunction stripSuffixIgnoreCase(value: string, suffix: string): string {\n return endsWithIgnoreCase(value, suffix)\n ? value.slice(0, value.length - suffix.length)\n : value;\n}\n\n/** Issuer URL (`…/oidc`) → Builder API base (`…/api/v1`). Linear-time; no regex. */\nexport function stripOidcPathSuffix(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Issuer URL (`…/api/v1/oidc`) → host origin for signer/API-key routes. Linear-time; no regex. */\nexport function stripIssuerOriginFromOidcUrl(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/api/v1/oidc\");\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Validate gateway session ids before embedding in request URLs. */\nexport function isSafePathSegment(value: unknown): value is string {\n if (typeof value !== \"string\" || value.length === 0 || value.length > 128) {\n return false;\n }\n for (let i = 0; i < value.length; i++) {\n const c = value.codePointAt(i) ?? 0;\n const ok =\n (c >= 48 && c <= 57) ||\n (c >= 65 && c <= 90) ||\n (c >= 97 && c <= 122) ||\n c === 95 ||\n c === 45;\n if (!ok) {\n return false;\n }\n }\n return true;\n}\n\n/** Parse and validate an http(s) facade origin (no path). */\nexport function parseHttpOrigin(raw: string | undefined, fallback: string): string {\n const trimmed = (raw ?? fallback).trim();\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new TypeError(\"Origin must be a valid http(s) URL\");\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new TypeError(\"Origin must use http or https\");\n }\n return parsed.origin;\n}\n\n/** Build a validated DELETE URL for `/api/gateway/sessions/:id`. */\nexport function buildGatewaySessionDeleteUrl(origin: string, sessionId: string): URL {\n if (!isSafePathSegment(sessionId)) {\n throw new TypeError(\"Invalid gateway session id\");\n }\n return new URL(`/api/gateway/sessions/${encodeURIComponent(sessionId)}`, origin);\n}\n","import {\n buildGatewaySessionDeleteUrl,\n isSafePathSegment,\n parseHttpOrigin,\n stripOidcPathSuffix,\n stripTrailingSlashes,\n} from \"./string-utils.js\";\n\nexport { buildGatewaySessionDeleteUrl, isSafePathSegment, parseHttpOrigin };\n\n/** Operator hint when Builder / Usage cannot run. */\nexport const PYMTHOUSE_NOT_CONFIGURED_MESSAGE =\n \"PymtHouse is not configured. Set PYMTHOUSE_ISSUER_URL, PYMTHOUSE_PUBLIC_CLIENT_ID, PYMTHOUSE_M2M_CLIENT_ID, and PYMTHOUSE_M2M_CLIENT_SECRET, then restart.\";\n\nexport interface PymthouseEnvConfig {\n issuerUrl: string;\n publicClientId: string;\n m2mClientId: string;\n m2mClientSecret: string;\n}\n\nfunction trimEnv(name: string): string | null {\n const value = process.env[name];\n if (!value) return null;\n const trimmed = value.trim();\n return trimmed || null;\n}\n\n/** Read `PYMTHOUSE_*` env vars without throwing. Returns null when incomplete. */\nexport function readPymthouseEnv(): PymthouseEnvConfig | null {\n const issuerUrl = trimEnv(\"PYMTHOUSE_ISSUER_URL\");\n const publicClientId = trimEnv(\"PYMTHOUSE_PUBLIC_CLIENT_ID\");\n const m2mClientId = trimEnv(\"PYMTHOUSE_M2M_CLIENT_ID\");\n const m2mClientSecret = trimEnv(\"PYMTHOUSE_M2M_CLIENT_SECRET\");\n if (!issuerUrl || !publicClientId || !m2mClientId || !m2mClientSecret) {\n return null;\n }\n return {\n issuerUrl: stripTrailingSlashes(issuerUrl),\n publicClientId,\n m2mClientId,\n m2mClientSecret,\n };\n}\n\n/** Read `PYMTHOUSE_ISSUER_URL` without requiring full M2M configuration. */\nexport function getPymthouseIssuerUrlFromEnv(): string | null {\n const raw = trimEnv(\"PYMTHOUSE_ISSUER_URL\");\n if (!raw) return null;\n try {\n return stripTrailingSlashes(new URL(raw).href);\n } catch {\n return null;\n }\n}\n\n/** Read `PYMTHOUSE_PUBLIC_CLIENT_ID` without requiring full M2M configuration. */\nexport function getPymthousePublicClientIdFromEnv(): string | null {\n return trimEnv(\"PYMTHOUSE_PUBLIC_CLIENT_ID\");\n}\n\n/** True when all vars required by `createPmtHouseClientFromEnv` are present. */\nexport function isPymthouseConfigured(): boolean {\n return readPymthouseEnv() !== null;\n}\n\n/** Resolve Builder API base (`…/api/v1`) from issuer URL (`…/api/v1/oidc`). */\nexport function getBuilderApiV1BaseFromIssuerUrl(issuerUrl: string): string {\n return stripOidcPathSuffix(issuerUrl);\n}\n\n/** Origin of the OIDC issuer host (e.g. `https://pymthouse.com`). */\nexport function getPymthouseIssuerOrigin(issuerUrl: string): string {\n return new URL(stripTrailingSlashes(issuerUrl.trim())).origin;\n}\n"]}
1
+ {"version":3,"sources":["../src/string-utils.ts","../src/config.ts"],"names":[],"mappings":";AACO,SAAS,qBAAqB,KAAA,EAAuB;AAC1D,EAAA,IAAI,MAAM,KAAA,CAAM,MAAA;AAChB,EAAA,OAAO,GAAA,GAAM,MAAM,KAAA,CAAM,WAAA,CAAY,MAAM,CAAC,CAAA,IAAK,OAAO,EAAA,EAAI;AAC1D,IAAA,GAAA,EAAA;AAAA,EACF;AACA,EAAA,OAAO,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA;AAC3B;AAEA,SAAS,kBAAA,CAAmB,OAAe,MAAA,EAAyB;AAClE,EAAA,IAAI,MAAA,CAAO,MAAA,GAAS,KAAA,CAAM,MAAA,EAAQ;AAChC,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,MAAA,GAAS,MAAA,CAAO,MAAA;AACpC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,CAAO,QAAQ,CAAA,EAAA,EAAK;AACtC,IAAA,MAAM,CAAA,GAAI,KAAA,CAAM,WAAA,CAAY,KAAA,GAAQ,CAAC,CAAA,IAAK,CAAA;AAC1C,IAAA,MAAM,CAAA,GAAI,MAAA,CAAO,WAAA,CAAY,CAAC,CAAA,IAAK,CAAA;AACnC,IAAA,IAAI,CAAA,KAAM,CAAA,IAAA,CAAM,CAAA,GAAI,EAAA,OAAS,IAAI,EAAA,CAAA,EAAK;AACpC,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,qBAAA,CAAsB,OAAe,MAAA,EAAwB;AACpE,EAAA,OAAO,kBAAA,CAAmB,KAAA,EAAO,MAAM,CAAA,GACnC,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,KAAA,CAAM,MAAA,GAAS,MAAA,CAAO,MAAM,CAAA,GAC3C,KAAA;AACN;AAGO,SAAS,oBAAoB,SAAA,EAA2B;AAC7D,EAAA,IAAI,IAAA,GAAO,oBAAA,CAAqB,SAAA,CAAU,IAAA,EAAM,CAAA;AAChD,EAAA,IAAA,GAAO,qBAAA,CAAsB,MAAM,OAAO,CAAA;AAC1C,EAAA,OAAO,qBAAqB,IAAI,CAAA;AAClC;AAWO,SAAS,eAAA,CAAgB,KAAyB,QAAA,EAA0B;AACjF,EAAA,MAAM,OAAA,GAAA,CAAW,GAAA,IAAO,QAAA,EAAU,IAAA,EAAK;AACvC,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAI,IAAI,OAAO,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,UAAU,oCAAoC,CAAA;AAAA,EAC1D;AACA,EAAA,IAAI,MAAA,CAAO,QAAA,KAAa,OAAA,IAAW,MAAA,CAAO,aAAa,QAAA,EAAU;AAC/D,IAAA,MAAM,IAAI,UAAU,+BAA+B,CAAA;AAAA,EACrD;AACA,EAAA,OAAO,MAAA,CAAO,MAAA;AAChB;;;AClDO,IAAM,gCAAA,GACX;AASF,SAAS,QAAQ,IAAA,EAA6B;AAC5C,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA;AAC9B,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,OAAA,GAAU,MAAM,IAAA,EAAK;AAC3B,EAAA,OAAO,OAAA,IAAW,IAAA;AACpB;AAGO,SAAS,gBAAA,GAA8C;AAC5D,EAAA,MAAM,SAAA,GAAY,QAAQ,sBAAsB,CAAA;AAChD,EAAA,MAAM,cAAA,GAAiB,QAAQ,4BAA4B,CAAA;AAC3D,EAAA,MAAM,WAAA,GAAc,QAAQ,yBAAyB,CAAA;AACrD,EAAA,MAAM,eAAA,GAAkB,QAAQ,6BAA6B,CAAA;AAC7D,EAAA,IAAI,CAAC,SAAA,IAAa,CAAC,kBAAkB,CAAC,WAAA,IAAe,CAAC,eAAA,EAAiB;AACrE,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,qBAAqB,SAAS,CAAA;AAAA,IACzC,cAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF;AACF;AAGO,SAAS,4BAAA,GAA8C;AAC5D,EAAA,MAAM,GAAA,GAAM,QAAQ,sBAAsB,CAAA;AAC1C,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,IAAI;AACF,IAAA,OAAO,oBAAA,CAAqB,IAAI,GAAA,CAAI,GAAG,EAAE,IAAI,CAAA;AAAA,EAC/C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAGO,SAAS,iCAAA,GAAmD;AACjE,EAAA,OAAO,QAAQ,4BAA4B,CAAA;AAC7C;AAGO,SAAS,qBAAA,GAAiC;AAC/C,EAAA,OAAO,kBAAiB,KAAM,IAAA;AAChC;AAGO,SAAS,iCAAiC,SAAA,EAA2B;AAC1E,EAAA,OAAO,oBAAoB,SAAS,CAAA;AACtC;AAGO,SAAS,yBAAyB,SAAA,EAA2B;AAClE,EAAA,OAAO,IAAI,GAAA,CAAI,oBAAA,CAAqB,UAAU,IAAA,EAAM,CAAC,CAAA,CAAE,MAAA;AACzD","file":"config.js","sourcesContent":["/** Removes trailing `/` without regex (linear time). */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {\n end--;\n }\n return value.slice(0, end);\n}\n\nfunction endsWithIgnoreCase(value: string, suffix: string): boolean {\n if (suffix.length > value.length) {\n return false;\n }\n const start = value.length - suffix.length;\n for (let i = 0; i < suffix.length; i++) {\n const a = value.codePointAt(start + i) ?? 0;\n const b = suffix.codePointAt(i) ?? 0;\n if (a !== b && (a | 32) !== (b | 32)) {\n return false;\n }\n }\n return true;\n}\n\nfunction stripSuffixIgnoreCase(value: string, suffix: string): string {\n return endsWithIgnoreCase(value, suffix)\n ? value.slice(0, value.length - suffix.length)\n : value;\n}\n\n/** Issuer URL (`…/oidc`) → Builder API base (`…/api/v1`). Linear-time; no regex. */\nexport function stripOidcPathSuffix(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Issuer URL (`…/api/v1/oidc`) → host origin for signer/API-key routes. Linear-time; no regex. */\nexport function stripIssuerOriginFromOidcUrl(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/api/v1/oidc\");\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Parse and validate an http(s) facade origin (no path). */\nexport function parseHttpOrigin(raw: string | undefined, fallback: string): string {\n const trimmed = (raw ?? fallback).trim();\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new TypeError(\"Origin must be a valid http(s) URL\");\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new TypeError(\"Origin must use http or https\");\n }\n return parsed.origin;\n}\n\n","import {\n stripOidcPathSuffix,\n stripTrailingSlashes,\n} from \"./string-utils.js\";\n\nexport { parseHttpOrigin } from \"./string-utils.js\";\n\n/** Operator hint when Builder / Usage cannot run. */\nexport const PYMTHOUSE_NOT_CONFIGURED_MESSAGE =\n \"PymtHouse is not configured. Set PYMTHOUSE_ISSUER_URL, PYMTHOUSE_PUBLIC_CLIENT_ID, PYMTHOUSE_M2M_CLIENT_ID, and PYMTHOUSE_M2M_CLIENT_SECRET, then restart.\";\n\nexport interface PymthouseEnvConfig {\n issuerUrl: string;\n publicClientId: string;\n m2mClientId: string;\n m2mClientSecret: string;\n}\n\nfunction trimEnv(name: string): string | null {\n const value = process.env[name];\n if (!value) return null;\n const trimmed = value.trim();\n return trimmed || null;\n}\n\n/** Read `PYMTHOUSE_*` env vars without throwing. Returns null when incomplete. */\nexport function readPymthouseEnv(): PymthouseEnvConfig | null {\n const issuerUrl = trimEnv(\"PYMTHOUSE_ISSUER_URL\");\n const publicClientId = trimEnv(\"PYMTHOUSE_PUBLIC_CLIENT_ID\");\n const m2mClientId = trimEnv(\"PYMTHOUSE_M2M_CLIENT_ID\");\n const m2mClientSecret = trimEnv(\"PYMTHOUSE_M2M_CLIENT_SECRET\");\n if (!issuerUrl || !publicClientId || !m2mClientId || !m2mClientSecret) {\n return null;\n }\n return {\n issuerUrl: stripTrailingSlashes(issuerUrl),\n publicClientId,\n m2mClientId,\n m2mClientSecret,\n };\n}\n\n/** Read `PYMTHOUSE_ISSUER_URL` without requiring full M2M configuration. */\nexport function getPymthouseIssuerUrlFromEnv(): string | null {\n const raw = trimEnv(\"PYMTHOUSE_ISSUER_URL\");\n if (!raw) return null;\n try {\n return stripTrailingSlashes(new URL(raw).href);\n } catch {\n return null;\n }\n}\n\n/** Read `PYMTHOUSE_PUBLIC_CLIENT_ID` without requiring full M2M configuration. */\nexport function getPymthousePublicClientIdFromEnv(): string | null {\n return trimEnv(\"PYMTHOUSE_PUBLIC_CLIENT_ID\");\n}\n\n/** True when all vars required by `createPmtHouseClientFromEnv` are present. */\nexport function isPymthouseConfigured(): boolean {\n return readPymthouseEnv() !== null;\n}\n\n/** Resolve Builder API base (`…/api/v1`) from issuer URL (`…/api/v1/oidc`). */\nexport function getBuilderApiV1BaseFromIssuerUrl(issuerUrl: string): string {\n return stripOidcPathSuffix(issuerUrl);\n}\n\n/** Origin of the OIDC issuer host (e.g. `https://pymthouse.com`). */\nexport function getPymthouseIssuerOrigin(issuerUrl: string): string {\n return new URL(stripTrailingSlashes(issuerUrl.trim())).origin;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/string-utils.ts","../src/device-initiate.ts"],"names":[],"mappings":"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","file":"device-initiate.cjs","sourcesContent":["/** Removes trailing `/` without regex (linear time). */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {\n end--;\n }\n return value.slice(0, end);\n}\n\nfunction endsWithIgnoreCase(value: string, suffix: string): boolean {\n if (suffix.length > value.length) {\n return false;\n }\n const start = value.length - suffix.length;\n for (let i = 0; i < suffix.length; i++) {\n const a = value.codePointAt(start + i) ?? 0;\n const b = suffix.codePointAt(i) ?? 0;\n if (a !== b && (a | 32) !== (b | 32)) {\n return false;\n }\n }\n return true;\n}\n\nfunction stripSuffixIgnoreCase(value: string, suffix: string): string {\n return endsWithIgnoreCase(value, suffix)\n ? value.slice(0, value.length - suffix.length)\n : value;\n}\n\n/** Issuer URL (`…/oidc`) → Builder API base (`…/api/v1`). Linear-time; no regex. */\nexport function stripOidcPathSuffix(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Issuer URL (`…/api/v1/oidc`) → host origin for signer/API-key routes. Linear-time; no regex. */\nexport function stripIssuerOriginFromOidcUrl(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/api/v1/oidc\");\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Validate gateway session ids before embedding in request URLs. */\nexport function isSafePathSegment(value: unknown): value is string {\n if (typeof value !== \"string\" || value.length === 0 || value.length > 128) {\n return false;\n }\n for (let i = 0; i < value.length; i++) {\n const c = value.codePointAt(i) ?? 0;\n const ok =\n (c >= 48 && c <= 57) ||\n (c >= 65 && c <= 90) ||\n (c >= 97 && c <= 122) ||\n c === 95 ||\n c === 45;\n if (!ok) {\n return false;\n }\n }\n return true;\n}\n\n/** Parse and validate an http(s) facade origin (no path). */\nexport function parseHttpOrigin(raw: string | undefined, fallback: string): string {\n const trimmed = (raw ?? fallback).trim();\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new TypeError(\"Origin must be a valid http(s) URL\");\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new TypeError(\"Origin must use http or https\");\n }\n return parsed.origin;\n}\n\n/** Build a validated DELETE URL for `/api/gateway/sessions/:id`. */\nexport function buildGatewaySessionDeleteUrl(origin: string, sessionId: string): URL {\n if (!isSafePathSegment(sessionId)) {\n throw new TypeError(\"Invalid gateway session id\");\n }\n return new URL(`/api/gateway/sessions/${encodeURIComponent(sessionId)}`, origin);\n}\n","import { stripTrailingSlashes } from \"./string-utils.js\";\n\n/** RFC 8628 user codes: 4–16 chars, at least one alphanumeric (not all dashes). */\nexport const USER_CODE_RE = /^(?=.*[A-Z0-9])[A-Z0-9-]{4,16}$/;\n\nexport type ValidateDeviceInitiateResult =\n | { ok: true; returnUrl: string }\n | { ok: false; reason: string };\n\nexport type DeviceApprovalTuple =\n | { userCode: string; publicClientId: string }\n | { error: string };\n\nfunction normalizeIssuerUrl(iss: string): string {\n try {\n return stripTrailingSlashes(new URL(iss.trim()).href);\n } catch {\n return iss.trim();\n }\n}\n\n/**\n * Validate OP-issued `iss` + `target_link_uri` before storing a device approval cookie.\n */\nexport function validateDeviceInitiateLogin(input: {\n expectedIssuerUrl: string;\n iss: string;\n targetLinkUri: string;\n}): ValidateDeviceInitiateResult {\n const expectedIss = stripTrailingSlashes(input.expectedIssuerUrl.trim());\n let opOrigin: string;\n try {\n opOrigin = new URL(expectedIss).origin;\n } catch {\n return { ok: false, reason: \"server_not_configured\" };\n }\n\n if (normalizeIssuerUrl(input.iss) !== normalizeIssuerUrl(expectedIss)) {\n return { ok: false, reason: \"iss_mismatch\" };\n }\n\n let target: URL;\n try {\n target = new URL(input.targetLinkUri);\n } catch {\n return { ok: false, reason: \"bad_target_uri\" };\n }\n if (target.origin !== opOrigin) {\n return { ok: false, reason: \"target_origin_mismatch\" };\n }\n if (target.pathname !== \"/oidc/device\") {\n return { ok: false, reason: \"target_path_mismatch\" };\n }\n if (target.hash) {\n return { ok: false, reason: \"target_has_hash\" };\n }\n return { ok: true, returnUrl: target.href };\n}\n\n/**\n * Parse PymtHouse `/oidc/device` URL query for `user_code` + `client_id`.\n */\nexport function extractDeviceApprovalFromTargetLink(\n targetHref: string,\n opts?: { expectedIssuerUrl?: string; expectedPublicClientId?: string },\n): DeviceApprovalTuple {\n let target: URL;\n try {\n target = new URL(targetHref);\n } catch {\n return { error: \"bad_target_uri\" };\n }\n\n if (opts?.expectedIssuerUrl) {\n let opOrigin: string;\n try {\n opOrigin = new URL(stripTrailingSlashes(opts.expectedIssuerUrl.trim())).origin;\n } catch {\n return { error: \"target_origin_mismatch\" };\n }\n if (target.origin !== opOrigin) {\n return { error: \"target_origin_mismatch\" };\n }\n }\n\n if (target.pathname !== \"/oidc/device\") {\n return { error: \"target_path_mismatch\" };\n }\n\n const userCodeRaw = target.searchParams.get(\"user_code\")?.trim() ?? \"\";\n const clientIdRaw = target.searchParams.get(\"client_id\")?.trim() ?? \"\";\n if (!userCodeRaw || !USER_CODE_RE.test(userCodeRaw)) {\n return { error: \"invalid_user_code\" };\n }\n if (!clientIdRaw?.startsWith(\"app_\")) {\n return { error: \"invalid_client_id\" };\n }\n if (opts?.expectedPublicClientId && clientIdRaw !== opts.expectedPublicClientId) {\n return { error: \"client_id_mismatch\" };\n }\n return { userCode: userCodeRaw, publicClientId: clientIdRaw };\n}\n"]}
1
+ {"version":3,"sources":["../src/string-utils.ts","../src/device-initiate.ts"],"names":[],"mappings":";;;AACO,SAAS,qBAAqB,KAAA,EAAuB;AAC1D,EAAA,IAAI,MAAM,KAAA,CAAM,MAAA;AAChB,EAAA,OAAO,GAAA,GAAM,MAAM,KAAA,CAAM,WAAA,CAAY,MAAM,CAAC,CAAA,IAAK,OAAO,EAAA,EAAI;AAC1D,IAAA,GAAA,EAAA;AAAA,EACF;AACA,EAAA,OAAO,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA;AAC3B;;;ACJO,IAAM,YAAA,GAAe;AAU5B,SAAS,mBAAmB,GAAA,EAAqB;AAC/C,EAAA,IAAI;AACF,IAAA,OAAO,qBAAqB,IAAI,GAAA,CAAI,IAAI,IAAA,EAAM,EAAE,IAAI,CAAA;AAAA,EACtD,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAI,IAAA,EAAK;AAAA,EAClB;AACF;AAKO,SAAS,4BAA4B,KAAA,EAIX;AAC/B,EAAA,MAAM,WAAA,GAAc,oBAAA,CAAqB,KAAA,CAAM,iBAAA,CAAkB,MAAM,CAAA;AACvE,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,IAAI,GAAA,CAAI,WAAW,CAAA,CAAE,MAAA;AAAA,EAClC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,uBAAA,EAAwB;AAAA,EACtD;AAEA,EAAA,IAAI,mBAAmB,KAAA,CAAM,GAAG,CAAA,KAAM,kBAAA,CAAmB,WAAW,CAAA,EAAG;AACrE,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,cAAA,EAAe;AAAA,EAC7C;AAEA,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAI,GAAA,CAAI,KAAA,CAAM,aAAa,CAAA;AAAA,EACtC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,gBAAA,EAAiB;AAAA,EAC/C;AACA,EAAA,IAAI,MAAA,CAAO,WAAW,QAAA,EAAU;AAC9B,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,wBAAA,EAAyB;AAAA,EACvD;AACA,EAAA,IAAI,MAAA,CAAO,aAAa,cAAA,EAAgB;AACtC,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,sBAAA,EAAuB;AAAA,EACrD;AACA,EAAA,IAAI,OAAO,IAAA,EAAM;AACf,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,iBAAA,EAAkB;AAAA,EAChD;AACA,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,SAAA,EAAW,OAAO,IAAA,EAAK;AAC5C;AAKO,SAAS,mCAAA,CACd,YACA,IAAA,EACqB;AACrB,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAI,IAAI,UAAU,CAAA;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAE,OAAO,gBAAA,EAAiB;AAAA,EACnC;AAEA,EAAA,IAAI,MAAM,iBAAA,EAAmB;AAC3B,IAAA,IAAI,QAAA;AACJ,IAAA,IAAI;AACF,MAAA,QAAA,GAAW,IAAI,IAAI,oBAAA,CAAqB,IAAA,CAAK,kBAAkB,IAAA,EAAM,CAAC,CAAA,CAAE,MAAA;AAAA,IAC1E,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,EAAE,OAAO,wBAAA,EAAyB;AAAA,IAC3C;AACA,IAAA,IAAI,MAAA,CAAO,WAAW,QAAA,EAAU;AAC9B,MAAA,OAAO,EAAE,OAAO,wBAAA,EAAyB;AAAA,IAC3C;AAAA,EACF;AAEA,EAAA,IAAI,MAAA,CAAO,aAAa,cAAA,EAAgB;AACtC,IAAA,OAAO,EAAE,OAAO,sBAAA,EAAuB;AAAA,EACzC;AAEA,EAAA,MAAM,cAAc,MAAA,CAAO,YAAA,CAAa,IAAI,WAAW,CAAA,EAAG,MAAK,IAAK,EAAA;AACpE,EAAA,MAAM,cAAc,MAAA,CAAO,YAAA,CAAa,IAAI,WAAW,CAAA,EAAG,MAAK,IAAK,EAAA;AACpE,EAAA,IAAI,CAAC,WAAA,IAAe,CAAC,YAAA,CAAa,IAAA,CAAK,WAAW,CAAA,EAAG;AACnD,IAAA,OAAO,EAAE,OAAO,mBAAA,EAAoB;AAAA,EACtC;AACA,EAAA,IAAI,CAAC,WAAA,EAAa,UAAA,CAAW,MAAM,CAAA,EAAG;AACpC,IAAA,OAAO,EAAE,OAAO,mBAAA,EAAoB;AAAA,EACtC;AACA,EAAA,IAAI,IAAA,EAAM,sBAAA,IAA0B,WAAA,KAAgB,IAAA,CAAK,sBAAA,EAAwB;AAC/E,IAAA,OAAO,EAAE,OAAO,oBAAA,EAAqB;AAAA,EACvC;AACA,EAAA,OAAO,EAAE,QAAA,EAAU,WAAA,EAAa,cAAA,EAAgB,WAAA,EAAY;AAC9D","file":"device-initiate.cjs","sourcesContent":["/** Removes trailing `/` without regex (linear time). */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {\n end--;\n }\n return value.slice(0, end);\n}\n\nfunction endsWithIgnoreCase(value: string, suffix: string): boolean {\n if (suffix.length > value.length) {\n return false;\n }\n const start = value.length - suffix.length;\n for (let i = 0; i < suffix.length; i++) {\n const a = value.codePointAt(start + i) ?? 0;\n const b = suffix.codePointAt(i) ?? 0;\n if (a !== b && (a | 32) !== (b | 32)) {\n return false;\n }\n }\n return true;\n}\n\nfunction stripSuffixIgnoreCase(value: string, suffix: string): string {\n return endsWithIgnoreCase(value, suffix)\n ? value.slice(0, value.length - suffix.length)\n : value;\n}\n\n/** Issuer URL (`…/oidc`) → Builder API base (`…/api/v1`). Linear-time; no regex. */\nexport function stripOidcPathSuffix(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Issuer URL (`…/api/v1/oidc`) → host origin for signer/API-key routes. Linear-time; no regex. */\nexport function stripIssuerOriginFromOidcUrl(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/api/v1/oidc\");\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Parse and validate an http(s) facade origin (no path). */\nexport function parseHttpOrigin(raw: string | undefined, fallback: string): string {\n const trimmed = (raw ?? fallback).trim();\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new TypeError(\"Origin must be a valid http(s) URL\");\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new TypeError(\"Origin must use http or https\");\n }\n return parsed.origin;\n}\n\n","import { stripTrailingSlashes } from \"./string-utils.js\";\n\n/** RFC 8628 user codes: 4–16 chars, at least one alphanumeric (not all dashes). */\nexport const USER_CODE_RE = /^(?=.*[A-Z0-9])[A-Z0-9-]{4,16}$/;\n\nexport type ValidateDeviceInitiateResult =\n | { ok: true; returnUrl: string }\n | { ok: false; reason: string };\n\nexport type DeviceApprovalTuple =\n | { userCode: string; publicClientId: string }\n | { error: string };\n\nfunction normalizeIssuerUrl(iss: string): string {\n try {\n return stripTrailingSlashes(new URL(iss.trim()).href);\n } catch {\n return iss.trim();\n }\n}\n\n/**\n * Validate OP-issued `iss` + `target_link_uri` before storing a device approval cookie.\n */\nexport function validateDeviceInitiateLogin(input: {\n expectedIssuerUrl: string;\n iss: string;\n targetLinkUri: string;\n}): ValidateDeviceInitiateResult {\n const expectedIss = stripTrailingSlashes(input.expectedIssuerUrl.trim());\n let opOrigin: string;\n try {\n opOrigin = new URL(expectedIss).origin;\n } catch {\n return { ok: false, reason: \"server_not_configured\" };\n }\n\n if (normalizeIssuerUrl(input.iss) !== normalizeIssuerUrl(expectedIss)) {\n return { ok: false, reason: \"iss_mismatch\" };\n }\n\n let target: URL;\n try {\n target = new URL(input.targetLinkUri);\n } catch {\n return { ok: false, reason: \"bad_target_uri\" };\n }\n if (target.origin !== opOrigin) {\n return { ok: false, reason: \"target_origin_mismatch\" };\n }\n if (target.pathname !== \"/oidc/device\") {\n return { ok: false, reason: \"target_path_mismatch\" };\n }\n if (target.hash) {\n return { ok: false, reason: \"target_has_hash\" };\n }\n return { ok: true, returnUrl: target.href };\n}\n\n/**\n * Parse PymtHouse `/oidc/device` URL query for `user_code` + `client_id`.\n */\nexport function extractDeviceApprovalFromTargetLink(\n targetHref: string,\n opts?: { expectedIssuerUrl?: string; expectedPublicClientId?: string },\n): DeviceApprovalTuple {\n let target: URL;\n try {\n target = new URL(targetHref);\n } catch {\n return { error: \"bad_target_uri\" };\n }\n\n if (opts?.expectedIssuerUrl) {\n let opOrigin: string;\n try {\n opOrigin = new URL(stripTrailingSlashes(opts.expectedIssuerUrl.trim())).origin;\n } catch {\n return { error: \"target_origin_mismatch\" };\n }\n if (target.origin !== opOrigin) {\n return { error: \"target_origin_mismatch\" };\n }\n }\n\n if (target.pathname !== \"/oidc/device\") {\n return { error: \"target_path_mismatch\" };\n }\n\n const userCodeRaw = target.searchParams.get(\"user_code\")?.trim() ?? \"\";\n const clientIdRaw = target.searchParams.get(\"client_id\")?.trim() ?? \"\";\n if (!userCodeRaw || !USER_CODE_RE.test(userCodeRaw)) {\n return { error: \"invalid_user_code\" };\n }\n if (!clientIdRaw?.startsWith(\"app_\")) {\n return { error: \"invalid_client_id\" };\n }\n if (opts?.expectedPublicClientId && clientIdRaw !== opts.expectedPublicClientId) {\n return { error: \"client_id_mismatch\" };\n }\n return { userCode: userCodeRaw, publicClientId: clientIdRaw };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/string-utils.ts","../src/device-initiate.ts"],"names":[],"mappings":"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","file":"device-initiate.js","sourcesContent":["/** Removes trailing `/` without regex (linear time). */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {\n end--;\n }\n return value.slice(0, end);\n}\n\nfunction endsWithIgnoreCase(value: string, suffix: string): boolean {\n if (suffix.length > value.length) {\n return false;\n }\n const start = value.length - suffix.length;\n for (let i = 0; i < suffix.length; i++) {\n const a = value.codePointAt(start + i) ?? 0;\n const b = suffix.codePointAt(i) ?? 0;\n if (a !== b && (a | 32) !== (b | 32)) {\n return false;\n }\n }\n return true;\n}\n\nfunction stripSuffixIgnoreCase(value: string, suffix: string): string {\n return endsWithIgnoreCase(value, suffix)\n ? value.slice(0, value.length - suffix.length)\n : value;\n}\n\n/** Issuer URL (`…/oidc`) → Builder API base (`…/api/v1`). Linear-time; no regex. */\nexport function stripOidcPathSuffix(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Issuer URL (`…/api/v1/oidc`) → host origin for signer/API-key routes. Linear-time; no regex. */\nexport function stripIssuerOriginFromOidcUrl(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/api/v1/oidc\");\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Validate gateway session ids before embedding in request URLs. */\nexport function isSafePathSegment(value: unknown): value is string {\n if (typeof value !== \"string\" || value.length === 0 || value.length > 128) {\n return false;\n }\n for (let i = 0; i < value.length; i++) {\n const c = value.codePointAt(i) ?? 0;\n const ok =\n (c >= 48 && c <= 57) ||\n (c >= 65 && c <= 90) ||\n (c >= 97 && c <= 122) ||\n c === 95 ||\n c === 45;\n if (!ok) {\n return false;\n }\n }\n return true;\n}\n\n/** Parse and validate an http(s) facade origin (no path). */\nexport function parseHttpOrigin(raw: string | undefined, fallback: string): string {\n const trimmed = (raw ?? fallback).trim();\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new TypeError(\"Origin must be a valid http(s) URL\");\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new TypeError(\"Origin must use http or https\");\n }\n return parsed.origin;\n}\n\n/** Build a validated DELETE URL for `/api/gateway/sessions/:id`. */\nexport function buildGatewaySessionDeleteUrl(origin: string, sessionId: string): URL {\n if (!isSafePathSegment(sessionId)) {\n throw new TypeError(\"Invalid gateway session id\");\n }\n return new URL(`/api/gateway/sessions/${encodeURIComponent(sessionId)}`, origin);\n}\n","import { stripTrailingSlashes } from \"./string-utils.js\";\n\n/** RFC 8628 user codes: 4–16 chars, at least one alphanumeric (not all dashes). */\nexport const USER_CODE_RE = /^(?=.*[A-Z0-9])[A-Z0-9-]{4,16}$/;\n\nexport type ValidateDeviceInitiateResult =\n | { ok: true; returnUrl: string }\n | { ok: false; reason: string };\n\nexport type DeviceApprovalTuple =\n | { userCode: string; publicClientId: string }\n | { error: string };\n\nfunction normalizeIssuerUrl(iss: string): string {\n try {\n return stripTrailingSlashes(new URL(iss.trim()).href);\n } catch {\n return iss.trim();\n }\n}\n\n/**\n * Validate OP-issued `iss` + `target_link_uri` before storing a device approval cookie.\n */\nexport function validateDeviceInitiateLogin(input: {\n expectedIssuerUrl: string;\n iss: string;\n targetLinkUri: string;\n}): ValidateDeviceInitiateResult {\n const expectedIss = stripTrailingSlashes(input.expectedIssuerUrl.trim());\n let opOrigin: string;\n try {\n opOrigin = new URL(expectedIss).origin;\n } catch {\n return { ok: false, reason: \"server_not_configured\" };\n }\n\n if (normalizeIssuerUrl(input.iss) !== normalizeIssuerUrl(expectedIss)) {\n return { ok: false, reason: \"iss_mismatch\" };\n }\n\n let target: URL;\n try {\n target = new URL(input.targetLinkUri);\n } catch {\n return { ok: false, reason: \"bad_target_uri\" };\n }\n if (target.origin !== opOrigin) {\n return { ok: false, reason: \"target_origin_mismatch\" };\n }\n if (target.pathname !== \"/oidc/device\") {\n return { ok: false, reason: \"target_path_mismatch\" };\n }\n if (target.hash) {\n return { ok: false, reason: \"target_has_hash\" };\n }\n return { ok: true, returnUrl: target.href };\n}\n\n/**\n * Parse PymtHouse `/oidc/device` URL query for `user_code` + `client_id`.\n */\nexport function extractDeviceApprovalFromTargetLink(\n targetHref: string,\n opts?: { expectedIssuerUrl?: string; expectedPublicClientId?: string },\n): DeviceApprovalTuple {\n let target: URL;\n try {\n target = new URL(targetHref);\n } catch {\n return { error: \"bad_target_uri\" };\n }\n\n if (opts?.expectedIssuerUrl) {\n let opOrigin: string;\n try {\n opOrigin = new URL(stripTrailingSlashes(opts.expectedIssuerUrl.trim())).origin;\n } catch {\n return { error: \"target_origin_mismatch\" };\n }\n if (target.origin !== opOrigin) {\n return { error: \"target_origin_mismatch\" };\n }\n }\n\n if (target.pathname !== \"/oidc/device\") {\n return { error: \"target_path_mismatch\" };\n }\n\n const userCodeRaw = target.searchParams.get(\"user_code\")?.trim() ?? \"\";\n const clientIdRaw = target.searchParams.get(\"client_id\")?.trim() ?? \"\";\n if (!userCodeRaw || !USER_CODE_RE.test(userCodeRaw)) {\n return { error: \"invalid_user_code\" };\n }\n if (!clientIdRaw?.startsWith(\"app_\")) {\n return { error: \"invalid_client_id\" };\n }\n if (opts?.expectedPublicClientId && clientIdRaw !== opts.expectedPublicClientId) {\n return { error: \"client_id_mismatch\" };\n }\n return { userCode: userCodeRaw, publicClientId: clientIdRaw };\n}\n"]}
1
+ {"version":3,"sources":["../src/string-utils.ts","../src/device-initiate.ts"],"names":[],"mappings":"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","file":"device-initiate.js","sourcesContent":["/** Removes trailing `/` without regex (linear time). */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {\n end--;\n }\n return value.slice(0, end);\n}\n\nfunction endsWithIgnoreCase(value: string, suffix: string): boolean {\n if (suffix.length > value.length) {\n return false;\n }\n const start = value.length - suffix.length;\n for (let i = 0; i < suffix.length; i++) {\n const a = value.codePointAt(start + i) ?? 0;\n const b = suffix.codePointAt(i) ?? 0;\n if (a !== b && (a | 32) !== (b | 32)) {\n return false;\n }\n }\n return true;\n}\n\nfunction stripSuffixIgnoreCase(value: string, suffix: string): string {\n return endsWithIgnoreCase(value, suffix)\n ? value.slice(0, value.length - suffix.length)\n : value;\n}\n\n/** Issuer URL (`…/oidc`) → Builder API base (`…/api/v1`). Linear-time; no regex. */\nexport function stripOidcPathSuffix(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Issuer URL (`…/api/v1/oidc`) → host origin for signer/API-key routes. Linear-time; no regex. */\nexport function stripIssuerOriginFromOidcUrl(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/api/v1/oidc\");\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Parse and validate an http(s) facade origin (no path). */\nexport function parseHttpOrigin(raw: string | undefined, fallback: string): string {\n const trimmed = (raw ?? fallback).trim();\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new TypeError(\"Origin must be a valid http(s) URL\");\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new TypeError(\"Origin must use http or https\");\n }\n return parsed.origin;\n}\n\n","import { stripTrailingSlashes } from \"./string-utils.js\";\n\n/** RFC 8628 user codes: 4–16 chars, at least one alphanumeric (not all dashes). */\nexport const USER_CODE_RE = /^(?=.*[A-Z0-9])[A-Z0-9-]{4,16}$/;\n\nexport type ValidateDeviceInitiateResult =\n | { ok: true; returnUrl: string }\n | { ok: false; reason: string };\n\nexport type DeviceApprovalTuple =\n | { userCode: string; publicClientId: string }\n | { error: string };\n\nfunction normalizeIssuerUrl(iss: string): string {\n try {\n return stripTrailingSlashes(new URL(iss.trim()).href);\n } catch {\n return iss.trim();\n }\n}\n\n/**\n * Validate OP-issued `iss` + `target_link_uri` before storing a device approval cookie.\n */\nexport function validateDeviceInitiateLogin(input: {\n expectedIssuerUrl: string;\n iss: string;\n targetLinkUri: string;\n}): ValidateDeviceInitiateResult {\n const expectedIss = stripTrailingSlashes(input.expectedIssuerUrl.trim());\n let opOrigin: string;\n try {\n opOrigin = new URL(expectedIss).origin;\n } catch {\n return { ok: false, reason: \"server_not_configured\" };\n }\n\n if (normalizeIssuerUrl(input.iss) !== normalizeIssuerUrl(expectedIss)) {\n return { ok: false, reason: \"iss_mismatch\" };\n }\n\n let target: URL;\n try {\n target = new URL(input.targetLinkUri);\n } catch {\n return { ok: false, reason: \"bad_target_uri\" };\n }\n if (target.origin !== opOrigin) {\n return { ok: false, reason: \"target_origin_mismatch\" };\n }\n if (target.pathname !== \"/oidc/device\") {\n return { ok: false, reason: \"target_path_mismatch\" };\n }\n if (target.hash) {\n return { ok: false, reason: \"target_has_hash\" };\n }\n return { ok: true, returnUrl: target.href };\n}\n\n/**\n * Parse PymtHouse `/oidc/device` URL query for `user_code` + `client_id`.\n */\nexport function extractDeviceApprovalFromTargetLink(\n targetHref: string,\n opts?: { expectedIssuerUrl?: string; expectedPublicClientId?: string },\n): DeviceApprovalTuple {\n let target: URL;\n try {\n target = new URL(targetHref);\n } catch {\n return { error: \"bad_target_uri\" };\n }\n\n if (opts?.expectedIssuerUrl) {\n let opOrigin: string;\n try {\n opOrigin = new URL(stripTrailingSlashes(opts.expectedIssuerUrl.trim())).origin;\n } catch {\n return { error: \"target_origin_mismatch\" };\n }\n if (target.origin !== opOrigin) {\n return { error: \"target_origin_mismatch\" };\n }\n }\n\n if (target.pathname !== \"/oidc/device\") {\n return { error: \"target_path_mismatch\" };\n }\n\n const userCodeRaw = target.searchParams.get(\"user_code\")?.trim() ?? \"\";\n const clientIdRaw = target.searchParams.get(\"client_id\")?.trim() ?? \"\";\n if (!userCodeRaw || !USER_CODE_RE.test(userCodeRaw)) {\n return { error: \"invalid_user_code\" };\n }\n if (!clientIdRaw?.startsWith(\"app_\")) {\n return { error: \"invalid_client_id\" };\n }\n if (opts?.expectedPublicClientId && clientIdRaw !== opts.expectedPublicClientId) {\n return { error: \"client_id_mismatch\" };\n }\n return { userCode: userCodeRaw, publicClientId: clientIdRaw };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/string-utils.ts","../src/discovery.ts","../src/oauth-map.ts","../src/device.ts"],"names":["customFetch","allowInsecureRequests","discoveryRequest","processDiscoveryResponse","ResponseBodyError","OperationProcessingError","deviceAuthorizationRequest","None","processDeviceAuthorizationResponse","deviceCodeGrantRequest","processDeviceCodeResponse"],"mappings":"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","file":"device.cjs","sourcesContent":["export class PmtHouseError extends Error {\n readonly status: number;\n readonly code: string;\n readonly details?: unknown;\n\n constructor(\n message: string,\n {\n status = 500,\n code = \"pymthouse_error\",\n details,\n }: {\n status?: number;\n code?: string;\n details?: unknown;\n } = {},\n ) {\n super(message);\n this.name = \"PmtHouseError\";\n this.status = status;\n this.code = code;\n this.details = details;\n }\n}\n\nexport function toPmtHouseError(\n error: unknown,\n fallbackMessage: string,\n): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n\n if (error instanceof Error) {\n return new PmtHouseError(error.message || fallbackMessage, {\n code: \"unexpected_error\",\n status: 500,\n });\n }\n\n return new PmtHouseError(fallbackMessage, {\n code: \"unexpected_error\",\n status: 500,\n });\n}\n","/** Removes trailing `/` without regex (linear time). */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {\n end--;\n }\n return value.slice(0, end);\n}\n\nfunction endsWithIgnoreCase(value: string, suffix: string): boolean {\n if (suffix.length > value.length) {\n return false;\n }\n const start = value.length - suffix.length;\n for (let i = 0; i < suffix.length; i++) {\n const a = value.codePointAt(start + i) ?? 0;\n const b = suffix.codePointAt(i) ?? 0;\n if (a !== b && (a | 32) !== (b | 32)) {\n return false;\n }\n }\n return true;\n}\n\nfunction stripSuffixIgnoreCase(value: string, suffix: string): string {\n return endsWithIgnoreCase(value, suffix)\n ? value.slice(0, value.length - suffix.length)\n : value;\n}\n\n/** Issuer URL (`…/oidc`) → Builder API base (`…/api/v1`). Linear-time; no regex. */\nexport function stripOidcPathSuffix(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Issuer URL (`…/api/v1/oidc`) → host origin for signer/API-key routes. Linear-time; no regex. */\nexport function stripIssuerOriginFromOidcUrl(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/api/v1/oidc\");\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Validate gateway session ids before embedding in request URLs. */\nexport function isSafePathSegment(value: unknown): value is string {\n if (typeof value !== \"string\" || value.length === 0 || value.length > 128) {\n return false;\n }\n for (let i = 0; i < value.length; i++) {\n const c = value.codePointAt(i) ?? 0;\n const ok =\n (c >= 48 && c <= 57) ||\n (c >= 65 && c <= 90) ||\n (c >= 97 && c <= 122) ||\n c === 95 ||\n c === 45;\n if (!ok) {\n return false;\n }\n }\n return true;\n}\n\n/** Parse and validate an http(s) facade origin (no path). */\nexport function parseHttpOrigin(raw: string | undefined, fallback: string): string {\n const trimmed = (raw ?? fallback).trim();\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new TypeError(\"Origin must be a valid http(s) URL\");\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new TypeError(\"Origin must use http or https\");\n }\n return parsed.origin;\n}\n\n/** Build a validated DELETE URL for `/api/gateway/sessions/:id`. */\nexport function buildGatewaySessionDeleteUrl(origin: string, sessionId: string): URL {\n if (!isSafePathSegment(sessionId)) {\n throw new TypeError(\"Invalid gateway session id\");\n }\n return new URL(`/api/gateway/sessions/${encodeURIComponent(sessionId)}`, origin);\n}\n","import {\n allowInsecureRequests,\n customFetch,\n discoveryRequest,\n processDiscoveryResponse,\n type AuthorizationServer,\n} from \"oauth4webapi\";\nimport { PmtHouseError } from \"./errors.js\";\nimport { stripTrailingSlashes } from \"./string-utils.js\";\nimport type { FetchLike, OidcDiscoveryDocument } from \"./types.js\";\n\nexport function authorizationServerToOidcDocument(as: AuthorizationServer): OidcDiscoveryDocument {\n const tokenEndpoint = as.token_endpoint;\n const jwksUri = as.jwks_uri;\n if (!tokenEndpoint || !jwksUri) {\n throw new PmtHouseError(\"OIDC discovery document is missing token_endpoint or jwks_uri\", {\n status: 500,\n code: \"oidc_discovery_invalid\",\n });\n }\n return {\n issuer: as.issuer,\n authorization_endpoint: as.authorization_endpoint ?? \"\",\n token_endpoint: tokenEndpoint,\n jwks_uri: jwksUri,\n userinfo_endpoint: as.userinfo_endpoint,\n device_authorization_endpoint: as.device_authorization_endpoint,\n };\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000;\n\ntype CacheEntry = {\n as: AuthorizationServer;\n fetchedAt: number;\n};\n\nconst discoveryCache = new Map<string, CacheEntry>();\n\nfunction normalizedIssuerKey(issuerUrl: string): string {\n return stripTrailingSlashes(issuerUrl);\n}\n\nexport interface LoadAuthorizationServerOptions {\n force?: boolean;\n allowInsecureHttp?: boolean;\n}\n\n/**\n * Loads OIDC discovery metadata via oauth4webapi (RFC 8414 / OIDC Discovery), with a 5-minute cache.\n */\nexport async function loadAuthorizationServer(\n issuerUrl: string,\n fetchImpl: FetchLike,\n options: LoadAuthorizationServerOptions = {},\n): Promise<AuthorizationServer> {\n const key = normalizedIssuerKey(issuerUrl);\n const now = Date.now();\n const cached = discoveryCache.get(key);\n\n if (!options.force && cached && now - cached.fetchedAt < CACHE_TTL_MS) {\n return cached.as;\n }\n\n const issuerIdentifier = new URL(key);\n const discoveryOpts: Parameters<typeof discoveryRequest>[1] = {\n algorithm: \"oidc\",\n [customFetch]: fetchImpl,\n };\n if (options.allowInsecureHttp) {\n discoveryOpts[allowInsecureRequests] = true;\n }\n\n let response: Response;\n try {\n response = await discoveryRequest(issuerIdentifier, discoveryOpts);\n } catch (e) {\n throw mapDiscoveryNetworkError(e);\n }\n\n let as: AuthorizationServer;\n try {\n as = await processDiscoveryResponse(issuerIdentifier, response);\n } catch (e) {\n throw mapOAuthDiscoveryError(e);\n }\n\n discoveryCache.set(key, { as, fetchedAt: now });\n return as;\n}\n\nexport async function fetchDiscoveryDocument(\n issuerUrl: string,\n fetchImpl: FetchLike,\n options: LoadAuthorizationServerOptions = {},\n): Promise<OidcDiscoveryDocument> {\n const as = await loadAuthorizationServer(issuerUrl, fetchImpl, options);\n return authorizationServerToOidcDocument(as);\n}\n\nexport function clearDiscoveryCache(issuerUrl?: string): void {\n if (!issuerUrl) {\n discoveryCache.clear();\n return;\n }\n discoveryCache.delete(normalizedIssuerKey(issuerUrl));\n}\n\nfunction mapOAuthDiscoveryError(error: unknown): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n if (error instanceof Error) {\n return new PmtHouseError(error.message, {\n status: 500,\n code: \"oidc_discovery_invalid\",\n details: { cause: error.cause },\n });\n }\n return new PmtHouseError(\"OIDC discovery failed\", {\n status: 500,\n code: \"oidc_discovery_invalid\",\n });\n}\n\nfunction mapDiscoveryNetworkError(error: unknown): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n if (error instanceof Error) {\n return new PmtHouseError(`Failed to load OIDC discovery: ${error.message}`, {\n status: 502,\n code: \"oidc_discovery_failed\",\n });\n }\n return new PmtHouseError(\"Failed to load OIDC discovery\", {\n status: 502,\n code: \"oidc_discovery_failed\",\n });\n}\n","import { type Client, OperationProcessingError, ResponseBodyError } from \"oauth4webapi\";\nimport { PmtHouseError } from \"./errors.js\";\nimport type { ClientCredentialsTokenResponse, TokenExchangeResponse } from \"./types.js\";\n\nconst ACCEPTED_ISSUED_TOKEN_TYPES = new Set([\n \"urn:ietf:params:oauth:token-type:access_token\",\n \"urn:pmth:token-type:remote-signer-session\",\n]);\n\nexport function mapOAuthError(error: unknown): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n\n if (error instanceof ResponseBodyError) {\n const cause = error.cause as Record<string, unknown>;\n const description =\n typeof error.error_description === \"string\"\n ? error.error_description\n : error.message;\n const details: Record<string, unknown> = { ...cause };\n if (typeof cause.error_uri === \"string\") {\n details.error_uri = cause.error_uri;\n }\n return new PmtHouseError(description, {\n status: error.status,\n code: error.error,\n details,\n });\n }\n\n if (error instanceof OperationProcessingError) {\n return new PmtHouseError(error.message, {\n status: 502,\n code: error.code ?? \"oauth_processing_error\",\n details: { cause: error.cause },\n });\n }\n\n if (error instanceof Error) {\n return new PmtHouseError(error.message, {\n status: 500,\n code: \"unexpected_error\",\n });\n }\n\n return new PmtHouseError(\"Unexpected error\", {\n status: 500,\n code: \"unexpected_error\",\n });\n}\n\nexport function tokenEndpointResponseToExchange(\n tr: import(\"oauth4webapi\").TokenEndpointResponse,\n): TokenExchangeResponse {\n const issued = tr.issued_token_type;\n if (typeof issued !== \"string\" || !ACCEPTED_ISSUED_TOKEN_TYPES.has(issued)) {\n throw new PmtHouseError(\"Token exchange returned an unexpected issued_token_type\", {\n status: 502,\n code: \"invalid_token_response\",\n details: { issued_token_type: issued },\n });\n }\n\n const tt = tr.token_type;\n if (typeof tt !== \"string\" || tt.toLowerCase() !== \"bearer\") {\n throw new PmtHouseError(\"Token endpoint returned a non-Bearer token_type\", {\n status: 502,\n code: \"invalid_token_response\",\n details: { token_type: tt },\n });\n }\n\n const expiresIn = tr.expires_in;\n if (typeof expiresIn !== \"number\") {\n throw new PmtHouseError(\"Token response missing expires_in\", {\n status: 502,\n code: \"invalid_token_response\",\n });\n }\n\n const scope = typeof tr.scope === \"string\" ? tr.scope : \"\";\n\n return {\n access_token: tr.access_token,\n token_type: \"Bearer\",\n expires_in: expiresIn,\n scope,\n issued_token_type: issued,\n };\n}\n\nexport function tokenEndpointResponseToClientCredentials(\n tr: import(\"oauth4webapi\").TokenEndpointResponse,\n): ClientCredentialsTokenResponse {\n const tt = tr.token_type;\n if (typeof tt !== \"string\" || tt.toLowerCase() !== \"bearer\") {\n throw new PmtHouseError(\"Token endpoint returned a non-Bearer token_type\", {\n status: 502,\n code: \"invalid_token_response\",\n details: { token_type: tt },\n });\n }\n\n return {\n access_token: tr.access_token,\n token_type: \"Bearer\",\n expires_in: tr.expires_in,\n scope: typeof tr.scope === \"string\" ? tr.scope : undefined,\n };\n}\n\nexport function m2mClient(clientId: string): Client {\n return { client_id: clientId };\n}\n","import {\n allowInsecureRequests,\n customFetch,\n deviceAuthorizationRequest,\n deviceCodeGrantRequest,\n None,\n processDeviceAuthorizationResponse,\n processDeviceCodeResponse,\n ResponseBodyError,\n type Client,\n} from \"oauth4webapi\";\nimport { loadAuthorizationServer } from \"./discovery.js\";\nimport { PmtHouseError } from \"./errors.js\";\nimport { mapOAuthError } from \"./oauth-map.js\";\nimport type { FetchLike } from \"./types.js\";\n\nexport interface PollDeviceTokenOptions {\n issuerUrl: string;\n /** Public OAuth `client_id` (RFC 8628). */\n clientId: string;\n /** Space-separated scopes for the device authorization request. */\n scope?: string;\n fetch?: FetchLike;\n allowInsecureHttp?: boolean;\n signal?: AbortSignal;\n onUserCode?: (info: {\n userCode: string;\n verificationUri: string;\n verificationUriComplete?: string;\n expiresIn: number;\n intervalSeconds?: number;\n }) => void;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(t);\n reject(\n signal.reason instanceof Error\n ? signal.reason\n : new Error(typeof signal.reason === \"string\" ? signal.reason : \"Aborted\"),\n );\n },\n { once: true },\n );\n });\n}\n\n/**\n * RFC 8628 device authorization grant: request a device code, then poll the token endpoint until\n * tokens are issued (handles `authorization_pending` and `slow_down`).\n */\nexport async function pollDeviceToken(\n options: PollDeviceTokenOptions,\n): Promise<import(\"oauth4webapi\").TokenEndpointResponse> {\n const fetchImpl = options.fetch ?? fetch;\n const as = await loadAuthorizationServer(options.issuerUrl, fetchImpl, {\n allowInsecureHttp: options.allowInsecureHttp,\n });\n\n if (!as.device_authorization_endpoint) {\n throw new PmtHouseError(\n \"Authorization server metadata has no device_authorization_endpoint\",\n { status: 400, code: \"unsupported_grant\" },\n );\n }\n\n const client: Client = { client_id: options.clientId };\n const params = new URLSearchParams();\n if (options.scope) {\n params.set(\"scope\", options.scope);\n }\n\n const httpOpts: Record<symbol, unknown> = {\n [customFetch]: fetchImpl,\n };\n if (options.allowInsecureHttp) {\n httpOpts[allowInsecureRequests] = true;\n }\n\n let deviceResponse: Response;\n try {\n deviceResponse = await deviceAuthorizationRequest(\n as,\n client,\n None(),\n params,\n httpOpts as import(\"oauth4webapi\").DeviceAuthorizationRequestOptions,\n );\n } catch (e) {\n throw mapOAuthError(e);\n }\n\n let dar: import(\"oauth4webapi\").DeviceAuthorizationResponse;\n try {\n dar = await processDeviceAuthorizationResponse(as, client, deviceResponse);\n } catch (e) {\n throw mapOAuthError(e);\n }\n\n options.onUserCode?.({\n userCode: dar.user_code,\n verificationUri: dar.verification_uri,\n verificationUriComplete: dar.verification_uri_complete,\n expiresIn: dar.expires_in,\n intervalSeconds: dar.interval,\n });\n\n let pollIntervalMs = (dar.interval ?? 5) * 1000;\n const deadline = Date.now() + dar.expires_in * 1000;\n let firstPoll = true;\n\n while (Date.now() < deadline) {\n if (options.signal?.aborted) {\n throw options.signal.reason instanceof Error\n ? options.signal.reason\n : new Error(\"Aborted\");\n }\n\n if (!firstPoll) {\n await sleep(pollIntervalMs, options.signal);\n }\n firstPoll = false;\n\n let tokenResponse: Response;\n try {\n tokenResponse = await deviceCodeGrantRequest(\n as,\n client,\n None(),\n dar.device_code,\n httpOpts as import(\"oauth4webapi\").TokenEndpointRequestOptions,\n );\n } catch (e) {\n throw mapOAuthError(e);\n }\n\n try {\n return await processDeviceCodeResponse(as, client, tokenResponse);\n } catch (e) {\n if (e instanceof ResponseBodyError) {\n if (e.error === \"authorization_pending\") {\n continue;\n }\n if (e.error === \"slow_down\") {\n pollIntervalMs += 5000;\n continue;\n }\n }\n throw mapOAuthError(e);\n }\n }\n\n throw new PmtHouseError(\"Device authorization expired before completion\", {\n status: 408,\n code: \"device_flow_expired\",\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/string-utils.ts","../src/discovery.ts","../src/oauth-map.ts","../src/device.ts"],"names":["customFetch","allowInsecureRequests","discoveryRequest","processDiscoveryResponse","ResponseBodyError","OperationProcessingError","deviceAuthorizationRequest","None","processDeviceAuthorizationResponse","deviceCodeGrantRequest","processDeviceCodeResponse"],"mappings":"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","file":"device.cjs","sourcesContent":["export class PmtHouseError extends Error {\n readonly status: number;\n readonly code: string;\n readonly details?: unknown;\n\n constructor(\n message: string,\n {\n status = 500,\n code = \"pymthouse_error\",\n details,\n }: {\n status?: number;\n code?: string;\n details?: unknown;\n } = {},\n ) {\n super(message);\n this.name = \"PmtHouseError\";\n this.status = status;\n this.code = code;\n this.details = details;\n }\n}\n\nexport function toPmtHouseError(\n error: unknown,\n fallbackMessage: string,\n): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n\n if (error instanceof Error) {\n return new PmtHouseError(error.message || fallbackMessage, {\n code: \"unexpected_error\",\n status: 500,\n });\n }\n\n return new PmtHouseError(fallbackMessage, {\n code: \"unexpected_error\",\n status: 500,\n });\n}\n","/** Removes trailing `/` without regex (linear time). */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {\n end--;\n }\n return value.slice(0, end);\n}\n\nfunction endsWithIgnoreCase(value: string, suffix: string): boolean {\n if (suffix.length > value.length) {\n return false;\n }\n const start = value.length - suffix.length;\n for (let i = 0; i < suffix.length; i++) {\n const a = value.codePointAt(start + i) ?? 0;\n const b = suffix.codePointAt(i) ?? 0;\n if (a !== b && (a | 32) !== (b | 32)) {\n return false;\n }\n }\n return true;\n}\n\nfunction stripSuffixIgnoreCase(value: string, suffix: string): string {\n return endsWithIgnoreCase(value, suffix)\n ? value.slice(0, value.length - suffix.length)\n : value;\n}\n\n/** Issuer URL (`…/oidc`) → Builder API base (`…/api/v1`). Linear-time; no regex. */\nexport function stripOidcPathSuffix(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Issuer URL (`…/api/v1/oidc`) → host origin for signer/API-key routes. Linear-time; no regex. */\nexport function stripIssuerOriginFromOidcUrl(issuerUrl: string): string {\n let base = stripTrailingSlashes(issuerUrl.trim());\n base = stripSuffixIgnoreCase(base, \"/api/v1/oidc\");\n base = stripSuffixIgnoreCase(base, \"/oidc\");\n return stripTrailingSlashes(base);\n}\n\n/** Parse and validate an http(s) facade origin (no path). */\nexport function parseHttpOrigin(raw: string | undefined, fallback: string): string {\n const trimmed = (raw ?? fallback).trim();\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new TypeError(\"Origin must be a valid http(s) URL\");\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new TypeError(\"Origin must use http or https\");\n }\n return parsed.origin;\n}\n\n","import {\n allowInsecureRequests,\n customFetch,\n discoveryRequest,\n processDiscoveryResponse,\n type AuthorizationServer,\n} from \"oauth4webapi\";\nimport { PmtHouseError } from \"./errors.js\";\nimport { stripTrailingSlashes } from \"./string-utils.js\";\nimport type { FetchLike, OidcDiscoveryDocument } from \"./types.js\";\n\nexport function authorizationServerToOidcDocument(as: AuthorizationServer): OidcDiscoveryDocument {\n const tokenEndpoint = as.token_endpoint;\n const jwksUri = as.jwks_uri;\n if (!tokenEndpoint || !jwksUri) {\n throw new PmtHouseError(\"OIDC discovery document is missing token_endpoint or jwks_uri\", {\n status: 500,\n code: \"oidc_discovery_invalid\",\n });\n }\n return {\n issuer: as.issuer,\n authorization_endpoint: as.authorization_endpoint ?? \"\",\n token_endpoint: tokenEndpoint,\n jwks_uri: jwksUri,\n userinfo_endpoint: as.userinfo_endpoint,\n device_authorization_endpoint: as.device_authorization_endpoint,\n };\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000;\n\ntype CacheEntry = {\n as: AuthorizationServer;\n fetchedAt: number;\n};\n\nconst discoveryCache = new Map<string, CacheEntry>();\n\nfunction normalizedIssuerKey(issuerUrl: string): string {\n return stripTrailingSlashes(issuerUrl);\n}\n\nexport interface LoadAuthorizationServerOptions {\n force?: boolean;\n allowInsecureHttp?: boolean;\n}\n\n/**\n * Loads OIDC discovery metadata via oauth4webapi (RFC 8414 / OIDC Discovery), with a 5-minute cache.\n */\nexport async function loadAuthorizationServer(\n issuerUrl: string,\n fetchImpl: FetchLike,\n options: LoadAuthorizationServerOptions = {},\n): Promise<AuthorizationServer> {\n const key = normalizedIssuerKey(issuerUrl);\n const now = Date.now();\n const cached = discoveryCache.get(key);\n\n if (!options.force && cached && now - cached.fetchedAt < CACHE_TTL_MS) {\n return cached.as;\n }\n\n const issuerIdentifier = new URL(key);\n const discoveryOpts: Parameters<typeof discoveryRequest>[1] = {\n algorithm: \"oidc\",\n [customFetch]: fetchImpl,\n };\n if (options.allowInsecureHttp) {\n discoveryOpts[allowInsecureRequests] = true;\n }\n\n let response: Response;\n try {\n response = await discoveryRequest(issuerIdentifier, discoveryOpts);\n } catch (e) {\n throw mapDiscoveryNetworkError(e);\n }\n\n let as: AuthorizationServer;\n try {\n as = await processDiscoveryResponse(issuerIdentifier, response);\n } catch (e) {\n throw mapOAuthDiscoveryError(e);\n }\n\n discoveryCache.set(key, { as, fetchedAt: now });\n return as;\n}\n\nexport async function fetchDiscoveryDocument(\n issuerUrl: string,\n fetchImpl: FetchLike,\n options: LoadAuthorizationServerOptions = {},\n): Promise<OidcDiscoveryDocument> {\n const as = await loadAuthorizationServer(issuerUrl, fetchImpl, options);\n return authorizationServerToOidcDocument(as);\n}\n\nexport function clearDiscoveryCache(issuerUrl?: string): void {\n if (!issuerUrl) {\n discoveryCache.clear();\n return;\n }\n discoveryCache.delete(normalizedIssuerKey(issuerUrl));\n}\n\nfunction mapOAuthDiscoveryError(error: unknown): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n if (error instanceof Error) {\n return new PmtHouseError(error.message, {\n status: 500,\n code: \"oidc_discovery_invalid\",\n details: { cause: error.cause },\n });\n }\n return new PmtHouseError(\"OIDC discovery failed\", {\n status: 500,\n code: \"oidc_discovery_invalid\",\n });\n}\n\nfunction mapDiscoveryNetworkError(error: unknown): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n if (error instanceof Error) {\n return new PmtHouseError(`Failed to load OIDC discovery: ${error.message}`, {\n status: 502,\n code: \"oidc_discovery_failed\",\n });\n }\n return new PmtHouseError(\"Failed to load OIDC discovery\", {\n status: 502,\n code: \"oidc_discovery_failed\",\n });\n}\n","import { type Client, OperationProcessingError, ResponseBodyError } from \"oauth4webapi\";\nimport { PmtHouseError } from \"./errors.js\";\nimport type { ClientCredentialsTokenResponse, TokenExchangeResponse } from \"./types.js\";\n\nconst ACCEPTED_ISSUED_TOKEN_TYPES = new Set([\n \"urn:ietf:params:oauth:token-type:access_token\",\n \"urn:pmth:token-type:remote-signer-session\",\n]);\n\nexport function mapOAuthError(error: unknown): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n\n if (error instanceof ResponseBodyError) {\n const cause = error.cause as Record<string, unknown>;\n const description =\n typeof error.error_description === \"string\"\n ? error.error_description\n : error.message;\n const details: Record<string, unknown> = { ...cause };\n if (typeof cause.error_uri === \"string\") {\n details.error_uri = cause.error_uri;\n }\n return new PmtHouseError(description, {\n status: error.status,\n code: error.error,\n details,\n });\n }\n\n if (error instanceof OperationProcessingError) {\n return new PmtHouseError(error.message, {\n status: 502,\n code: error.code ?? \"oauth_processing_error\",\n details: { cause: error.cause },\n });\n }\n\n if (error instanceof Error) {\n return new PmtHouseError(error.message, {\n status: 500,\n code: \"unexpected_error\",\n });\n }\n\n return new PmtHouseError(\"Unexpected error\", {\n status: 500,\n code: \"unexpected_error\",\n });\n}\n\nexport function tokenEndpointResponseToExchange(\n tr: import(\"oauth4webapi\").TokenEndpointResponse,\n): TokenExchangeResponse {\n const issued = tr.issued_token_type;\n if (typeof issued !== \"string\" || !ACCEPTED_ISSUED_TOKEN_TYPES.has(issued)) {\n throw new PmtHouseError(\"Token exchange returned an unexpected issued_token_type\", {\n status: 502,\n code: \"invalid_token_response\",\n details: { issued_token_type: issued },\n });\n }\n\n const tt = tr.token_type;\n if (typeof tt !== \"string\" || tt.toLowerCase() !== \"bearer\") {\n throw new PmtHouseError(\"Token endpoint returned a non-Bearer token_type\", {\n status: 502,\n code: \"invalid_token_response\",\n details: { token_type: tt },\n });\n }\n\n const expiresIn = tr.expires_in;\n if (typeof expiresIn !== \"number\") {\n throw new PmtHouseError(\"Token response missing expires_in\", {\n status: 502,\n code: \"invalid_token_response\",\n });\n }\n\n const scope = typeof tr.scope === \"string\" ? tr.scope : \"\";\n\n return {\n access_token: tr.access_token,\n token_type: \"Bearer\",\n expires_in: expiresIn,\n scope,\n issued_token_type: issued,\n };\n}\n\nexport function tokenEndpointResponseToClientCredentials(\n tr: import(\"oauth4webapi\").TokenEndpointResponse,\n): ClientCredentialsTokenResponse {\n const tt = tr.token_type;\n if (typeof tt !== \"string\" || tt.toLowerCase() !== \"bearer\") {\n throw new PmtHouseError(\"Token endpoint returned a non-Bearer token_type\", {\n status: 502,\n code: \"invalid_token_response\",\n details: { token_type: tt },\n });\n }\n\n return {\n access_token: tr.access_token,\n token_type: \"Bearer\",\n expires_in: tr.expires_in,\n scope: typeof tr.scope === \"string\" ? tr.scope : undefined,\n };\n}\n\nexport function m2mClient(clientId: string): Client {\n return { client_id: clientId };\n}\n","import {\n allowInsecureRequests,\n customFetch,\n deviceAuthorizationRequest,\n deviceCodeGrantRequest,\n None,\n processDeviceAuthorizationResponse,\n processDeviceCodeResponse,\n ResponseBodyError,\n type Client,\n} from \"oauth4webapi\";\nimport { loadAuthorizationServer } from \"./discovery.js\";\nimport { PmtHouseError } from \"./errors.js\";\nimport { mapOAuthError } from \"./oauth-map.js\";\nimport type { FetchLike } from \"./types.js\";\n\nexport interface PollDeviceTokenOptions {\n issuerUrl: string;\n /** Public OAuth `client_id` (RFC 8628). */\n clientId: string;\n /** Space-separated scopes for the device authorization request. */\n scope?: string;\n fetch?: FetchLike;\n allowInsecureHttp?: boolean;\n signal?: AbortSignal;\n onUserCode?: (info: {\n userCode: string;\n verificationUri: string;\n verificationUriComplete?: string;\n expiresIn: number;\n intervalSeconds?: number;\n }) => void;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(t);\n reject(\n signal.reason instanceof Error\n ? signal.reason\n : new Error(typeof signal.reason === \"string\" ? signal.reason : \"Aborted\"),\n );\n },\n { once: true },\n );\n });\n}\n\n/**\n * RFC 8628 device authorization grant: request a device code, then poll the token endpoint until\n * tokens are issued (handles `authorization_pending` and `slow_down`).\n */\nexport async function pollDeviceToken(\n options: PollDeviceTokenOptions,\n): Promise<import(\"oauth4webapi\").TokenEndpointResponse> {\n const fetchImpl = options.fetch ?? fetch;\n const as = await loadAuthorizationServer(options.issuerUrl, fetchImpl, {\n allowInsecureHttp: options.allowInsecureHttp,\n });\n\n if (!as.device_authorization_endpoint) {\n throw new PmtHouseError(\n \"Authorization server metadata has no device_authorization_endpoint\",\n { status: 400, code: \"unsupported_grant\" },\n );\n }\n\n const client: Client = { client_id: options.clientId };\n const params = new URLSearchParams();\n if (options.scope) {\n params.set(\"scope\", options.scope);\n }\n\n const httpOpts: Record<symbol, unknown> = {\n [customFetch]: fetchImpl,\n };\n if (options.allowInsecureHttp) {\n httpOpts[allowInsecureRequests] = true;\n }\n\n let deviceResponse: Response;\n try {\n deviceResponse = await deviceAuthorizationRequest(\n as,\n client,\n None(),\n params,\n httpOpts as import(\"oauth4webapi\").DeviceAuthorizationRequestOptions,\n );\n } catch (e) {\n throw mapOAuthError(e);\n }\n\n let dar: import(\"oauth4webapi\").DeviceAuthorizationResponse;\n try {\n dar = await processDeviceAuthorizationResponse(as, client, deviceResponse);\n } catch (e) {\n throw mapOAuthError(e);\n }\n\n options.onUserCode?.({\n userCode: dar.user_code,\n verificationUri: dar.verification_uri,\n verificationUriComplete: dar.verification_uri_complete,\n expiresIn: dar.expires_in,\n intervalSeconds: dar.interval,\n });\n\n let pollIntervalMs = (dar.interval ?? 5) * 1000;\n const deadline = Date.now() + dar.expires_in * 1000;\n let firstPoll = true;\n\n while (Date.now() < deadline) {\n if (options.signal?.aborted) {\n throw options.signal.reason instanceof Error\n ? options.signal.reason\n : new Error(\"Aborted\");\n }\n\n if (!firstPoll) {\n await sleep(pollIntervalMs, options.signal);\n }\n firstPoll = false;\n\n let tokenResponse: Response;\n try {\n tokenResponse = await deviceCodeGrantRequest(\n as,\n client,\n None(),\n dar.device_code,\n httpOpts as import(\"oauth4webapi\").TokenEndpointRequestOptions,\n );\n } catch (e) {\n throw mapOAuthError(e);\n }\n\n try {\n return await processDeviceCodeResponse(as, client, tokenResponse);\n } catch (e) {\n if (e instanceof ResponseBodyError) {\n if (e.error === \"authorization_pending\") {\n continue;\n }\n if (e.error === \"slow_down\") {\n pollIntervalMs += 5000;\n continue;\n }\n }\n throw mapOAuthError(e);\n }\n }\n\n throw new PmtHouseError(\"Device authorization expired before completion\", {\n status: 408,\n code: \"device_flow_expired\",\n });\n}\n"]}
package/dist/device.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as oauth4webapi from 'oauth4webapi';
2
- import { F as FetchLike } from './types-_R1AwEZp.cjs';
2
+ import { F as FetchLike } from './types-BORaHW_x.cjs';
3
3
 
4
4
  interface PollDeviceTokenOptions {
5
5
  issuerUrl: string;
package/dist/device.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as oauth4webapi from 'oauth4webapi';
2
- import { F as FetchLike } from './types-_R1AwEZp.js';
2
+ import { F as FetchLike } from './types-BORaHW_x.js';
3
3
 
4
4
  interface PollDeviceTokenOptions {
5
5
  issuerUrl: string;