toolcraft 0.0.50 → 0.0.52
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/node_modules/@poe-code/agent-defs/README.md +35 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.js +9 -2
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +3 -1
- package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.js +44 -2
- package/node_modules/@poe-code/agent-human-in-loop/package.json +0 -1
- package/node_modules/@poe-code/agent-mcp-config/README.md +54 -0
- package/node_modules/@poe-code/agent-mcp-config/package.json +0 -2
- package/node_modules/@poe-code/config-mutations/README.md +55 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.d.ts +1 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +65 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.js +4 -11
- package/node_modules/@poe-code/config-mutations/package.json +0 -1
- package/node_modules/@poe-code/frontmatter/dist/fences.d.ts +17 -0
- package/node_modules/@poe-code/frontmatter/dist/fences.js +33 -5
- package/node_modules/@poe-code/frontmatter/dist/parse.js +86 -14
- package/node_modules/@poe-code/frontmatter/dist/stringify.js +13 -0
- package/node_modules/@poe-code/process-runner/dist/docker/args.js +14 -1
- package/node_modules/@poe-code/process-runner/dist/docker/build-context.d.ts +5 -0
- package/node_modules/@poe-code/process-runner/dist/docker/build-context.js +37 -0
- package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +29 -29
- package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
- package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
- package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +49 -3
- package/node_modules/@poe-code/process-runner/package.json +8 -1
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +7 -0
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +34 -7
- package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +75 -19
- package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/backends/utils.js +23 -2
- package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +16 -12
- package/node_modules/@poe-code/task-list/dist/state-machine.js +9 -0
- package/node_modules/@poe-code/task-list/package.json +0 -1
- package/node_modules/auth-store/dist/create-secret-store.js +4 -3
- package/node_modules/auth-store/dist/encrypted-file-store.js +49 -2
- package/node_modules/auth-store/dist/keychain-store.js +11 -4
- package/node_modules/auth-store/package.json +0 -1
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +69 -12
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +100 -68
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +19 -18
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +37 -31
- package/node_modules/mcp-oauth/package.json +0 -1
- package/node_modules/tiny-mcp-client/dist/internal.js +96 -10
- package/node_modules/tiny-mcp-client/package.json +0 -3
- package/node_modules/tiny-mcp-client/src/internal.ts +120 -18
- package/node_modules/tiny-mcp-client/src/transports.test.ts +231 -2
- package/node_modules/toolcraft-design/package.json +0 -1
- package/package.json +3 -2
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { URL } from "node:url";
|
|
2
|
-
import { createAuthStoreClientStore, createAuthStoreSessionStore
|
|
2
|
+
import { createAuthStoreClientStore, createAuthStoreSessionStore } from "./auth-store-session-store.js";
|
|
3
3
|
import { createLoopbackAuthorizationSession } from "./loopback-authorization.js";
|
|
4
4
|
import { createAuthorizationState } from "./authorization-state.js";
|
|
5
5
|
import { generateCodeChallenge, generateCodeVerifier } from "./pkce.js";
|
|
6
|
-
import { exchangeAuthorizationCode, OAuthError, refreshAccessToken, isRetryableOAuthError, readOAuthJsonObjectResponse
|
|
6
|
+
import { exchangeAuthorizationCode, OAuthError, refreshAccessToken, isRetryableOAuthError, readOAuthJsonObjectResponse } from "./token-endpoint.js";
|
|
7
7
|
import { canonicalizeResourceIndicator } from "../resource-indicator.js";
|
|
8
|
+
const MAX_JS_DATE_MS = 8_640_000_000_000_000;
|
|
8
9
|
export function createOAuthClientProvider(options) {
|
|
9
10
|
if (isProviderOptions(options)) {
|
|
10
11
|
return options.provider;
|
|
@@ -24,10 +25,10 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
24
25
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
25
26
|
const session = await ensureAuthorizedSession(requestUrl, undefined, input.fetch, false);
|
|
26
27
|
const accessToken = session?.tokens?.accessToken;
|
|
27
|
-
if (session === null
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
if (session === null ||
|
|
29
|
+
accessToken === undefined ||
|
|
30
|
+
session.tokens === undefined ||
|
|
31
|
+
isExpired(session.tokens, now)) {
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
assertRequestMatchesResource(requestUrl, session.resource);
|
|
@@ -39,11 +40,11 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
39
40
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
40
41
|
const resource = canonicalizeResourceIndicator(input.discovery.resource);
|
|
41
42
|
assertRequestMatchesResource(requestUrl, resource);
|
|
42
|
-
const forceRefresh = hasCachedAccessToken(await loadSession(resource))
|
|
43
|
-
|
|
43
|
+
const forceRefresh = hasCachedAccessToken(await loadSession(resource)) &&
|
|
44
|
+
input.challenge?.params.error === "invalid_token";
|
|
44
45
|
const session = await ensureAuthorizedSession(resource, {
|
|
45
46
|
...input.discovery,
|
|
46
|
-
resource
|
|
47
|
+
resource
|
|
47
48
|
}, input.fetch, true, forceRefresh);
|
|
48
49
|
if (session?.tokens?.accessToken === undefined) {
|
|
49
50
|
return { action: "fail" };
|
|
@@ -53,10 +54,10 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
53
54
|
catch (error) {
|
|
54
55
|
return {
|
|
55
56
|
action: "fail",
|
|
56
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
57
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
|
-
}
|
|
60
|
+
}
|
|
60
61
|
};
|
|
61
62
|
async function ensureAuthorizedSession(resource, discovery, fetch, allowInteractive, forceRefresh = false) {
|
|
62
63
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
@@ -65,9 +66,9 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
65
66
|
if (session?.tokens !== undefined && !forceRefresh && !isExpired(session.tokens, now)) {
|
|
66
67
|
return session;
|
|
67
68
|
}
|
|
68
|
-
if (session?.tokens?.refreshToken !== undefined
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
if (session?.tokens?.refreshToken !== undefined &&
|
|
70
|
+
sessionDiscovery !== undefined &&
|
|
71
|
+
(forceRefresh || isExpired(session.tokens, now))) {
|
|
71
72
|
session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch);
|
|
72
73
|
if (session?.tokens !== undefined && !isExpired(session.tokens, now)) {
|
|
73
74
|
return session;
|
|
@@ -104,7 +105,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
104
105
|
refreshToken: session.tokens.refreshToken,
|
|
105
106
|
resource,
|
|
106
107
|
fetch,
|
|
107
|
-
now
|
|
108
|
+
now
|
|
108
109
|
});
|
|
109
110
|
break;
|
|
110
111
|
}
|
|
@@ -130,9 +131,9 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
130
131
|
...session,
|
|
131
132
|
tokens: {
|
|
132
133
|
...refreshedTokens,
|
|
133
|
-
refreshToken: refreshedTokens.refreshToken ?? session.tokens.refreshToken
|
|
134
|
+
refreshToken: refreshedTokens.refreshToken ?? session.tokens.refreshToken
|
|
134
135
|
},
|
|
135
|
-
discovery: toStoredDiscovery(discovery)
|
|
136
|
+
discovery: toStoredDiscovery(discovery)
|
|
136
137
|
};
|
|
137
138
|
await saveSession(resource, updatedSession);
|
|
138
139
|
return updatedSession;
|
|
@@ -160,7 +161,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
160
161
|
openBrowser: options.browser.openBrowser,
|
|
161
162
|
readLine: options.browser.readLine,
|
|
162
163
|
createServer: options.browser.createServer,
|
|
163
|
-
landingPage: options.browser.landingPage
|
|
164
|
+
landingPage: options.browser.landingPage
|
|
164
165
|
});
|
|
165
166
|
let resolvedClient = null;
|
|
166
167
|
try {
|
|
@@ -169,7 +170,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
169
170
|
resource,
|
|
170
171
|
authorizationServer: discovery.authorizationServer,
|
|
171
172
|
client: resolvedClient.client,
|
|
172
|
-
discovery: toStoredDiscovery(discovery)
|
|
173
|
+
discovery: toStoredDiscovery(discovery)
|
|
173
174
|
};
|
|
174
175
|
await saveSession(resource, sessionWithoutTokens);
|
|
175
176
|
const verifier = generateCodeVerifier();
|
|
@@ -180,7 +181,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
180
181
|
clientId: resolvedClient.client.clientId,
|
|
181
182
|
redirectUri: loopback.redirectUri,
|
|
182
183
|
codeChallenge: challenge,
|
|
183
|
-
clientMetadata: getClientMetadata(options.client)
|
|
184
|
+
clientMetadata: getClientMetadata(options.client)
|
|
184
185
|
});
|
|
185
186
|
const code = await loopback.waitForCode(authorizationUrl);
|
|
186
187
|
const tokens = await exchangeAuthorizationCode({
|
|
@@ -192,11 +193,11 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
192
193
|
redirectUri: loopback.redirectUri,
|
|
193
194
|
resource,
|
|
194
195
|
fetch,
|
|
195
|
-
now
|
|
196
|
+
now
|
|
196
197
|
});
|
|
197
198
|
const session = {
|
|
198
199
|
...sessionWithoutTokens,
|
|
199
|
-
tokens
|
|
200
|
+
tokens
|
|
200
201
|
};
|
|
201
202
|
await saveSession(resource, session);
|
|
202
203
|
return session;
|
|
@@ -229,25 +230,23 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
229
230
|
return finalPromise;
|
|
230
231
|
}
|
|
231
232
|
async function resolveClient(existingSession, discovery, redirectUri, fetch) {
|
|
233
|
+
const configuredClient = normalizeConfiguredClient(options.client);
|
|
232
234
|
if (options.client.mode === "static") {
|
|
235
|
+
if (configuredClient === null) {
|
|
236
|
+
throw new Error("OAuth client_id must not be blank");
|
|
237
|
+
}
|
|
233
238
|
return {
|
|
234
239
|
kind: "static",
|
|
235
240
|
fromStoredRegistration: false,
|
|
236
|
-
client:
|
|
237
|
-
clientId: options.client.clientId,
|
|
238
|
-
clientSecret: options.client.clientSecret,
|
|
239
|
-
},
|
|
241
|
+
client: configuredClient
|
|
240
242
|
};
|
|
241
243
|
}
|
|
242
244
|
const registrationEndpoint = getOwnString(discovery.authorizationServerMetadata, "registration_endpoint");
|
|
243
|
-
if (registrationEndpoint === undefined &&
|
|
245
|
+
if (registrationEndpoint === undefined && configuredClient !== null) {
|
|
244
246
|
return {
|
|
245
247
|
kind: "static",
|
|
246
248
|
fromStoredRegistration: false,
|
|
247
|
-
client:
|
|
248
|
-
clientId: options.client.clientId,
|
|
249
|
-
clientSecret: options.client.clientSecret,
|
|
250
|
-
},
|
|
249
|
+
client: configuredClient
|
|
251
250
|
};
|
|
252
251
|
}
|
|
253
252
|
const storedClient = await loadRegisteredClient(discovery.authorizationServer);
|
|
@@ -255,7 +254,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
255
254
|
return {
|
|
256
255
|
kind: "dynamic",
|
|
257
256
|
fromStoredRegistration: true,
|
|
258
|
-
client: storedClient
|
|
257
|
+
client: storedClient
|
|
259
258
|
};
|
|
260
259
|
}
|
|
261
260
|
if (registrationEndpoint === undefined) {
|
|
@@ -263,21 +262,21 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
263
262
|
return {
|
|
264
263
|
kind: "dynamic",
|
|
265
264
|
fromStoredRegistration: true,
|
|
266
|
-
client: existingSession.client
|
|
265
|
+
client: existingSession.client
|
|
267
266
|
};
|
|
268
267
|
}
|
|
269
268
|
throw new Error("Authorization server metadata is missing registration_endpoint");
|
|
270
269
|
}
|
|
271
270
|
if (existingSession !== null && existingSession.client.clientId.length > 0) {
|
|
272
|
-
const isConfiguredStaticFallback =
|
|
273
|
-
|
|
274
|
-
|
|
271
|
+
const isConfiguredStaticFallback = configuredClient !== null &&
|
|
272
|
+
existingSession.client.clientId === configuredClient.clientId &&
|
|
273
|
+
existingSession.client.clientSecret === configuredClient.clientSecret;
|
|
275
274
|
if (!isConfiguredStaticFallback) {
|
|
276
275
|
await saveRegisteredClient(discovery.authorizationServer, existingSession.client);
|
|
277
276
|
return {
|
|
278
277
|
kind: "dynamic",
|
|
279
278
|
fromStoredRegistration: true,
|
|
280
|
-
client: existingSession.client
|
|
279
|
+
client: existingSession.client
|
|
281
280
|
};
|
|
282
281
|
}
|
|
283
282
|
}
|
|
@@ -285,28 +284,27 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
285
284
|
const response = await fetch(registrationEndpoint, {
|
|
286
285
|
method: "POST",
|
|
287
286
|
headers: {
|
|
288
|
-
"Content-Type": "application/json"
|
|
287
|
+
"Content-Type": "application/json"
|
|
289
288
|
},
|
|
290
|
-
body: JSON.stringify(registrationBody)
|
|
289
|
+
body: JSON.stringify(registrationBody)
|
|
291
290
|
});
|
|
292
291
|
const payload = await readOAuthJsonObjectResponse(response);
|
|
293
292
|
const clientId = getOwnString(payload, "client_id");
|
|
294
|
-
if (clientId === undefined ||
|
|
295
|
-
clientId.trim().length === 0) {
|
|
293
|
+
if (clientId === undefined || clientId.trim().length === 0) {
|
|
296
294
|
throw new Error("OAuth client registration response missing client_id");
|
|
297
295
|
}
|
|
298
296
|
const clientSecret = getOwnString(payload, "client_secret");
|
|
299
297
|
const registeredClient = {
|
|
300
|
-
clientId,
|
|
301
|
-
clientSecret: clientSecret !== undefined && clientSecret.length > 0
|
|
302
|
-
? clientSecret
|
|
303
|
-
: undefined
|
|
298
|
+
clientId: clientId.trim(),
|
|
299
|
+
clientSecret: clientSecret !== undefined && clientSecret.trim().length > 0
|
|
300
|
+
? clientSecret.trim()
|
|
301
|
+
: undefined
|
|
304
302
|
};
|
|
305
303
|
await saveRegisteredClient(discovery.authorizationServer, registeredClient);
|
|
306
304
|
return {
|
|
307
305
|
kind: "dynamic",
|
|
308
306
|
fromStoredRegistration: false,
|
|
309
|
-
client: registeredClient
|
|
307
|
+
client: registeredClient
|
|
310
308
|
};
|
|
311
309
|
}
|
|
312
310
|
async function loadSession(resource) {
|
|
@@ -357,7 +355,7 @@ function resolveDiscovery(discovery, session) {
|
|
|
357
355
|
if (discovery !== undefined) {
|
|
358
356
|
return {
|
|
359
357
|
...discovery,
|
|
360
|
-
resource: canonicalizeResourceIndicator(discovery.resource)
|
|
358
|
+
resource: canonicalizeResourceIndicator(discovery.resource)
|
|
361
359
|
};
|
|
362
360
|
}
|
|
363
361
|
if (session === null) {
|
|
@@ -378,10 +376,11 @@ function resolveDiscovery(discovery, session) {
|
|
|
378
376
|
return {
|
|
379
377
|
resource: canonicalizeResourceIndicator(session.resource),
|
|
380
378
|
resourceMetadataUrl: session.discovery.resourceMetadataUrl,
|
|
381
|
-
resourceMetadata: session.discovery
|
|
379
|
+
resourceMetadata: session.discovery
|
|
380
|
+
.resourceMetadata,
|
|
382
381
|
authorizationServer: session.authorizationServer,
|
|
383
382
|
authorizationServerMetadataUrl: "",
|
|
384
|
-
authorizationServerMetadata: metadata
|
|
383
|
+
authorizationServerMetadata: metadata
|
|
385
384
|
};
|
|
386
385
|
}
|
|
387
386
|
function clearSessionTokens(session) {
|
|
@@ -403,7 +402,7 @@ function normalizeLoadedSession(session) {
|
|
|
403
402
|
return {
|
|
404
403
|
...session,
|
|
405
404
|
client,
|
|
406
|
-
tokens: normalizeStoredTokens(getOwnEntry(session, "tokens"))
|
|
405
|
+
tokens: normalizeStoredTokens(getOwnEntry(session, "tokens"))
|
|
407
406
|
};
|
|
408
407
|
}
|
|
409
408
|
function normalizeStoredClient(value) {
|
|
@@ -414,14 +413,16 @@ function normalizeStoredClient(value) {
|
|
|
414
413
|
if (clientId === undefined || clientId.trim().length === 0) {
|
|
415
414
|
return null;
|
|
416
415
|
}
|
|
416
|
+
const normalizedClientId = clientId.trim();
|
|
417
417
|
const clientSecret = getOwnEntry(value, "clientSecret");
|
|
418
418
|
if (clientSecret === undefined) {
|
|
419
|
-
return { clientId };
|
|
419
|
+
return { clientId: normalizedClientId };
|
|
420
420
|
}
|
|
421
421
|
if (typeof clientSecret !== "string" || clientSecret.trim().length === 0) {
|
|
422
422
|
return null;
|
|
423
423
|
}
|
|
424
|
-
|
|
424
|
+
const normalizedClientSecret = clientSecret.trim();
|
|
425
|
+
return { clientId: normalizedClientId, clientSecret: normalizedClientSecret };
|
|
425
426
|
}
|
|
426
427
|
function normalizeStoredTokens(value) {
|
|
427
428
|
if (value === undefined || !isObjectRecord(value)) {
|
|
@@ -432,26 +433,59 @@ function normalizeStoredTokens(value) {
|
|
|
432
433
|
const expiresAt = getOwnEntry(value, "expiresAt");
|
|
433
434
|
const refreshToken = getOwnEntry(value, "refreshToken");
|
|
434
435
|
const scope = getOwnString(value, "scope");
|
|
435
|
-
const
|
|
436
|
+
const normalizedAccessToken = accessToken?.trim();
|
|
437
|
+
const normalizedRefreshToken = typeof refreshToken === "string" ? refreshToken.trim() : undefined;
|
|
438
|
+
const normalizedScope = scope?.trim();
|
|
436
439
|
if (accessToken === undefined ||
|
|
437
|
-
|
|
440
|
+
normalizedAccessToken === undefined ||
|
|
441
|
+
normalizedAccessToken.length === 0 ||
|
|
438
442
|
tokenType !== "Bearer" ||
|
|
439
443
|
!(expiresAt === null ||
|
|
440
|
-
(typeof expiresAt === "number" &&
|
|
444
|
+
(typeof expiresAt === "number" &&
|
|
445
|
+
Number.isSafeInteger(expiresAt) &&
|
|
446
|
+
expiresAt <= MAX_JS_DATE_MS &&
|
|
447
|
+
Number.isFinite(new Date(expiresAt).getTime()))) ||
|
|
441
448
|
(refreshToken !== undefined &&
|
|
442
|
-
(typeof refreshToken !== "string" ||
|
|
449
|
+
(typeof refreshToken !== "string" ||
|
|
450
|
+
normalizedRefreshToken === undefined ||
|
|
451
|
+
normalizedRefreshToken.length === 0))) {
|
|
443
452
|
return undefined;
|
|
444
453
|
}
|
|
445
454
|
return {
|
|
446
|
-
accessToken,
|
|
455
|
+
accessToken: normalizedAccessToken,
|
|
447
456
|
tokenType,
|
|
448
457
|
expiresAt,
|
|
449
458
|
...(normalizedRefreshToken === undefined ? {} : { refreshToken: normalizedRefreshToken }),
|
|
450
|
-
...(
|
|
459
|
+
...(normalizedScope === undefined || normalizedScope.length === 0
|
|
460
|
+
? {}
|
|
461
|
+
: { scope: normalizedScope })
|
|
451
462
|
};
|
|
452
463
|
}
|
|
453
464
|
function getClientMetadata(client) {
|
|
454
|
-
|
|
465
|
+
if (client.metadata === undefined) {
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
clientName: normalizeOptionalOAuthString(client.metadata.clientName),
|
|
470
|
+
scope: normalizeOptionalOAuthString(client.metadata.scope),
|
|
471
|
+
softwareId: normalizeOptionalOAuthString(client.metadata.softwareId),
|
|
472
|
+
softwareVersion: normalizeOptionalOAuthString(client.metadata.softwareVersion)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function normalizeConfiguredClient(client) {
|
|
476
|
+
const clientId = normalizeOptionalOAuthString(client.clientId);
|
|
477
|
+
if (clientId === undefined) {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
const clientSecret = normalizeOptionalOAuthString(client.clientSecret);
|
|
481
|
+
return clientSecret === undefined ? { clientId } : { clientId, clientSecret };
|
|
482
|
+
}
|
|
483
|
+
function normalizeOptionalOAuthString(value) {
|
|
484
|
+
if (value === undefined) {
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
487
|
+
const trimmed = value.trim();
|
|
488
|
+
return trimmed.length === 0 ? undefined : trimmed;
|
|
455
489
|
}
|
|
456
490
|
function getOwnEntry(record, key) {
|
|
457
491
|
return Object.prototype.hasOwnProperty.call(record, key)
|
|
@@ -485,7 +519,7 @@ function buildAuthorizationUrl(input) {
|
|
|
485
519
|
const resource = canonicalizeResourceIndicator(input.resource);
|
|
486
520
|
const state = createAuthorizationState({
|
|
487
521
|
issuer,
|
|
488
|
-
requireIssuer: getOwnEntry(input.metadata, "authorization_response_iss_parameter_supported") === true
|
|
522
|
+
requireIssuer: getOwnEntry(input.metadata, "authorization_response_iss_parameter_supported") === true
|
|
489
523
|
});
|
|
490
524
|
url.searchParams.set("response_type", "code");
|
|
491
525
|
url.searchParams.set("client_id", input.clientId);
|
|
@@ -509,9 +543,9 @@ function normalizeHostname(hostname) {
|
|
|
509
543
|
}
|
|
510
544
|
function isLoopbackHostname(hostname) {
|
|
511
545
|
const normalizedHostname = normalizeHostname(hostname);
|
|
512
|
-
return normalizedHostname === "localhost"
|
|
513
|
-
|
|
514
|
-
|
|
546
|
+
return (normalizedHostname === "localhost" ||
|
|
547
|
+
normalizedHostname === "::1" ||
|
|
548
|
+
normalizedHostname.startsWith("127."));
|
|
515
549
|
}
|
|
516
550
|
function assertSecureUrl(value, label) {
|
|
517
551
|
const url = new URL(value);
|
|
@@ -552,14 +586,12 @@ function buildClientRegistrationBody(metadata, redirectUri) {
|
|
|
552
586
|
redirect_uris: [redirectUri],
|
|
553
587
|
grant_types: ["authorization_code", "refresh_token"],
|
|
554
588
|
response_types: ["code"],
|
|
555
|
-
token_endpoint_auth_method: "none"
|
|
589
|
+
token_endpoint_auth_method: "none"
|
|
556
590
|
};
|
|
557
591
|
const clientName = metadata === undefined ? undefined : getOwnString(metadata, "clientName");
|
|
558
592
|
const scope = metadata === undefined ? undefined : getOwnString(metadata, "scope");
|
|
559
593
|
const softwareId = metadata === undefined ? undefined : getOwnString(metadata, "softwareId");
|
|
560
|
-
const softwareVersion = metadata === undefined
|
|
561
|
-
? undefined
|
|
562
|
-
: getOwnString(metadata, "softwareVersion");
|
|
594
|
+
const softwareVersion = metadata === undefined ? undefined : getOwnString(metadata, "softwareVersion");
|
|
563
595
|
if (clientName !== undefined && clientName.length > 0) {
|
|
564
596
|
body.client_name = clientName;
|
|
565
597
|
}
|
|
@@ -578,7 +610,7 @@ function toStoredDiscovery(discovery) {
|
|
|
578
610
|
return {
|
|
579
611
|
resourceMetadataUrl: discovery.resourceMetadataUrl,
|
|
580
612
|
resourceMetadata: discovery.resourceMetadata,
|
|
581
|
-
authorizationServerMetadata: discovery.authorizationServerMetadata
|
|
613
|
+
authorizationServerMetadata: discovery.authorizationServerMetadata
|
|
582
614
|
};
|
|
583
615
|
}
|
|
584
616
|
function shouldReRegisterStoredDynamicClient(error, client, alreadyAttempted) {
|
|
@@ -13,7 +13,7 @@ export async function createLoopbackAuthorizationSession(options = {}) {
|
|
|
13
13
|
close() {
|
|
14
14
|
server.closeAllConnections?.();
|
|
15
15
|
server.close();
|
|
16
|
-
}
|
|
16
|
+
}
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
async function startServer(server) {
|
|
@@ -52,7 +52,7 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
|
|
|
52
52
|
error: url.searchParams.get("error"),
|
|
53
53
|
errorDescription: url.searchParams.get("error_description"),
|
|
54
54
|
state: url.searchParams.get("state"),
|
|
55
|
-
iss: url.searchParams.get("iss")
|
|
55
|
+
iss: url.searchParams.get("iss")
|
|
56
56
|
};
|
|
57
57
|
try {
|
|
58
58
|
validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
|
|
@@ -60,9 +60,7 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
|
|
|
60
60
|
catch (error) {
|
|
61
61
|
res.writeHead(400);
|
|
62
62
|
res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
|
|
63
|
-
|
|
64
|
-
settle(() => reject(error instanceof Error ? error : new Error(String(error))));
|
|
65
|
-
}
|
|
63
|
+
settle(() => reject(error instanceof Error ? error : new Error(String(error))));
|
|
66
64
|
return;
|
|
67
65
|
}
|
|
68
66
|
const authorizationError = callbackParameters.error;
|
|
@@ -86,7 +84,9 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
|
|
|
86
84
|
}
|
|
87
85
|
});
|
|
88
86
|
if (options.readLine !== undefined) {
|
|
89
|
-
options
|
|
87
|
+
options
|
|
88
|
+
.readLine()
|
|
89
|
+
.then((input) => {
|
|
90
90
|
const callbackParameters = extractCallbackParametersFromInput(input);
|
|
91
91
|
if (callbackParameters === null) {
|
|
92
92
|
settle(() => reject(new Error("OAuth callback missing authorization code")));
|
|
@@ -104,7 +104,8 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
|
|
|
104
104
|
catch (error) {
|
|
105
105
|
settle(() => reject(error instanceof Error ? error : new Error(String(error))));
|
|
106
106
|
}
|
|
107
|
-
})
|
|
107
|
+
})
|
|
108
|
+
.catch((error) => {
|
|
108
109
|
settle(() => reject(error instanceof Error ? error : new Error(String(error))));
|
|
109
110
|
});
|
|
110
111
|
}
|
|
@@ -130,7 +131,7 @@ function extractCallbackParametersFromInput(input) {
|
|
|
130
131
|
error: url.searchParams.get("error"),
|
|
131
132
|
errorDescription: url.searchParams.get("error_description"),
|
|
132
133
|
state: url.searchParams.get("state"),
|
|
133
|
-
iss: url.searchParams.get("iss")
|
|
134
|
+
iss: url.searchParams.get("iss")
|
|
134
135
|
};
|
|
135
136
|
}
|
|
136
137
|
catch {
|
|
@@ -139,7 +140,7 @@ function extractCallbackParametersFromInput(input) {
|
|
|
139
140
|
error: null,
|
|
140
141
|
errorDescription: null,
|
|
141
142
|
state: null,
|
|
142
|
-
iss: null
|
|
143
|
+
iss: null
|
|
143
144
|
};
|
|
144
145
|
}
|
|
145
146
|
}
|
|
@@ -150,7 +151,7 @@ function readExpectedAuthorizationCallback(authorizationUrl) {
|
|
|
150
151
|
return {
|
|
151
152
|
state,
|
|
152
153
|
issuer: parsedState?.issuer ?? null,
|
|
153
|
-
requireIssuer: parsedState?.requireIssuer ?? false
|
|
154
|
+
requireIssuer: parsedState?.requireIssuer ?? false
|
|
154
155
|
};
|
|
155
156
|
}
|
|
156
157
|
function validateAuthorizationCallbackParameters(callback, expected) {
|
|
@@ -174,10 +175,10 @@ function validateAuthorizationCallbackBinding(callback, expected) {
|
|
|
174
175
|
throw new Error("OAuth callback missing issuer");
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
|
-
if (callback.iss !== null
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
if (callback.iss !== null &&
|
|
179
|
+
callback.iss.length > 0 &&
|
|
180
|
+
expected.issuer !== null &&
|
|
181
|
+
callback.iss !== expected.issuer) {
|
|
181
182
|
throw new Error("OAuth callback issuer mismatch");
|
|
182
183
|
}
|
|
183
184
|
}
|
|
@@ -189,7 +190,7 @@ function escapeHtml(text) {
|
|
|
189
190
|
.replaceAll("&", "&")
|
|
190
191
|
.replaceAll("<", "<")
|
|
191
192
|
.replaceAll(">", ">")
|
|
192
|
-
.replaceAll("
|
|
193
|
+
.replaceAll('"', """);
|
|
193
194
|
}
|
|
194
195
|
export function buildSuccessPage(landingPage) {
|
|
195
196
|
const title = landingPage?.title ?? "Connected";
|
|
@@ -197,10 +198,10 @@ export function buildSuccessPage(landingPage) {
|
|
|
197
198
|
return [
|
|
198
199
|
"<!DOCTYPE html>",
|
|
199
200
|
`<html><head><meta charset=utf-8><title>${escapeHtml(title)}</title></head>`,
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
'<body style="font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0">',
|
|
202
|
+
'<div style="text-align:center">',
|
|
202
203
|
`<h1>${escapeHtml(title)}</h1>`,
|
|
203
204
|
`<p style="color:#666">${escapeHtml(body)}</p>`,
|
|
204
|
-
"</div></body></html>"
|
|
205
|
+
"</div></body></html>"
|
|
205
206
|
].join("");
|
|
206
207
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canonicalizeResourceIndicator } from "../resource-indicator.js";
|
|
2
|
+
const MAX_JS_DATE_MS = 8_640_000_000_000_000;
|
|
2
3
|
export class OAuthError extends Error {
|
|
3
4
|
error;
|
|
4
5
|
errorDescription;
|
|
@@ -22,10 +23,10 @@ export class OAuthError extends Error {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
export function isRetryableOAuthError(error) {
|
|
25
|
-
return (error instanceof OAuthError
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
return (error instanceof OAuthError &&
|
|
27
|
+
(error.status >= 500 ||
|
|
28
|
+
error.error === "server_error" ||
|
|
29
|
+
error.error === "temporarily_unavailable"));
|
|
29
30
|
}
|
|
30
31
|
export async function exchangeAuthorizationCode(input) {
|
|
31
32
|
const resource = canonicalizeResourceIndicator(input.resource);
|
|
@@ -38,10 +39,10 @@ export async function exchangeAuthorizationCode(input) {
|
|
|
38
39
|
code: input.code,
|
|
39
40
|
code_verifier: input.codeVerifier,
|
|
40
41
|
redirect_uri: input.redirectUri,
|
|
41
|
-
resource
|
|
42
|
+
resource
|
|
42
43
|
},
|
|
43
44
|
fetch: input.fetch,
|
|
44
|
-
now: input.now
|
|
45
|
+
now: input.now
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
48
|
export async function refreshAccessToken(input) {
|
|
@@ -53,16 +54,16 @@ export async function refreshAccessToken(input) {
|
|
|
53
54
|
params: {
|
|
54
55
|
grant_type: "refresh_token",
|
|
55
56
|
refresh_token: input.refreshToken,
|
|
56
|
-
resource
|
|
57
|
+
resource
|
|
57
58
|
},
|
|
58
59
|
fetch: input.fetch,
|
|
59
|
-
now: input.now
|
|
60
|
+
now: input.now
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
63
|
async function requestTokens(input) {
|
|
63
64
|
const body = new URLSearchParams({
|
|
64
65
|
client_id: input.clientId,
|
|
65
|
-
...input.params
|
|
66
|
+
...input.params
|
|
66
67
|
});
|
|
67
68
|
if (input.clientSecret !== undefined) {
|
|
68
69
|
body.set("client_secret", input.clientSecret);
|
|
@@ -70,39 +71,48 @@ async function requestTokens(input) {
|
|
|
70
71
|
const response = await input.fetch(input.tokenEndpoint, {
|
|
71
72
|
method: "POST",
|
|
72
73
|
headers: {
|
|
73
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
74
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
74
75
|
},
|
|
75
|
-
body: body.toString()
|
|
76
|
+
body: body.toString()
|
|
76
77
|
});
|
|
77
78
|
const payload = await readOAuthJsonObjectResponse(response);
|
|
78
79
|
const accessToken = getOwnEntry(payload, "access_token");
|
|
79
80
|
if (typeof accessToken !== "string" || accessToken.trim().length === 0) {
|
|
80
81
|
throw new Error("OAuth token response missing access_token");
|
|
81
82
|
}
|
|
83
|
+
const normalizedAccessToken = accessToken.trim();
|
|
82
84
|
const tokenType = normalizeBearerTokenType(getOwnEntry(payload, "token_type"));
|
|
83
85
|
if (tokenType === null) {
|
|
84
86
|
throw new Error("OAuth token response missing token_type=Bearer");
|
|
85
87
|
}
|
|
86
88
|
const expiresIn = getOwnEntry(payload, "expires_in");
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
let expiresAt = null;
|
|
90
|
+
if (expiresIn !== undefined) {
|
|
91
|
+
if (typeof expiresIn !== "number" ||
|
|
92
|
+
!Number.isFinite(expiresIn) ||
|
|
93
|
+
!Number.isInteger(expiresIn) ||
|
|
94
|
+
expiresIn < 0) {
|
|
95
|
+
throw new Error("OAuth token response has invalid expires_in");
|
|
96
|
+
}
|
|
97
|
+
expiresAt = input.now() + expiresIn * 1000;
|
|
98
|
+
if (!Number.isSafeInteger(expiresAt) ||
|
|
99
|
+
expiresAt > MAX_JS_DATE_MS ||
|
|
100
|
+
!Number.isFinite(new Date(expiresAt).getTime())) {
|
|
101
|
+
throw new Error("OAuth token response has invalid expires_in");
|
|
102
|
+
}
|
|
91
103
|
}
|
|
92
104
|
const refreshToken = getOwnEntry(payload, "refresh_token");
|
|
93
105
|
const scope = getOwnEntry(payload, "scope");
|
|
106
|
+
const normalizedRefreshToken = typeof refreshToken === "string" && refreshToken.trim().length > 0
|
|
107
|
+
? refreshToken.trim()
|
|
108
|
+
: undefined;
|
|
109
|
+
const normalizedScope = typeof scope === "string" && scope.trim().length > 0 ? scope.trim() : undefined;
|
|
94
110
|
return {
|
|
95
|
-
accessToken,
|
|
96
|
-
refreshToken:
|
|
97
|
-
? refreshToken
|
|
98
|
-
: undefined,
|
|
111
|
+
accessToken: normalizedAccessToken,
|
|
112
|
+
refreshToken: normalizedRefreshToken === undefined ? undefined : normalizedRefreshToken,
|
|
99
113
|
tokenType,
|
|
100
|
-
expiresAt
|
|
101
|
-
|
|
102
|
-
: null,
|
|
103
|
-
scope: typeof scope === "string" && scope.length > 0
|
|
104
|
-
? scope
|
|
105
|
-
: undefined,
|
|
114
|
+
expiresAt,
|
|
115
|
+
scope: normalizedScope === undefined ? undefined : normalizedScope
|
|
106
116
|
};
|
|
107
117
|
}
|
|
108
118
|
export async function readOAuthJsonObjectResponse(response) {
|
|
@@ -135,12 +145,8 @@ function readOAuthError(payload, fallbackError = "server_error") {
|
|
|
135
145
|
const errorUri = getOwnEntry(payload, "error_uri");
|
|
136
146
|
return {
|
|
137
147
|
error: typeof error === "string" ? error : fallbackError,
|
|
138
|
-
error_description: typeof errorDescription === "string"
|
|
139
|
-
|
|
140
|
-
: undefined,
|
|
141
|
-
error_uri: typeof errorUri === "string"
|
|
142
|
-
? errorUri
|
|
143
|
-
: undefined,
|
|
148
|
+
error_description: typeof errorDescription === "string" ? errorDescription : undefined,
|
|
149
|
+
error_uri: typeof errorUri === "string" ? errorUri : undefined
|
|
144
150
|
};
|
|
145
151
|
}
|
|
146
152
|
function getOwnEntry(record, key) {
|