@pymthouse/builder-sdk 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/device.cjs +246 -0
- package/dist/device.cjs.map +1 -0
- package/dist/device.d.cts +27 -0
- package/dist/device.d.ts +27 -0
- package/dist/device.js +244 -0
- package/dist/device.js.map +1 -0
- package/dist/env-4YmzarGJ.d.ts +68 -0
- package/dist/env-CZczUMzR.d.cts +68 -0
- package/dist/env.cjs +615 -0
- package/dist/env.cjs.map +1 -0
- package/dist/env.d.cts +2 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.js +612 -0
- package/dist/env.js.map +1 -0
- package/dist/format.cjs +27 -0
- package/dist/format.cjs.map +1 -0
- package/dist/format.d.cts +4 -0
- package/dist/format.d.ts +4 -0
- package/dist/format.js +24 -0
- package/dist/format.js.map +1 -0
- package/dist/index.cjs +685 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +670 -0
- package/dist/index.js.map +1 -0
- package/dist/types-W9PJAspR.d.cts +136 -0
- package/dist/types-W9PJAspR.d.ts +136 -0
- package/dist/verify.cjs +181 -0
- package/dist/verify.cjs.map +1 -0
- package/dist/verify.d.cts +18 -0
- package/dist/verify.d.ts +18 -0
- package/dist/verify.js +179 -0
- package/dist/verify.js.map +1 -0
- package/package.json +86 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PymtHouse
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# @pymthouse/builder-sdk
|
|
2
|
+
|
|
3
|
+
Source repository: [pymthouse/builder-sdk](https://github.com/pymthouse/builder-sdk). The npm package name is `@pymthouse/builder-sdk`.
|
|
4
|
+
|
|
5
|
+
TypeScript client for the **PymtHouse Builder API**, **Usage API**, and **OIDC issuer** surfaces.
|
|
6
|
+
|
|
7
|
+
OAuth/OIDC protocol calls use **[oauth4webapi](https://github.com/panva/oauth4webapi)** (OpenID-certified relying-party implementation). PymtHouse-specific REST paths and helpers live in `PmtHouseClient`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @pymthouse/builder-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { PmtHouseClient } from "@pymthouse/builder-sdk";
|
|
19
|
+
import {
|
|
20
|
+
createPmtHouseClientFromEnv,
|
|
21
|
+
getPymthouseBaseUrl,
|
|
22
|
+
} from "@pymthouse/builder-sdk/env";
|
|
23
|
+
|
|
24
|
+
const client = createPmtHouseClientFromEnv();
|
|
25
|
+
const base = getPymthouseBaseUrl();
|
|
26
|
+
const discovery = await client.getDiscovery();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or construct explicitly:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { PmtHouseClient } from "@pymthouse/builder-sdk";
|
|
33
|
+
|
|
34
|
+
const client = new PmtHouseClient({
|
|
35
|
+
issuerUrl: process.env.PYMTHOUSE_ISSUER_URL!,
|
|
36
|
+
publicClientId: process.env.PYMTHOUSE_PUBLIC_CLIENT_ID!,
|
|
37
|
+
m2mClientId: process.env.PYMTHOUSE_M2M_CLIENT_ID!,
|
|
38
|
+
m2mClientSecret: process.env.PYMTHOUSE_M2M_CLIENT_SECRET!,
|
|
39
|
+
allowInsecureHttp: process.env.PYMTHOUSE_ISSUER_URL?.startsWith("http:"),
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## User tokens: short-lived JWT or long-lived signer session
|
|
44
|
+
|
|
45
|
+
Use `mintUserAccessToken()` when your backend needs the short-lived
|
|
46
|
+
Builder-minted user JWT directly:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
const userJwt = await client.mintUserAccessToken({
|
|
50
|
+
externalUserId: "naap-user-123",
|
|
51
|
+
scope: "sign:job",
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Use `mintUserSignerSessionToken()` when you want the user-facing opaque
|
|
56
|
+
`pmth_...` signer session. This first mints the short-lived user JWT, then
|
|
57
|
+
performs the RFC 8693 token exchange with the confidential M2M client:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const signerSession = await client.mintUserSignerSessionToken({
|
|
61
|
+
externalUserId: "naap-user-123",
|
|
62
|
+
scope: "sign:job",
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
For advanced flows that already have a user JWT, call
|
|
67
|
+
`exchangeForSignerSession({ userJwt })` directly.
|
|
68
|
+
|
|
69
|
+
## Subpath exports
|
|
70
|
+
|
|
71
|
+
| Import | Purpose |
|
|
72
|
+
|--------|---------|
|
|
73
|
+
| `@pymthouse/builder-sdk` | `PmtHouseClient`, discovery cache, errors, usage aggregation helpers |
|
|
74
|
+
| `@pymthouse/builder-sdk/format` | Wei formatting for Usage API |
|
|
75
|
+
| `@pymthouse/builder-sdk/env` | `createPmtHouseClientFromEnv`, `getPymthouseBaseUrl` |
|
|
76
|
+
| `@pymthouse/builder-sdk/device` | RFC 8628 `pollDeviceToken` |
|
|
77
|
+
| `@pymthouse/builder-sdk/verify` | RFC 9068 `verifyJwt` |
|
|
78
|
+
|
|
79
|
+
## Usage API: duplicate `byUser` rows
|
|
80
|
+
|
|
81
|
+
When `getUsage({ groupBy: "user" })` returns multiple `byUser` rows with the same
|
|
82
|
+
`externalUserId`, sum them with `summarizeUsageForExternalUser` (or
|
|
83
|
+
`aggregateUsageByExternalUserId` on `byUser` alone):
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { summarizeUsageForExternalUser } from "@pymthouse/builder-sdk";
|
|
87
|
+
|
|
88
|
+
const usage = await client.getUsage({ groupBy: "user", startDate, endDate });
|
|
89
|
+
const summary = summarizeUsageForExternalUser(usage, externalUserId);
|
|
90
|
+
// summary.requestCount, summary.feeWei (wei string)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Usage API: pipeline/model grouping
|
|
94
|
+
|
|
95
|
+
When `getUsage({ groupBy: "pipeline_model", startDate, endDate, userId })` returns
|
|
96
|
+
`byPipelineModel`, use `listUsageByPipelineModel` for a stable-sorted copy. Pass
|
|
97
|
+
the optional `gatewayRequestId` filter to scope results to a single upstream
|
|
98
|
+
gateway request:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { listUsageByPipelineModel } from "@pymthouse/builder-sdk";
|
|
102
|
+
|
|
103
|
+
const usage = await client.getUsage({
|
|
104
|
+
groupBy: "pipeline_model",
|
|
105
|
+
startDate,
|
|
106
|
+
endDate,
|
|
107
|
+
userId: internalUserId,
|
|
108
|
+
gatewayRequestId, // optional: filter to a single gateway request
|
|
109
|
+
});
|
|
110
|
+
const rows = listUsageByPipelineModel(usage);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Documentation
|
|
114
|
+
|
|
115
|
+
Authoritative API behavior: [PymtHouse `docs/builder-api.md`](https://github.com/pymthouse/pymthouse/blob/main/docs/builder-api.md).
|
|
116
|
+
|
|
117
|
+
## Server-only: `createPmtHouseClientFromEnv` / `@pymthouse/builder-sdk/env`
|
|
118
|
+
|
|
119
|
+
M2M credentials are **confidential**. The `env` entry point:
|
|
120
|
+
|
|
121
|
+
1. **Throws as soon as the module loads in a browser** (detects `globalThis.window`), so a mistaken client import fails immediately instead of silently bundling secrets.
|
|
122
|
+
2. Does **not** stop someone from putting `m2mClientSecret` in `new PmtHouseClient({ ... })` in client code—you still must not do that.
|
|
123
|
+
|
|
124
|
+
**Next.js — build-time guard (optional):** in a file that is only used from the server, add the official marker so the bundler errors instead of shipping the module to the client:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
// e.g. lib/pymthouse-server.ts
|
|
128
|
+
import "server-only";
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
createPmtHouseClientFromEnv,
|
|
132
|
+
getPymthouseBaseUrl,
|
|
133
|
+
} from "@pymthouse/builder-sdk/env";
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Import `createPmtHouseClientFromEnv` only from that wrapper (or from Route Handlers / Server Actions directly).
|
|
137
|
+
|
|
138
|
+
## Next.js (monorepo) consumption
|
|
139
|
+
|
|
140
|
+
When the SDK lives as a sibling folder (e.g. `../node-pymt-sdk`), enable `experimental.externalDir` in `next.config` and re-export from a small `lib` shim that points at `../../node-pymt-sdk` (see the `website` app in this org). Published installs from npm use the package name directly without shims.
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
package/dist/device.cjs
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var oauth4webapi = require('oauth4webapi');
|
|
4
|
+
|
|
5
|
+
// src/device.ts
|
|
6
|
+
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
var PmtHouseError = class extends Error {
|
|
9
|
+
status;
|
|
10
|
+
code;
|
|
11
|
+
details;
|
|
12
|
+
constructor(message, {
|
|
13
|
+
status = 500,
|
|
14
|
+
code = "pymthouse_error",
|
|
15
|
+
details
|
|
16
|
+
} = {}) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "PmtHouseError";
|
|
19
|
+
this.status = status;
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.details = details;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/string-utils.ts
|
|
26
|
+
function stripTrailingSlashes(value) {
|
|
27
|
+
let end = value.length;
|
|
28
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
29
|
+
end--;
|
|
30
|
+
}
|
|
31
|
+
return value.slice(0, end);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/discovery.ts
|
|
35
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
36
|
+
var discoveryCache = /* @__PURE__ */ new Map();
|
|
37
|
+
function normalizedIssuerKey(issuerUrl) {
|
|
38
|
+
return stripTrailingSlashes(issuerUrl);
|
|
39
|
+
}
|
|
40
|
+
async function loadAuthorizationServer(issuerUrl, fetchImpl, options = {}) {
|
|
41
|
+
const key = normalizedIssuerKey(issuerUrl);
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const cached = discoveryCache.get(key);
|
|
44
|
+
if (!options.force && cached && now - cached.fetchedAt < CACHE_TTL_MS) {
|
|
45
|
+
return cached.as;
|
|
46
|
+
}
|
|
47
|
+
const issuerIdentifier = new URL(key);
|
|
48
|
+
const discoveryOpts = {
|
|
49
|
+
algorithm: "oidc",
|
|
50
|
+
[oauth4webapi.customFetch]: fetchImpl
|
|
51
|
+
};
|
|
52
|
+
if (options.allowInsecureHttp) {
|
|
53
|
+
discoveryOpts[oauth4webapi.allowInsecureRequests] = true;
|
|
54
|
+
}
|
|
55
|
+
let response;
|
|
56
|
+
try {
|
|
57
|
+
response = await oauth4webapi.discoveryRequest(issuerIdentifier, discoveryOpts);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
throw mapDiscoveryNetworkError(e);
|
|
60
|
+
}
|
|
61
|
+
let as;
|
|
62
|
+
try {
|
|
63
|
+
as = await oauth4webapi.processDiscoveryResponse(issuerIdentifier, response);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
throw mapOAuthDiscoveryError(e);
|
|
66
|
+
}
|
|
67
|
+
discoveryCache.set(key, { as, fetchedAt: now });
|
|
68
|
+
return as;
|
|
69
|
+
}
|
|
70
|
+
function mapOAuthDiscoveryError(error) {
|
|
71
|
+
if (error instanceof PmtHouseError) {
|
|
72
|
+
return error;
|
|
73
|
+
}
|
|
74
|
+
if (error instanceof Error) {
|
|
75
|
+
return new PmtHouseError(error.message, {
|
|
76
|
+
status: 500,
|
|
77
|
+
code: "oidc_discovery_invalid",
|
|
78
|
+
details: { cause: error.cause }
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return new PmtHouseError("OIDC discovery failed", {
|
|
82
|
+
status: 500,
|
|
83
|
+
code: "oidc_discovery_invalid"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function mapDiscoveryNetworkError(error) {
|
|
87
|
+
if (error instanceof PmtHouseError) {
|
|
88
|
+
return error;
|
|
89
|
+
}
|
|
90
|
+
if (error instanceof Error) {
|
|
91
|
+
return new PmtHouseError(`Failed to load OIDC discovery: ${error.message}`, {
|
|
92
|
+
status: 502,
|
|
93
|
+
code: "oidc_discovery_failed"
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return new PmtHouseError("Failed to load OIDC discovery", {
|
|
97
|
+
status: 502,
|
|
98
|
+
code: "oidc_discovery_failed"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function mapOAuthError(error) {
|
|
102
|
+
if (error instanceof PmtHouseError) {
|
|
103
|
+
return error;
|
|
104
|
+
}
|
|
105
|
+
if (error instanceof oauth4webapi.ResponseBodyError) {
|
|
106
|
+
const cause = error.cause;
|
|
107
|
+
const description = typeof error.error_description === "string" ? error.error_description : error.message;
|
|
108
|
+
const details = { ...cause };
|
|
109
|
+
if (typeof cause.error_uri === "string") {
|
|
110
|
+
details.error_uri = cause.error_uri;
|
|
111
|
+
}
|
|
112
|
+
return new PmtHouseError(description, {
|
|
113
|
+
status: error.status,
|
|
114
|
+
code: error.error,
|
|
115
|
+
details
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (error instanceof oauth4webapi.OperationProcessingError) {
|
|
119
|
+
return new PmtHouseError(error.message, {
|
|
120
|
+
status: 502,
|
|
121
|
+
code: error.code ?? "oauth_processing_error",
|
|
122
|
+
details: { cause: error.cause }
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (error instanceof Error) {
|
|
126
|
+
return new PmtHouseError(error.message, {
|
|
127
|
+
status: 500,
|
|
128
|
+
code: "unexpected_error"
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return new PmtHouseError("Unexpected error", {
|
|
132
|
+
status: 500,
|
|
133
|
+
code: "unexpected_error"
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/device.ts
|
|
138
|
+
function sleep(ms, signal) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const t = setTimeout(resolve, ms);
|
|
141
|
+
signal?.addEventListener(
|
|
142
|
+
"abort",
|
|
143
|
+
() => {
|
|
144
|
+
clearTimeout(t);
|
|
145
|
+
reject(
|
|
146
|
+
signal.reason instanceof Error ? signal.reason : new Error(typeof signal.reason === "string" ? signal.reason : "Aborted")
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
{ once: true }
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async function pollDeviceToken(options) {
|
|
154
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
155
|
+
const as = await loadAuthorizationServer(options.issuerUrl, fetchImpl, {
|
|
156
|
+
allowInsecureHttp: options.allowInsecureHttp
|
|
157
|
+
});
|
|
158
|
+
if (!as.device_authorization_endpoint) {
|
|
159
|
+
throw new PmtHouseError(
|
|
160
|
+
"Authorization server metadata has no device_authorization_endpoint",
|
|
161
|
+
{ status: 400, code: "unsupported_grant" }
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const client = { client_id: options.clientId };
|
|
165
|
+
const params = new URLSearchParams();
|
|
166
|
+
if (options.scope) {
|
|
167
|
+
params.set("scope", options.scope);
|
|
168
|
+
}
|
|
169
|
+
const httpOpts = {
|
|
170
|
+
[oauth4webapi.customFetch]: fetchImpl
|
|
171
|
+
};
|
|
172
|
+
if (options.allowInsecureHttp) {
|
|
173
|
+
httpOpts[oauth4webapi.allowInsecureRequests] = true;
|
|
174
|
+
}
|
|
175
|
+
let deviceResponse;
|
|
176
|
+
try {
|
|
177
|
+
deviceResponse = await oauth4webapi.deviceAuthorizationRequest(
|
|
178
|
+
as,
|
|
179
|
+
client,
|
|
180
|
+
oauth4webapi.None(),
|
|
181
|
+
params,
|
|
182
|
+
httpOpts
|
|
183
|
+
);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
throw mapOAuthError(e);
|
|
186
|
+
}
|
|
187
|
+
let dar;
|
|
188
|
+
try {
|
|
189
|
+
dar = await oauth4webapi.processDeviceAuthorizationResponse(as, client, deviceResponse);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
throw mapOAuthError(e);
|
|
192
|
+
}
|
|
193
|
+
options.onUserCode?.({
|
|
194
|
+
userCode: dar.user_code,
|
|
195
|
+
verificationUri: dar.verification_uri,
|
|
196
|
+
verificationUriComplete: dar.verification_uri_complete,
|
|
197
|
+
expiresIn: dar.expires_in,
|
|
198
|
+
intervalSeconds: dar.interval
|
|
199
|
+
});
|
|
200
|
+
let pollIntervalMs = (dar.interval ?? 5) * 1e3;
|
|
201
|
+
const deadline = Date.now() + dar.expires_in * 1e3;
|
|
202
|
+
let firstPoll = true;
|
|
203
|
+
while (Date.now() < deadline) {
|
|
204
|
+
if (options.signal?.aborted) {
|
|
205
|
+
throw options.signal.reason instanceof Error ? options.signal.reason : new Error("Aborted");
|
|
206
|
+
}
|
|
207
|
+
if (!firstPoll) {
|
|
208
|
+
await sleep(pollIntervalMs, options.signal);
|
|
209
|
+
}
|
|
210
|
+
firstPoll = false;
|
|
211
|
+
let tokenResponse;
|
|
212
|
+
try {
|
|
213
|
+
tokenResponse = await oauth4webapi.deviceCodeGrantRequest(
|
|
214
|
+
as,
|
|
215
|
+
client,
|
|
216
|
+
oauth4webapi.None(),
|
|
217
|
+
dar.device_code,
|
|
218
|
+
httpOpts
|
|
219
|
+
);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
throw mapOAuthError(e);
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
return await oauth4webapi.processDeviceCodeResponse(as, client, tokenResponse);
|
|
225
|
+
} catch (e) {
|
|
226
|
+
if (e instanceof oauth4webapi.ResponseBodyError) {
|
|
227
|
+
if (e.error === "authorization_pending") {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (e.error === "slow_down") {
|
|
231
|
+
pollIntervalMs += 5e3;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
throw mapOAuthError(e);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
throw new PmtHouseError("Device authorization expired before completion", {
|
|
239
|
+
status: 408,
|
|
240
|
+
code: "device_flow_expired"
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
exports.pollDeviceToken = pollDeviceToken;
|
|
245
|
+
//# sourceMappingURL=device.cjs.map
|
|
246
|
+
//# sourceMappingURL=device.cjs.map
|
|
@@ -0,0 +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.charCodeAt(end - 1) === 47) {\n end--;\n }\n return value.slice(0, end);\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"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as oauth4webapi from 'oauth4webapi';
|
|
2
|
+
import { F as FetchLike } from './types-W9PJAspR.cjs';
|
|
3
|
+
|
|
4
|
+
interface PollDeviceTokenOptions {
|
|
5
|
+
issuerUrl: string;
|
|
6
|
+
/** Public OAuth `client_id` (RFC 8628). */
|
|
7
|
+
clientId: string;
|
|
8
|
+
/** Space-separated scopes for the device authorization request. */
|
|
9
|
+
scope?: string;
|
|
10
|
+
fetch?: FetchLike;
|
|
11
|
+
allowInsecureHttp?: boolean;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
onUserCode?: (info: {
|
|
14
|
+
userCode: string;
|
|
15
|
+
verificationUri: string;
|
|
16
|
+
verificationUriComplete?: string;
|
|
17
|
+
expiresIn: number;
|
|
18
|
+
intervalSeconds?: number;
|
|
19
|
+
}) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* RFC 8628 device authorization grant: request a device code, then poll the token endpoint until
|
|
23
|
+
* tokens are issued (handles `authorization_pending` and `slow_down`).
|
|
24
|
+
*/
|
|
25
|
+
declare function pollDeviceToken(options: PollDeviceTokenOptions): Promise<oauth4webapi.TokenEndpointResponse>;
|
|
26
|
+
|
|
27
|
+
export { type PollDeviceTokenOptions, pollDeviceToken };
|
package/dist/device.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as oauth4webapi from 'oauth4webapi';
|
|
2
|
+
import { F as FetchLike } from './types-W9PJAspR.js';
|
|
3
|
+
|
|
4
|
+
interface PollDeviceTokenOptions {
|
|
5
|
+
issuerUrl: string;
|
|
6
|
+
/** Public OAuth `client_id` (RFC 8628). */
|
|
7
|
+
clientId: string;
|
|
8
|
+
/** Space-separated scopes for the device authorization request. */
|
|
9
|
+
scope?: string;
|
|
10
|
+
fetch?: FetchLike;
|
|
11
|
+
allowInsecureHttp?: boolean;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
onUserCode?: (info: {
|
|
14
|
+
userCode: string;
|
|
15
|
+
verificationUri: string;
|
|
16
|
+
verificationUriComplete?: string;
|
|
17
|
+
expiresIn: number;
|
|
18
|
+
intervalSeconds?: number;
|
|
19
|
+
}) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* RFC 8628 device authorization grant: request a device code, then poll the token endpoint until
|
|
23
|
+
* tokens are issued (handles `authorization_pending` and `slow_down`).
|
|
24
|
+
*/
|
|
25
|
+
declare function pollDeviceToken(options: PollDeviceTokenOptions): Promise<oauth4webapi.TokenEndpointResponse>;
|
|
26
|
+
|
|
27
|
+
export { type PollDeviceTokenOptions, pollDeviceToken };
|