@pymthouse/builder-sdk 0.1.0 → 0.3.0
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 +66 -0
- package/dist/{client-BroVFyIy.d.ts → client-BHfjDvIe.d.ts} +49 -1
- package/dist/{client-BhC1YhB1.d.cts → client-CvhJEhjV.d.cts} +49 -1
- package/dist/config.cjs +59 -3
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +8 -1
- package/dist/config.d.ts +8 -1
- package/dist/config.js +57 -4
- package/dist/config.js.map +1 -1
- package/dist/device-initiate.cjs +1 -1
- package/dist/device-initiate.cjs.map +1 -1
- package/dist/device-initiate.js +1 -1
- package/dist/device-initiate.js.map +1 -1
- package/dist/device.cjs +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 +1 -1
- package/dist/device.js.map +1 -1
- package/dist/env.cjs +794 -36
- 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 +794 -36
- package/dist/env.js.map +1 -1
- package/dist/gateway/client/index.cjs +492 -0
- package/dist/gateway/client/index.cjs.map +1 -0
- package/dist/gateway/client/index.d.cts +63 -0
- package/dist/gateway/client/index.d.ts +63 -0
- package/dist/gateway/client/index.js +489 -0
- package/dist/gateway/client/index.js.map +1 -0
- package/dist/gateway/index.cjs +16 -0
- package/dist/gateway/index.cjs.map +1 -0
- package/dist/gateway/index.d.cts +52 -0
- package/dist/gateway/index.d.ts +52 -0
- package/dist/gateway/index.js +10 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/gateway/server/index.cjs +1248 -0
- package/dist/gateway/server/index.cjs.map +1 -0
- package/dist/gateway/server/index.d.cts +31 -0
- package/dist/gateway/server/index.d.ts +31 -0
- package/dist/gateway/server/index.js +1233 -0
- package/dist/gateway/server/index.js.map +1 -0
- package/dist/index.cjs +1075 -186
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +1042 -163
- package/dist/index.js.map +1 -1
- package/dist/ingest-B3Yi8Tb1.d.cts +271 -0
- package/dist/ingest-DoKJTWU9.d.ts +271 -0
- package/dist/plan-pricing.cjs +108 -0
- package/dist/plan-pricing.cjs.map +1 -0
- package/dist/plan-pricing.d.cts +15 -0
- package/dist/plan-pricing.d.ts +15 -0
- package/dist/plan-pricing.js +98 -0
- package/dist/plan-pricing.js.map +1 -0
- package/dist/signer/server.cjs +1366 -0
- package/dist/signer/server.cjs.map +1 -0
- package/dist/signer/server.d.cts +73 -0
- package/dist/signer/server.d.ts +73 -0
- package/dist/signer/server.js +1331 -0
- package/dist/signer/server.js.map +1 -0
- package/dist/tokens.d.cts +1 -1
- package/dist/tokens.d.ts +1 -1
- package/dist/types-_R1AwEZp.d.cts +343 -0
- package/dist/types-_R1AwEZp.d.ts +343 -0
- package/dist/verify.cjs +1 -1
- 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 +1 -1
- package/dist/verify.js.map +1 -1
- package/gateway/proto/lp_rpc.proto +542 -0
- package/package.json +42 -1
- package/dist/types-rKzVXvMu.d.cts +0 -196
- package/dist/types-rKzVXvMu.d.ts +0 -196
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ OAuth/OIDC protocol calls use **[oauth4webapi](https://github.com/panva/oauth4we
|
|
|
12
12
|
pnpm add @pymthouse/builder-sdk
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
Maintainers: see [docs/RELEASING.md](docs/RELEASING.md) for trusted publishing and re-running failed releases.
|
|
16
|
+
|
|
15
17
|
## Quick start
|
|
16
18
|
|
|
17
19
|
```ts
|
|
@@ -66,6 +68,48 @@ const signerSession = await client.mintUserSignerSessionToken({
|
|
|
66
68
|
For advanced flows that already have a user JWT, call
|
|
67
69
|
`exchangeForSignerSession({ userJwt })` directly.
|
|
68
70
|
|
|
71
|
+
### Dashboard API keys (long-lived `pmth_*`)
|
|
72
|
+
|
|
73
|
+
Create a key in the Dashboard **API keys** page, then exchange it for a signer
|
|
74
|
+
session without repeating device login:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const session = await client.exchangeApiKeyForSignerSession({
|
|
78
|
+
apiKey: process.env.PMTH_API_KEY!,
|
|
79
|
+
facadeUrl: process.env.DASHBOARD_ORIGIN!, // e.g. https://dashboard.example.com
|
|
80
|
+
scope: "sign:job",
|
|
81
|
+
});
|
|
82
|
+
// session.access_token — opaque signer bearer for discovery / gateway
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
See `examples/stream-with-api-key.mjs` for a minimal Node script.
|
|
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
|
+
|
|
69
113
|
Integrators can use the higher-level workflow helpers:
|
|
70
114
|
|
|
71
115
|
```ts
|
|
@@ -128,6 +172,28 @@ const summary = summarizeUsageForExternalUser(usage, externalUserId);
|
|
|
128
172
|
// summary.requestCount, summary.feeWei (wei string)
|
|
129
173
|
```
|
|
130
174
|
|
|
175
|
+
## Billing: plans, retail usage, signed-ticket ingest
|
|
176
|
+
|
|
177
|
+
**Plans (apiVersion=2):** `listBillingProducts({ apiVersion: "2" })` returns `BillingProduct[]` with capability pricing and sync status. `syncBillingProduct(planId)` POSTs to OpenMeter.
|
|
178
|
+
|
|
179
|
+
**Retail estimates:** `getUsage({ includeRetail: true, groupBy: "pipeline_model" })` adds `endUserBillableUsdMicros` / fiat rows when the active plan has retail rates.
|
|
180
|
+
|
|
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`.
|
|
182
|
+
|
|
183
|
+
**Routing:** `getSignerRouting()` returns `signerApiUrl`, `remoteDmzUrl`, `meteringMode`, and pattern hints for hosted vs platform-ingest vs BYO OpenMeter.
|
|
184
|
+
|
|
185
|
+
**Allowances (OpenMeter):** Trial and manual USD micros allowance use OpenMeter entitlements — not a Postgres wei ledger.
|
|
186
|
+
|
|
187
|
+
| Method | SDK | HTTP |
|
|
188
|
+
|--------|-----|------|
|
|
189
|
+
| Read balance | `getUsageBalance(externalUserId)` | `GET .../usage/balance?externalUserId=` |
|
|
190
|
+
| Read allowance detail | `getUserAllowances(externalUserId)` | `GET .../users/{id}/allowances` |
|
|
191
|
+
| Top-up grant | `grantUserAllowance(externalUserId, { amountUsdMicros, source })` | `POST .../users/{id}/allowances` |
|
|
192
|
+
|
|
193
|
+
`grantUserCredits` / `getUserCredits` remain as **deprecated** aliases that call the allowances / balance endpoints. `POST .../users/{id}/credits` was removed from PymtHouse (the route may still re-export allowances temporarily).
|
|
194
|
+
|
|
195
|
+
**Plan pricing helpers:** `markupPercentToRetailRateUsd`, `applyRetailRateToNetworkMicros` (exported from the main entry).
|
|
196
|
+
|
|
131
197
|
## Usage API: pipeline/model grouping
|
|
132
198
|
|
|
133
199
|
When `getUsage({ groupBy: "pipeline_model", startDate, endDate, userId })` returns
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SignerSessionToken } from './tokens.js';
|
|
2
|
-
import {
|
|
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';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Normalize RFC 8628 user codes for comparison and resource URIs (uppercase, strip separators).
|
|
@@ -31,6 +31,22 @@ declare class PmtHouseClient {
|
|
|
31
31
|
success: boolean;
|
|
32
32
|
}>;
|
|
33
33
|
mintUserAccessToken(input: MintUserAccessTokenInput): Promise<MintUserAccessTokenResponse>;
|
|
34
|
+
/**
|
|
35
|
+
* Exchange a long-lived dashboard API key (`pmth_*`) for a short-lived user JWT.
|
|
36
|
+
*/
|
|
37
|
+
exchangeApiKeyForUserAccessToken(input: {
|
|
38
|
+
apiKey: string;
|
|
39
|
+
scope?: string;
|
|
40
|
+
}): Promise<MintUserAccessTokenResponse>;
|
|
41
|
+
/**
|
|
42
|
+
* Exchange a dashboard API key for a signer session via a trusted facade (recommended)
|
|
43
|
+
* or directly when M2M credentials are available on this client.
|
|
44
|
+
*/
|
|
45
|
+
exchangeApiKeyForSignerSession(input: {
|
|
46
|
+
apiKey: string;
|
|
47
|
+
scope?: string;
|
|
48
|
+
facadeUrl?: string;
|
|
49
|
+
}): Promise<TokenExchangeResponse>;
|
|
34
50
|
completeDeviceApproval(input: DeviceApprovalInput): Promise<TokenExchangeResponse>;
|
|
35
51
|
issueMachineAccessToken(scope?: string): Promise<ClientCredentialsTokenResponse>;
|
|
36
52
|
exchangeForSignerSession(input: {
|
|
@@ -49,6 +65,38 @@ declare class PmtHouseClient {
|
|
|
49
65
|
/**
|
|
50
66
|
* Session-scoped usage for one `externalUserId`: user rollup plus merged pipeline/model breakdown.
|
|
51
67
|
*/
|
|
68
|
+
ingestSignedTicket(ticket: SignedTicketIngestInput): Promise<SignedTicketIngestResult>;
|
|
69
|
+
ingestSignedTickets(tickets: SignedTicketIngestInput[]): Promise<{
|
|
70
|
+
results: Array<SignedTicketIngestResult & {
|
|
71
|
+
requestId?: string;
|
|
72
|
+
ok?: boolean;
|
|
73
|
+
}>;
|
|
74
|
+
}>;
|
|
75
|
+
getSignerRouting(): Promise<SignerRoutingResponse>;
|
|
76
|
+
listBillingProducts(): Promise<ListBillingProductsResult>;
|
|
77
|
+
syncBillingProduct(planId: string): Promise<PlanSyncResult>;
|
|
78
|
+
getUsageBalance(externalUserId: string): Promise<UsageBalanceResponse>;
|
|
79
|
+
getUserAllowances(externalUserId: string): Promise<UserAllowancesResponse>;
|
|
80
|
+
grantUserAllowance(externalUserId: string, input: UserAllowanceGrantInput): Promise<UserAllowancesResponse & {
|
|
81
|
+
grantedUsdMicros?: string;
|
|
82
|
+
featureKey?: string;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* @deprecated Removed from PymtHouse — use {@link getUsageBalance} or {@link getUserAllowances}.
|
|
86
|
+
*/
|
|
87
|
+
getUserCredits(externalUserId: string): Promise<UsageBalanceResponse>;
|
|
88
|
+
/**
|
|
89
|
+
* @deprecated Removed from PymtHouse — use {@link grantUserAllowance} (`POST .../allowances`).
|
|
90
|
+
*/
|
|
91
|
+
grantUserCredits(externalUserId: string, input: {
|
|
92
|
+
amountUsdMicros: string;
|
|
93
|
+
source?: GrantSource;
|
|
94
|
+
featureKey?: string;
|
|
95
|
+
}): Promise<UsageBalanceResponse & {
|
|
96
|
+
grantedUsdMicros?: string;
|
|
97
|
+
featureKey?: string;
|
|
98
|
+
}>;
|
|
99
|
+
getUserSubscription(externalUserId: string): Promise<UserSubscriptionResponse>;
|
|
52
100
|
fetchUsageForExternalUser(input: {
|
|
53
101
|
externalUserId: string;
|
|
54
102
|
startDate: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SignerSessionToken } from './tokens.cjs';
|
|
2
|
-
import {
|
|
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';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Normalize RFC 8628 user codes for comparison and resource URIs (uppercase, strip separators).
|
|
@@ -31,6 +31,22 @@ declare class PmtHouseClient {
|
|
|
31
31
|
success: boolean;
|
|
32
32
|
}>;
|
|
33
33
|
mintUserAccessToken(input: MintUserAccessTokenInput): Promise<MintUserAccessTokenResponse>;
|
|
34
|
+
/**
|
|
35
|
+
* Exchange a long-lived dashboard API key (`pmth_*`) for a short-lived user JWT.
|
|
36
|
+
*/
|
|
37
|
+
exchangeApiKeyForUserAccessToken(input: {
|
|
38
|
+
apiKey: string;
|
|
39
|
+
scope?: string;
|
|
40
|
+
}): Promise<MintUserAccessTokenResponse>;
|
|
41
|
+
/**
|
|
42
|
+
* Exchange a dashboard API key for a signer session via a trusted facade (recommended)
|
|
43
|
+
* or directly when M2M credentials are available on this client.
|
|
44
|
+
*/
|
|
45
|
+
exchangeApiKeyForSignerSession(input: {
|
|
46
|
+
apiKey: string;
|
|
47
|
+
scope?: string;
|
|
48
|
+
facadeUrl?: string;
|
|
49
|
+
}): Promise<TokenExchangeResponse>;
|
|
34
50
|
completeDeviceApproval(input: DeviceApprovalInput): Promise<TokenExchangeResponse>;
|
|
35
51
|
issueMachineAccessToken(scope?: string): Promise<ClientCredentialsTokenResponse>;
|
|
36
52
|
exchangeForSignerSession(input: {
|
|
@@ -49,6 +65,38 @@ declare class PmtHouseClient {
|
|
|
49
65
|
/**
|
|
50
66
|
* Session-scoped usage for one `externalUserId`: user rollup plus merged pipeline/model breakdown.
|
|
51
67
|
*/
|
|
68
|
+
ingestSignedTicket(ticket: SignedTicketIngestInput): Promise<SignedTicketIngestResult>;
|
|
69
|
+
ingestSignedTickets(tickets: SignedTicketIngestInput[]): Promise<{
|
|
70
|
+
results: Array<SignedTicketIngestResult & {
|
|
71
|
+
requestId?: string;
|
|
72
|
+
ok?: boolean;
|
|
73
|
+
}>;
|
|
74
|
+
}>;
|
|
75
|
+
getSignerRouting(): Promise<SignerRoutingResponse>;
|
|
76
|
+
listBillingProducts(): Promise<ListBillingProductsResult>;
|
|
77
|
+
syncBillingProduct(planId: string): Promise<PlanSyncResult>;
|
|
78
|
+
getUsageBalance(externalUserId: string): Promise<UsageBalanceResponse>;
|
|
79
|
+
getUserAllowances(externalUserId: string): Promise<UserAllowancesResponse>;
|
|
80
|
+
grantUserAllowance(externalUserId: string, input: UserAllowanceGrantInput): Promise<UserAllowancesResponse & {
|
|
81
|
+
grantedUsdMicros?: string;
|
|
82
|
+
featureKey?: string;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* @deprecated Removed from PymtHouse — use {@link getUsageBalance} or {@link getUserAllowances}.
|
|
86
|
+
*/
|
|
87
|
+
getUserCredits(externalUserId: string): Promise<UsageBalanceResponse>;
|
|
88
|
+
/**
|
|
89
|
+
* @deprecated Removed from PymtHouse — use {@link grantUserAllowance} (`POST .../allowances`).
|
|
90
|
+
*/
|
|
91
|
+
grantUserCredits(externalUserId: string, input: {
|
|
92
|
+
amountUsdMicros: string;
|
|
93
|
+
source?: GrantSource;
|
|
94
|
+
featureKey?: string;
|
|
95
|
+
}): Promise<UsageBalanceResponse & {
|
|
96
|
+
grantedUsdMicros?: string;
|
|
97
|
+
featureKey?: string;
|
|
98
|
+
}>;
|
|
99
|
+
getUserSubscription(externalUserId: string): Promise<UserSubscriptionResponse>;
|
|
52
100
|
fetchUsageForExternalUser(input: {
|
|
53
101
|
externalUserId: string;
|
|
54
102
|
startDate: string;
|
package/dist/config.cjs
CHANGED
|
@@ -3,11 +3,65 @@
|
|
|
3
3
|
// src/string-utils.ts
|
|
4
4
|
function stripTrailingSlashes(value) {
|
|
5
5
|
let end = value.length;
|
|
6
|
-
while (end > 0 && value.
|
|
6
|
+
while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {
|
|
7
7
|
end--;
|
|
8
8
|
}
|
|
9
9
|
return value.slice(0, end);
|
|
10
10
|
}
|
|
11
|
+
function endsWithIgnoreCase(value, suffix) {
|
|
12
|
+
if (suffix.length > value.length) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const start = value.length - suffix.length;
|
|
16
|
+
for (let i = 0; i < suffix.length; i++) {
|
|
17
|
+
const a = value.codePointAt(start + i) ?? 0;
|
|
18
|
+
const b = suffix.codePointAt(i) ?? 0;
|
|
19
|
+
if (a !== b && (a | 32) !== (b | 32)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
function stripSuffixIgnoreCase(value, suffix) {
|
|
26
|
+
return endsWithIgnoreCase(value, suffix) ? value.slice(0, value.length - suffix.length) : value;
|
|
27
|
+
}
|
|
28
|
+
function stripOidcPathSuffix(issuerUrl) {
|
|
29
|
+
let base = stripTrailingSlashes(issuerUrl.trim());
|
|
30
|
+
base = stripSuffixIgnoreCase(base, "/oidc");
|
|
31
|
+
return stripTrailingSlashes(base);
|
|
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
|
+
function parseHttpOrigin(raw, fallback) {
|
|
47
|
+
const trimmed = (raw ?? fallback).trim();
|
|
48
|
+
let parsed;
|
|
49
|
+
try {
|
|
50
|
+
parsed = new URL(trimmed);
|
|
51
|
+
} catch {
|
|
52
|
+
throw new TypeError("Origin must be a valid http(s) URL");
|
|
53
|
+
}
|
|
54
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
55
|
+
throw new TypeError("Origin must use http or https");
|
|
56
|
+
}
|
|
57
|
+
return parsed.origin;
|
|
58
|
+
}
|
|
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
|
+
}
|
|
11
65
|
|
|
12
66
|
// src/config.ts
|
|
13
67
|
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.";
|
|
@@ -48,19 +102,21 @@ function isPymthouseConfigured() {
|
|
|
48
102
|
return readPymthouseEnv() !== null;
|
|
49
103
|
}
|
|
50
104
|
function getBuilderApiV1BaseFromIssuerUrl(issuerUrl) {
|
|
51
|
-
|
|
52
|
-
return noTrail.replace(/\/oidc\/?$/i, "");
|
|
105
|
+
return stripOidcPathSuffix(issuerUrl);
|
|
53
106
|
}
|
|
54
107
|
function getPymthouseIssuerOrigin(issuerUrl) {
|
|
55
108
|
return new URL(stripTrailingSlashes(issuerUrl.trim())).origin;
|
|
56
109
|
}
|
|
57
110
|
|
|
58
111
|
exports.PYMTHOUSE_NOT_CONFIGURED_MESSAGE = PYMTHOUSE_NOT_CONFIGURED_MESSAGE;
|
|
112
|
+
exports.buildGatewaySessionDeleteUrl = buildGatewaySessionDeleteUrl;
|
|
59
113
|
exports.getBuilderApiV1BaseFromIssuerUrl = getBuilderApiV1BaseFromIssuerUrl;
|
|
60
114
|
exports.getPymthouseIssuerOrigin = getPymthouseIssuerOrigin;
|
|
61
115
|
exports.getPymthouseIssuerUrlFromEnv = getPymthouseIssuerUrlFromEnv;
|
|
62
116
|
exports.getPymthousePublicClientIdFromEnv = getPymthousePublicClientIdFromEnv;
|
|
63
117
|
exports.isPymthouseConfigured = isPymthouseConfigured;
|
|
118
|
+
exports.isSafePathSegment = isSafePathSegment;
|
|
119
|
+
exports.parseHttpOrigin = parseHttpOrigin;
|
|
64
120
|
exports.readPymthouseEnv = readPymthouseEnv;
|
|
65
121
|
//# sourceMappingURL=config.cjs.map
|
|
66
122
|
//# 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,
|
|
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"]}
|
package/dist/config.d.cts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/** Validate gateway session ids before embedding in request URLs. */
|
|
2
|
+
declare function isSafePathSegment(value: unknown): value is string;
|
|
3
|
+
/** Parse and validate an http(s) facade origin (no path). */
|
|
4
|
+
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
|
+
|
|
1
8
|
/** Operator hint when Builder / Usage cannot run. */
|
|
2
9
|
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.";
|
|
3
10
|
interface PymthouseEnvConfig {
|
|
@@ -19,4 +26,4 @@ declare function getBuilderApiV1BaseFromIssuerUrl(issuerUrl: string): string;
|
|
|
19
26
|
/** Origin of the OIDC issuer host (e.g. `https://pymthouse.com`). */
|
|
20
27
|
declare function getPymthouseIssuerOrigin(issuerUrl: string): string;
|
|
21
28
|
|
|
22
|
-
export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, type PymthouseEnvConfig, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, readPymthouseEnv };
|
|
29
|
+
export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, type PymthouseEnvConfig, buildGatewaySessionDeleteUrl, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, isSafePathSegment, parseHttpOrigin, readPymthouseEnv };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/** Validate gateway session ids before embedding in request URLs. */
|
|
2
|
+
declare function isSafePathSegment(value: unknown): value is string;
|
|
3
|
+
/** Parse and validate an http(s) facade origin (no path). */
|
|
4
|
+
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
|
+
|
|
1
8
|
/** Operator hint when Builder / Usage cannot run. */
|
|
2
9
|
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.";
|
|
3
10
|
interface PymthouseEnvConfig {
|
|
@@ -19,4 +26,4 @@ declare function getBuilderApiV1BaseFromIssuerUrl(issuerUrl: string): string;
|
|
|
19
26
|
/** Origin of the OIDC issuer host (e.g. `https://pymthouse.com`). */
|
|
20
27
|
declare function getPymthouseIssuerOrigin(issuerUrl: string): string;
|
|
21
28
|
|
|
22
|
-
export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, type PymthouseEnvConfig, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, readPymthouseEnv };
|
|
29
|
+
export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, type PymthouseEnvConfig, buildGatewaySessionDeleteUrl, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, isSafePathSegment, parseHttpOrigin, readPymthouseEnv };
|
package/dist/config.js
CHANGED
|
@@ -1,11 +1,65 @@
|
|
|
1
1
|
// src/string-utils.ts
|
|
2
2
|
function stripTrailingSlashes(value) {
|
|
3
3
|
let end = value.length;
|
|
4
|
-
while (end > 0 && value.
|
|
4
|
+
while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {
|
|
5
5
|
end--;
|
|
6
6
|
}
|
|
7
7
|
return value.slice(0, end);
|
|
8
8
|
}
|
|
9
|
+
function endsWithIgnoreCase(value, suffix) {
|
|
10
|
+
if (suffix.length > value.length) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const start = value.length - suffix.length;
|
|
14
|
+
for (let i = 0; i < suffix.length; i++) {
|
|
15
|
+
const a = value.codePointAt(start + i) ?? 0;
|
|
16
|
+
const b = suffix.codePointAt(i) ?? 0;
|
|
17
|
+
if (a !== b && (a | 32) !== (b | 32)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
function stripSuffixIgnoreCase(value, suffix) {
|
|
24
|
+
return endsWithIgnoreCase(value, suffix) ? value.slice(0, value.length - suffix.length) : value;
|
|
25
|
+
}
|
|
26
|
+
function stripOidcPathSuffix(issuerUrl) {
|
|
27
|
+
let base = stripTrailingSlashes(issuerUrl.trim());
|
|
28
|
+
base = stripSuffixIgnoreCase(base, "/oidc");
|
|
29
|
+
return stripTrailingSlashes(base);
|
|
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
|
+
function parseHttpOrigin(raw, fallback) {
|
|
45
|
+
const trimmed = (raw ?? fallback).trim();
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = new URL(trimmed);
|
|
49
|
+
} catch {
|
|
50
|
+
throw new TypeError("Origin must be a valid http(s) URL");
|
|
51
|
+
}
|
|
52
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
53
|
+
throw new TypeError("Origin must use http or https");
|
|
54
|
+
}
|
|
55
|
+
return parsed.origin;
|
|
56
|
+
}
|
|
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
|
+
}
|
|
9
63
|
|
|
10
64
|
// src/config.ts
|
|
11
65
|
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.";
|
|
@@ -46,13 +100,12 @@ function isPymthouseConfigured() {
|
|
|
46
100
|
return readPymthouseEnv() !== null;
|
|
47
101
|
}
|
|
48
102
|
function getBuilderApiV1BaseFromIssuerUrl(issuerUrl) {
|
|
49
|
-
|
|
50
|
-
return noTrail.replace(/\/oidc\/?$/i, "");
|
|
103
|
+
return stripOidcPathSuffix(issuerUrl);
|
|
51
104
|
}
|
|
52
105
|
function getPymthouseIssuerOrigin(issuerUrl) {
|
|
53
106
|
return new URL(stripTrailingSlashes(issuerUrl.trim())).origin;
|
|
54
107
|
}
|
|
55
108
|
|
|
56
|
-
export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, readPymthouseEnv };
|
|
109
|
+
export { PYMTHOUSE_NOT_CONFIGURED_MESSAGE, buildGatewaySessionDeleteUrl, getBuilderApiV1BaseFromIssuerUrl, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, isPymthouseConfigured, isSafePathSegment, parseHttpOrigin, readPymthouseEnv };
|
|
57
110
|
//# sourceMappingURL=config.js.map
|
|
58
111
|
//# 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,
|
|
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"]}
|
package/dist/device-initiate.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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"]}
|
package/dist/device-initiate.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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"]}
|
package/dist/device.cjs
CHANGED
|
@@ -25,7 +25,7 @@ var PmtHouseError = class extends Error {
|
|
|
25
25
|
// src/string-utils.ts
|
|
26
26
|
function stripTrailingSlashes(value) {
|
|
27
27
|
let end = value.length;
|
|
28
|
-
while (end > 0 && value.
|
|
28
|
+
while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {
|
|
29
29
|
end--;
|
|
30
30
|
}
|
|
31
31
|
return value.slice(0, end);
|