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.
Files changed (47) hide show
  1. package/node_modules/@poe-code/agent-defs/README.md +35 -0
  2. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.js +9 -2
  3. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +3 -1
  4. package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.js +44 -2
  5. package/node_modules/@poe-code/agent-human-in-loop/package.json +0 -1
  6. package/node_modules/@poe-code/agent-mcp-config/README.md +54 -0
  7. package/node_modules/@poe-code/agent-mcp-config/package.json +0 -2
  8. package/node_modules/@poe-code/config-mutations/README.md +55 -0
  9. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.d.ts +1 -0
  10. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +65 -0
  11. package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.js +4 -11
  12. package/node_modules/@poe-code/config-mutations/package.json +0 -1
  13. package/node_modules/@poe-code/frontmatter/dist/fences.d.ts +17 -0
  14. package/node_modules/@poe-code/frontmatter/dist/fences.js +33 -5
  15. package/node_modules/@poe-code/frontmatter/dist/parse.js +86 -14
  16. package/node_modules/@poe-code/frontmatter/dist/stringify.js +13 -0
  17. package/node_modules/@poe-code/process-runner/dist/docker/args.js +14 -1
  18. package/node_modules/@poe-code/process-runner/dist/docker/build-context.d.ts +5 -0
  19. package/node_modules/@poe-code/process-runner/dist/docker/build-context.js +37 -0
  20. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +29 -29
  21. package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
  22. package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
  23. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +49 -3
  24. package/node_modules/@poe-code/process-runner/package.json +8 -1
  25. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +7 -0
  26. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +34 -7
  27. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +75 -19
  28. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
  29. package/node_modules/@poe-code/task-list/dist/backends/utils.js +23 -2
  30. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +16 -12
  31. package/node_modules/@poe-code/task-list/dist/state-machine.js +9 -0
  32. package/node_modules/@poe-code/task-list/package.json +0 -1
  33. package/node_modules/auth-store/dist/create-secret-store.js +4 -3
  34. package/node_modules/auth-store/dist/encrypted-file-store.js +49 -2
  35. package/node_modules/auth-store/dist/keychain-store.js +11 -4
  36. package/node_modules/auth-store/package.json +0 -1
  37. package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +69 -12
  38. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +100 -68
  39. package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +19 -18
  40. package/node_modules/mcp-oauth/dist/client/token-endpoint.js +37 -31
  41. package/node_modules/mcp-oauth/package.json +0 -1
  42. package/node_modules/tiny-mcp-client/dist/internal.js +96 -10
  43. package/node_modules/tiny-mcp-client/package.json +0 -3
  44. package/node_modules/tiny-mcp-client/src/internal.ts +120 -18
  45. package/node_modules/tiny-mcp-client/src/transports.test.ts +231 -2
  46. package/node_modules/toolcraft-design/package.json +0 -1
  47. package/package.json +3 -2
@@ -1,10 +1,11 @@
1
1
  import { URL } from "node:url";
2
- import { createAuthStoreClientStore, createAuthStoreSessionStore, } from "./auth-store-session-store.js";
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, } from "./token-endpoint.js";
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
- || accessToken === undefined
29
- || session.tokens === undefined
30
- || isExpired(session.tokens, now)) {
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
- && input.challenge?.params.error === "invalid_token";
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
- && sessionDiscovery !== undefined
70
- && (forceRefresh || isExpired(session.tokens, now))) {
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 && options.client.clientId !== 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 = options.client.clientId !== undefined
273
- && existingSession.client.clientId === options.client.clientId
274
- && existingSession.client.clientSecret === options.client.clientSecret;
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.resourceMetadata,
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
- return { clientId, clientSecret };
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 normalizedRefreshToken = typeof refreshToken === "string" ? refreshToken : undefined;
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
- accessToken.trim().length === 0 ||
440
+ normalizedAccessToken === undefined ||
441
+ normalizedAccessToken.length === 0 ||
438
442
  tokenType !== "Bearer" ||
439
443
  !(expiresAt === null ||
440
- (typeof expiresAt === "number" && Number.isFinite(expiresAt))) ||
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" || refreshToken.trim().length === 0))) {
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
- ...(scope === undefined || scope.length === 0 ? {} : { scope }),
459
+ ...(normalizedScope === undefined || normalizedScope.length === 0
460
+ ? {}
461
+ : { scope: normalizedScope })
451
462
  };
452
463
  }
453
464
  function getClientMetadata(client) {
454
- return client.metadata;
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
- || normalizedHostname === "::1"
514
- || normalizedHostname.startsWith("127.");
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
- if (callbackParameters.error === null) {
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.readLine().then((input) => {
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
- }).catch((error) => {
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
- && callback.iss.length > 0
179
- && expected.issuer !== null
180
- && callback.iss !== expected.issuer) {
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("&", "&amp;")
190
191
  .replaceAll("<", "&lt;")
191
192
  .replaceAll(">", "&gt;")
192
- .replaceAll("\"", "&quot;");
193
+ .replaceAll('"', "&quot;");
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
- "<body style=\"font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0\">",
201
- "<div style=\"text-align:center\">",
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
- && (error.status >= 500
27
- || error.error === "server_error"
28
- || error.error === "temporarily_unavailable"));
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
- if (typeof expiresIn === "number"
88
- && Number.isFinite(expiresIn)
89
- && expiresIn < 0) {
90
- throw new Error("OAuth token response has invalid expires_in");
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: typeof refreshToken === "string" && refreshToken.length > 0
97
- ? refreshToken
98
- : undefined,
111
+ accessToken: normalizedAccessToken,
112
+ refreshToken: normalizedRefreshToken === undefined ? undefined : normalizedRefreshToken,
99
113
  tokenType,
100
- expiresAt: typeof expiresIn === "number" && Number.isFinite(expiresIn)
101
- ? input.now() + (expiresIn * 1000)
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
- ? errorDescription
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) {
@@ -15,7 +15,6 @@
15
15
  "build": "tsc"
16
16
  },
17
17
  "dependencies": {
18
- "auth-store": "*",
19
18
  "jose": "^6.1.2"
20
19
  },
21
20
  "devDependencies": {