@pymthouse/builder-sdk 0.3.1 → 0.4.1-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -28
- package/dist/{client-BHfjDvIe.d.ts → client-CauCfGa7.d.ts} +1 -1
- package/dist/{client-CvhJEhjV.d.cts → client-D1Xz-xlx.d.cts} +1 -1
- package/dist/config.cjs +0 -21
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +1 -5
- package/dist/config.d.ts +1 -5
- package/dist/config.js +1 -20
- package/dist/config.js.map +1 -1
- package/dist/device-initiate.cjs.map +1 -1
- package/dist/device-initiate.js.map +1 -1
- package/dist/device.cjs.map +1 -1
- package/dist/device.d.cts +1 -1
- package/dist/device.d.ts +1 -1
- package/dist/device.js.map +1 -1
- package/dist/env.cjs +13 -4
- package/dist/env.cjs.map +1 -1
- package/dist/env.d.cts +2 -2
- package/dist/env.d.ts +2 -2
- package/dist/env.js +13 -4
- package/dist/env.js.map +1 -1
- package/dist/index-BTDKEorK.d.ts +64 -0
- package/dist/index-BixH4VIG.d.cts +64 -0
- package/dist/index.cjs +13 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -5
- package/dist/index.d.ts +29 -5
- package/dist/index.js +13 -4
- package/dist/index.js.map +1 -1
- package/dist/{ingest-DoKJTWU9.d.ts → proxy-JrT6raU_.d.cts} +5 -42
- package/dist/{ingest-B3Yi8Tb1.d.cts → proxy-U32DFNuj.d.ts} +5 -42
- package/dist/signer/server.cjs +799 -895
- package/dist/signer/server.cjs.map +1 -1
- package/dist/signer/server.d.cts +9 -13
- package/dist/signer/server.d.ts +9 -13
- package/dist/signer/server.js +799 -893
- package/dist/signer/server.js.map +1 -1
- package/dist/signer/webhook/adapters/api-key.cjs +78 -0
- package/dist/signer/webhook/adapters/api-key.cjs.map +1 -0
- package/dist/signer/webhook/adapters/api-key.d.cts +18 -0
- package/dist/signer/webhook/adapters/api-key.d.ts +18 -0
- package/dist/signer/webhook/adapters/api-key.js +76 -0
- package/dist/signer/webhook/adapters/api-key.js.map +1 -0
- package/dist/signer/webhook/adapters/composite.cjs +60 -0
- package/dist/signer/webhook/adapters/composite.cjs.map +1 -0
- package/dist/signer/webhook/adapters/composite.d.cts +5 -0
- package/dist/signer/webhook/adapters/composite.d.ts +5 -0
- package/dist/signer/webhook/adapters/composite.js +58 -0
- package/dist/signer/webhook/adapters/composite.js.map +1 -0
- package/dist/signer/webhook/adapters/oauth1.cjs +18 -0
- package/dist/signer/webhook/adapters/oauth1.cjs.map +1 -0
- package/dist/signer/webhook/adapters/oauth1.d.cts +19 -0
- package/dist/signer/webhook/adapters/oauth1.d.ts +19 -0
- package/dist/signer/webhook/adapters/oauth1.js +16 -0
- package/dist/signer/webhook/adapters/oauth1.js.map +1 -0
- package/dist/signer/webhook/adapters/oidc.cjs +522 -0
- package/dist/signer/webhook/adapters/oidc.cjs.map +1 -0
- package/dist/signer/webhook/adapters/oidc.d.cts +4 -0
- package/dist/signer/webhook/adapters/oidc.d.ts +4 -0
- package/dist/signer/webhook/adapters/oidc.js +515 -0
- package/dist/signer/webhook/adapters/oidc.js.map +1 -0
- package/dist/signer/webhook/adapters/trusted-headers.cjs +103 -0
- package/dist/signer/webhook/adapters/trusted-headers.cjs.map +1 -0
- package/dist/signer/webhook/adapters/trusted-headers.d.cts +18 -0
- package/dist/signer/webhook/adapters/trusted-headers.d.ts +18 -0
- package/dist/signer/webhook/adapters/trusted-headers.js +99 -0
- package/dist/signer/webhook/adapters/trusted-headers.js.map +1 -0
- package/dist/signer/webhook.cjs +747 -0
- package/dist/signer/webhook.cjs.map +1 -0
- package/dist/signer/webhook.d.cts +26 -0
- package/dist/signer/webhook.d.ts +26 -0
- package/dist/signer/webhook.js +721 -0
- package/dist/signer/webhook.js.map +1 -0
- package/dist/tokens.d.cts +1 -1
- package/dist/tokens.d.ts +1 -1
- package/dist/{types-_R1AwEZp.d.cts → types-BORaHW_x.d.cts} +5 -5
- package/dist/{types-_R1AwEZp.d.ts → types-BORaHW_x.d.ts} +5 -5
- package/dist/verifier-B-WFDMz6.d.cts +48 -0
- package/dist/verifier-B-WFDMz6.d.ts +48 -0
- package/dist/verify.cjs.map +1 -1
- package/dist/verify.d.cts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js.map +1 -1
- package/package.json +30 -30
- package/dist/gateway/client/index.cjs +0 -492
- package/dist/gateway/client/index.cjs.map +0 -1
- package/dist/gateway/client/index.d.cts +0 -63
- package/dist/gateway/client/index.d.ts +0 -63
- package/dist/gateway/client/index.js +0 -489
- package/dist/gateway/client/index.js.map +0 -1
- package/dist/gateway/index.cjs +0 -16
- package/dist/gateway/index.cjs.map +0 -1
- package/dist/gateway/index.d.cts +0 -52
- package/dist/gateway/index.d.ts +0 -52
- package/dist/gateway/index.js +0 -10
- package/dist/gateway/index.js.map +0 -1
- package/dist/gateway/server/index.cjs +0 -1248
- package/dist/gateway/server/index.cjs.map +0 -1
- package/dist/gateway/server/index.d.cts +0 -31
- package/dist/gateway/server/index.d.ts +0 -31
- package/dist/gateway/server/index.js +0 -1233
- package/dist/gateway/server/index.js.map +0 -1
- package/gateway/proto/lp_rpc.proto +0 -542
package/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
|
-
**
|
|
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
|
|
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-
|
|
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-
|
|
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
|
package/dist/config.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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.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,
|
|
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,
|
|
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,
|
|
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
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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/** 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/**
|
|
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/** 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/**
|
|
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"]}
|
package/dist/device.cjs.map
CHANGED
|
@@ -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
package/dist/device.d.ts
CHANGED