poe-code 4.0.20 → 4.0.21

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/dist/index.js CHANGED
@@ -69270,6 +69270,35 @@ var init_authorization_state2 = __esm({
69270
69270
 
69271
69271
  // packages/poe-oauth/src/loopback-authorization.ts
69272
69272
  import http2 from "node:http";
69273
+ function validateOAuthAuthorizationCallback(options) {
69274
+ let url;
69275
+ try {
69276
+ url = typeof options.callbackUrl === "string" ? new URL(options.callbackUrl) : options.callbackUrl;
69277
+ } catch {
69278
+ throw new Error("Poe OAuth callbackUrl must be an absolute URL.");
69279
+ }
69280
+ const callback = {
69281
+ code: url.searchParams.get("code"),
69282
+ state: url.searchParams.get("state"),
69283
+ iss: url.searchParams.get("iss")
69284
+ };
69285
+ const expected = {
69286
+ state: validateExpectedState(options.expectedState),
69287
+ issuer: options.expectedIssuer ?? null,
69288
+ requireIssuer: options.requireIssuer ?? false
69289
+ };
69290
+ validateAuthorizationCallbackBinding2(callback, expected);
69291
+ const authorizationError = url.searchParams.get("error");
69292
+ if (authorizationError !== null) {
69293
+ const description = url.searchParams.get("error_description");
69294
+ const safeError = sanitizeAuthorizationError(authorizationError, expected.state);
69295
+ const safeDescription = description === null ? null : sanitizeAuthorizationError(description, expected.state);
69296
+ throw new Error(
69297
+ safeDescription === null ? `OAuth authorization failed: ${safeError}` : `OAuth authorization failed: ${safeError} \u2014 ${safeDescription}`
69298
+ );
69299
+ }
69300
+ return validateAuthorizationCallbackParameters2(callback, expected);
69301
+ }
69273
69302
  async function createLoopbackAuthorizationSession2(options = {}) {
69274
69303
  const callbackPath = options.callbackPath ?? "/callback";
69275
69304
  const server = options.createServer ? options.createServer() : http2.createServer();
@@ -69316,45 +69345,24 @@ function waitForAuthorizationCode2(server, authorizationUrl, options, callbackPa
69316
69345
  res.end("Not found");
69317
69346
  return;
69318
69347
  }
69319
- const error3 = url.searchParams.get("error");
69320
- if (error3 !== null) {
69321
- try {
69322
- validateAuthorizationCallbackBinding2(
69323
- {
69324
- state: url.searchParams.get("state"),
69325
- iss: url.searchParams.get("iss")
69326
- },
69327
- expectedAuthorization
69328
- );
69329
- } catch (validationError) {
69330
- res.writeHead(400);
69331
- res.end(
69332
- validationError instanceof Error ? validationError.message : "Invalid OAuth callback"
69333
- );
69334
- return;
69335
- }
69336
- const description = url.searchParams.get("error_description") ?? error3;
69337
- res.writeHead(400);
69338
- res.end(`Authorization failed: ${description}`);
69339
- settle(() => reject(new Error(`OAuth authorization failed: ${error3} \u2014 ${description}`)));
69340
- return;
69341
- }
69342
69348
  try {
69343
- const code = validateAuthorizationCallbackParameters2(
69344
- {
69345
- code: url.searchParams.get("code"),
69346
- state: url.searchParams.get("state"),
69347
- iss: url.searchParams.get("iss")
69348
- },
69349
- expectedAuthorization
69350
- );
69349
+ const code = validateOAuthAuthorizationCallback({
69350
+ callbackUrl: url,
69351
+ expectedState: expectedAuthorization.state ?? "",
69352
+ expectedIssuer: expectedAuthorization.issuer ?? void 0,
69353
+ requireIssuer: expectedAuthorization.requireIssuer
69354
+ });
69351
69355
  res.writeHead(200, { "Content-Type": "text/html" });
69352
69356
  res.end(buildSuccessPage2(options.landingPage));
69353
69357
  settle(() => resolve16(code));
69354
- } catch (error4) {
69358
+ } catch (error3) {
69359
+ const validationError = error3 instanceof Error ? error3 : new Error(String(error3));
69355
69360
  res.writeHead(400);
69356
- res.end(error4 instanceof Error ? error4.message : "Invalid OAuth callback");
69357
- settle(() => reject(error4 instanceof Error ? error4 : new Error(String(error4))));
69361
+ res.end(validationError.message);
69362
+ if (url.searchParams.has("error") && !validationError.message.startsWith("OAuth authorization failed:")) {
69363
+ return;
69364
+ }
69365
+ settle(() => reject(validationError));
69358
69366
  }
69359
69367
  });
69360
69368
  if (options.readLine !== void 0) {
@@ -69439,6 +69447,18 @@ function validateAuthorizationCallbackBinding2(callback, expected) {
69439
69447
  throw new Error("OAuth callback issuer mismatch");
69440
69448
  }
69441
69449
  }
69450
+ function validateExpectedState(state) {
69451
+ if (state.length === 0 || state.trim() !== state) {
69452
+ throw new Error("Poe OAuth expectedState must not be blank or contain surrounding whitespace.");
69453
+ }
69454
+ return state;
69455
+ }
69456
+ function sanitizeAuthorizationError(value, expectedState) {
69457
+ if (value.includes(expectedState)) {
69458
+ return "authorization_error";
69459
+ }
69460
+ return value;
69461
+ }
69442
69462
  function escapeHtml4(text5) {
69443
69463
  return text5.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
69444
69464
  }
@@ -69468,7 +69488,33 @@ function generateCodeVerifier2() {
69468
69488
  return crypto7.randomBytes(32).toString("base64url");
69469
69489
  }
69470
69490
  function generateCodeChallenge2(verifier) {
69471
- return crypto7.createHash("sha256").update(verifier).digest("base64url");
69491
+ return crypto7.createHash("sha256").update(validateCodeVerifier(verifier)).digest("base64url");
69492
+ }
69493
+ function validateCodeVerifier(verifier) {
69494
+ if (verifier.length < 43 || verifier.length > 128 || !hasOnlyPkceCharacters(verifier)) {
69495
+ throw new Error(
69496
+ "Poe OAuth codeVerifier must contain 43 to 128 URL-safe PKCE characters."
69497
+ );
69498
+ }
69499
+ return verifier;
69500
+ }
69501
+ function validateCodeChallenge(challenge) {
69502
+ if (challenge.length !== 43 || !hasOnlyPkceCharacters(challenge)) {
69503
+ throw new Error("Poe OAuth codeChallenge must contain 43 URL-safe PKCE characters.");
69504
+ }
69505
+ return challenge;
69506
+ }
69507
+ function hasOnlyPkceCharacters(value) {
69508
+ for (const character of value) {
69509
+ const code = character.charCodeAt(0);
69510
+ const isDigit6 = code >= 48 && code <= 57;
69511
+ const isUppercase = code >= 65 && code <= 90;
69512
+ const isLowercase = code >= 97 && code <= 122;
69513
+ if (!isDigit6 && !isUppercase && !isLowercase && character !== "-" && character !== "_") {
69514
+ return false;
69515
+ }
69516
+ }
69517
+ return true;
69472
69518
  }
69473
69519
  var init_pkce2 = __esm({
69474
69520
  "packages/poe-oauth/src/pkce.ts"() {
@@ -69482,7 +69528,15 @@ function createOAuthClient(config2) {
69482
69528
  const clientId = validateClientId(config2.clientId);
69483
69529
  const normalizedConfig = {
69484
69530
  ...config2,
69485
- clientId
69531
+ clientId,
69532
+ authorizationEndpoint: validateHttpUrl(
69533
+ config2.authorizationEndpoint ?? DEFAULT_AUTHORIZATION_ENDPOINT,
69534
+ "authorizationEndpoint"
69535
+ ),
69536
+ tokenEndpoint: validateHttpUrl(
69537
+ config2.tokenEndpoint ?? DEFAULT_TOKEN_ENDPOINT,
69538
+ "tokenEndpoint"
69539
+ )
69486
69540
  };
69487
69541
  return {
69488
69542
  authorize: () => startAuthorization(normalizedConfig, fetchFn)
@@ -69506,8 +69560,8 @@ async function startAuthorization(config2, fetchFn) {
69506
69560
  }
69507
69561
  });
69508
69562
  const redirectUri = loopbackSession.redirectUri;
69509
- const authorizationUrl = buildAuthorizationUrl2({
69510
- endpoint: authorizationEndpoint,
69563
+ const authorizationUrl = createOAuthAuthorizationUrl({
69564
+ authorizationEndpoint,
69511
69565
  clientId: config2.clientId,
69512
69566
  redirectUri,
69513
69567
  codeChallenge,
@@ -69521,13 +69575,13 @@ async function startAuthorization(config2, fetchFn) {
69521
69575
  resultPromise ??= (async () => {
69522
69576
  try {
69523
69577
  const code = await loopbackSession.waitForCode(authorizationUrl);
69524
- return await exchangeCodeForApiKey({
69578
+ return await exchangeOAuthCode({
69525
69579
  tokenEndpoint,
69526
69580
  code,
69527
69581
  codeVerifier,
69528
69582
  clientId: config2.clientId,
69529
69583
  redirectUri,
69530
- fetchFn
69584
+ fetch: fetchFn
69531
69585
  });
69532
69586
  } finally {
69533
69587
  loopbackSession.close();
@@ -69537,40 +69591,65 @@ async function startAuthorization(config2, fetchFn) {
69537
69591
  };
69538
69592
  return { authorizationUrl, waitForResult: waitForResult3 };
69539
69593
  }
69540
- function buildAuthorizationUrl2(params) {
69541
- const url = new URL(params.endpoint);
69594
+ function createOAuthAuthorizationUrl(options) {
69595
+ const clientId = validateClientId(options.clientId);
69596
+ const redirectUri = validateHttpUrl(options.redirectUri, "redirectUri");
69597
+ const state = validateOpaqueValue(options.state, "state");
69598
+ const codeChallenge = validateCodeChallenge(options.codeChallenge);
69599
+ const authorizationEndpoint = validateHttpUrl(
69600
+ options.authorizationEndpoint ?? DEFAULT_AUTHORIZATION_ENDPOINT,
69601
+ "authorizationEndpoint"
69602
+ );
69603
+ const url = new URL(authorizationEndpoint);
69542
69604
  url.searchParams.set("response_type", "code");
69543
- url.searchParams.set("client_id", params.clientId);
69605
+ url.searchParams.set("client_id", clientId);
69544
69606
  url.searchParams.set("scope", "apikey:create");
69545
- url.searchParams.set("code_challenge", params.codeChallenge);
69607
+ url.searchParams.set("code_challenge", codeChallenge);
69546
69608
  url.searchParams.set("code_challenge_method", "S256");
69547
- url.searchParams.set("redirect_uri", params.redirectUri);
69548
- url.searchParams.set("state", params.state);
69609
+ url.searchParams.set("redirect_uri", redirectUri);
69610
+ url.searchParams.set("state", state);
69549
69611
  return url.toString();
69550
69612
  }
69551
- async function exchangeCodeForApiKey(params) {
69613
+ async function exchangeOAuthCode(options) {
69614
+ const clientId = validateClientId(options.clientId);
69615
+ const redirectUri = validateHttpUrl(options.redirectUri, "redirectUri");
69616
+ const code = validateOpaqueValue(options.code, "code");
69617
+ const codeVerifier = validateCodeVerifier(options.codeVerifier);
69618
+ const tokenEndpoint = validateHttpUrl(
69619
+ options.tokenEndpoint ?? DEFAULT_TOKEN_ENDPOINT,
69620
+ "tokenEndpoint"
69621
+ );
69622
+ const fetchFn = options.fetch ?? globalThis.fetch;
69552
69623
  const body = new URLSearchParams({
69553
69624
  grant_type: "authorization_code",
69554
- code: params.code,
69555
- code_verifier: params.codeVerifier,
69556
- client_id: params.clientId,
69557
- redirect_uri: params.redirectUri
69558
- });
69559
- const response = await params.fetchFn(params.tokenEndpoint, {
69560
- method: "POST",
69561
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
69562
- body: body.toString()
69625
+ code,
69626
+ code_verifier: codeVerifier,
69627
+ client_id: clientId,
69628
+ redirect_uri: redirectUri
69563
69629
  });
69630
+ let response;
69631
+ try {
69632
+ response = await fetchFn(tokenEndpoint, {
69633
+ method: "POST",
69634
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
69635
+ body: body.toString()
69636
+ });
69637
+ } catch {
69638
+ throw new Error("Token exchange request failed");
69639
+ }
69564
69640
  if (!response.ok) {
69565
69641
  const text5 = await response.text();
69566
69642
  const description = parseErrorDescription(text5);
69567
- throw new Error(description ?? `Token exchange failed (${response.status}): ${text5}`);
69643
+ if (description !== null && !description.includes(codeVerifier) && !description.includes(code)) {
69644
+ throw new Error(description);
69645
+ }
69646
+ throw new Error(`Token exchange failed (${response.status})`);
69568
69647
  }
69569
69648
  let value;
69570
69649
  try {
69571
69650
  value = await response.json();
69572
69651
  } catch (error3) {
69573
- throw new Error(`Token exchange failed: invalid JSON response from ${params.tokenEndpoint}`, {
69652
+ throw new Error(`Token exchange failed: invalid JSON response from ${tokenEndpoint}`, {
69574
69653
  cause: error3
69575
69654
  });
69576
69655
  }
@@ -69626,6 +69705,27 @@ function validateClientId(clientId) {
69626
69705
  }
69627
69706
  return clientId;
69628
69707
  }
69708
+ function validateHttpUrl(value, field) {
69709
+ if (value.trim() !== value || value.length === 0) {
69710
+ throw new Error(`Poe OAuth ${field} must be an absolute HTTP(S) URL.`);
69711
+ }
69712
+ let url;
69713
+ try {
69714
+ url = new URL(value);
69715
+ } catch {
69716
+ throw new Error(`Poe OAuth ${field} must be an absolute HTTP(S) URL.`);
69717
+ }
69718
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.hash.length > 0) {
69719
+ throw new Error(`Poe OAuth ${field} must be an absolute HTTP(S) URL without a fragment.`);
69720
+ }
69721
+ return url.toString();
69722
+ }
69723
+ function validateOpaqueValue(value, field) {
69724
+ if (value.length === 0 || value.trim() !== value) {
69725
+ throw new Error(`Poe OAuth ${field} must not be blank or contain surrounding whitespace.`);
69726
+ }
69727
+ return value;
69728
+ }
69629
69729
  function isValidExpiresIn(value) {
69630
69730
  if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
69631
69731
  return false;
@@ -69652,6 +69752,8 @@ var init_src30 = __esm({
69652
69752
  "use strict";
69653
69753
  init_check_auth();
69654
69754
  init_oauth_client();
69755
+ init_loopback_authorization2();
69756
+ init_pkce2();
69655
69757
  }
69656
69758
  });
69657
69759
 
@@ -151176,7 +151278,7 @@ var init_package2 = __esm({
151176
151278
  "package.json"() {
151177
151279
  package_default2 = {
151178
151280
  name: "poe-code",
151179
- version: "4.0.20",
151281
+ version: "4.0.21",
151180
151282
  description: "CLI tool to configure Poe API for developer workflows.",
151181
151283
  license: "MIT",
151182
151284
  type: "module",