@deque/axe-auth 1.7.1-next.783fc70a → 1.7.1-next.81f1793b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -95,7 +95,7 @@ Under npm the wrapped process inherits the values directly and the listener stay
95
95
  npx @deque/axe-auth run -- npx axe-mcp-server
96
96
  ```
97
97
 
98
- `run` picks a free loopback port per session; pin one with `AXE_TOKEN_REFRESH_PORT` or `--port` only for containers, which can reach only a port you publish. `run` generates the shared secret unless you pin one with `AXE_TOKEN_REFRESH_SECRET` / `--secret`. The secret authenticates the push; the server's listener binds loopback by default and never starts without a secret.
98
+ `run` picks a free loopback port per session; pin one with `AXE_TOKEN_REFRESH_PORT` or `--port` only for containers, which can reach only a port you publish. `run` generates the shared secret unless you pin one with `AXE_TOKEN_REFRESH_SECRET`. `--secret` also works, but other local users can read it in the process list, so `run` warns when you use it. The secret authenticates the push; the server's listener binds loopback by default and never starts without a secret.
99
99
 
100
100
  ## Architecture
101
101
 
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.confirm = confirm;
4
7
  const node_readline_1 = require("node:readline");
8
+ const toTerminalText_1 = __importDefault(require("../toTerminalText"));
5
9
  /**
6
10
  * Reads a single line from `input` and returns `true` for an
7
11
  * affirmative answer (`y` / `yes`, case-insensitive), `false`
@@ -11,9 +15,8 @@ const node_readline_1 = require("node:readline");
11
15
  async function confirm(options) {
12
16
  const input = options.input ?? process.stdin;
13
17
  const output = options.output ?? process.stderr;
14
- const question = options.question.endsWith(" ")
15
- ? options.question
16
- : `${options.question} `;
18
+ const text = (0, toTerminalText_1.default)(options.question);
19
+ const question = text.endsWith(" ") ? text : `${text} `;
17
20
  // Use createInterface rather than the higher-level `rl.question`
18
21
  // promise API because the latter wires up SIGINT handling that
19
22
  // doesn't compose well when this is called from a CLI dispatcher
@@ -12,6 +12,8 @@ const tokenStore_1 = require("../oauth/tokenStore");
12
12
  const confirm_1 = require("../cli/confirm");
13
13
  const login_help_1 = require("./login.help");
14
14
  const errors_2 = require("../cli/errors");
15
+ const toTerminalText_1 = __importDefault(require("../toTerminalText"));
16
+ const toPrintableASCII_1 = __importDefault(require("../toPrintableASCII"));
15
17
  /** `axe-auth login` — drive the OAuth flow and persist tokens. */
16
18
  const loginCommand = {
17
19
  name: "login",
@@ -56,20 +58,28 @@ const loginCommand = {
56
58
  if (stored.ok) {
57
59
  const sameIssuer = stored.entry.issuerURL === issuerURL &&
58
60
  stored.entry.clientId === clientId;
61
+ // The issuer and client ID come from the axe server's SSO config, so
62
+ // keep each to one printable line wherever they are shown.
63
+ const shown = {
64
+ issuer: (0, toPrintableASCII_1.default)(issuerURL),
65
+ client: (0, toPrintableASCII_1.default)(clientId),
66
+ storedIssuer: (0, toPrintableASCII_1.default)(stored.entry.issuerURL),
67
+ storedClient: (0, toPrintableASCII_1.default)(stored.entry.clientId),
68
+ };
59
69
  if (!isInteractive) {
60
70
  throw new errors_2.CLIError("ALREADY_AUTHENTICATED", sameIssuer
61
- ? `Already authenticated against ${issuerURL}. Re-run with --force to override.`
71
+ ? `Already authenticated against ${shown.issuer}. Re-run with --force to override.`
62
72
  : (0, ts_dedent_1.dedent) `
63
- Currently authenticated against ${stored.entry.issuerURL}.
64
- Logging in to ${issuerURL} would replace those tokens.
73
+ Currently authenticated against ${shown.storedIssuer}.
74
+ Logging in to ${shown.issuer} would replace those tokens.
65
75
  Re-run with --force to override.
66
76
  `);
67
77
  }
68
78
  const prompt = sameIssuer
69
- ? `Already authenticated against ${issuerURL}. Re-authenticate? [y/N]`
79
+ ? `Already authenticated against ${shown.issuer}. Re-authenticate? [y/N]`
70
80
  : (0, ts_dedent_1.dedent) `
71
- Currently authenticated against ${stored.entry.issuerURL} (client ${stored.entry.clientId}).
72
- Logging in to ${issuerURL} (client ${clientId}) will replace those tokens.
81
+ Currently authenticated against ${shown.storedIssuer} (client ${shown.storedClient}).
82
+ Logging in to ${shown.issuer} (client ${shown.client}) will replace those tokens.
73
83
  Continue? [y/N]
74
84
  `;
75
85
  const ok = await confirmFn(prompt);
@@ -89,10 +99,10 @@ const loginCommand = {
89
99
  allowInsecureIssuer: args.allowInsecureIssuer,
90
100
  tokenStore,
91
101
  onAuthorizationURL: (url) => {
92
- deps.stderr.write(`Authorization URL: ${url}\n`);
102
+ deps.stderr.write(`Authorization URL: ${(0, toTerminalText_1.default)(url)}\n`);
93
103
  },
94
104
  onWarning: (msg) => {
95
- deps.stderr.write(`axe-auth: ${msg}\n`);
105
+ deps.stderr.write(`axe-auth: ${(0, toTerminalText_1.default)(msg)}\n`);
96
106
  },
97
107
  });
98
108
  }
@@ -1,10 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  const discoverOIDC_1 = require("../oauth/discoverOIDC");
4
7
  const revokeToken_1 = require("../oauth/revokeToken");
5
8
  const tokenStore_1 = require("../oauth/tokenStore");
6
9
  const logout_help_1 = require("./logout.help");
7
10
  const errors_1 = require("../cli/errors");
11
+ const toTerminalText_1 = __importDefault(require("../toTerminalText"));
8
12
  /** `axe-auth logout` — best-effort revoke + always clear local. */
9
13
  const logoutCommand = {
10
14
  name: "logout",
@@ -50,7 +54,7 @@ const logoutCommand = {
50
54
  }
51
55
  }
52
56
  catch (err) {
53
- deps.stderr.write(`axe-auth: server-side revocation failed (${(0, errors_1.describeError)(err)}). Continuing with local clear.\n`);
57
+ deps.stderr.write(`axe-auth: server-side revocation failed (${(0, toTerminalText_1.default)((0, errors_1.describeError)(err))}). Continuing with local clear.\n`);
54
58
  }
55
59
  }
56
60
  await clearLocal(tokenStore);
@@ -9,6 +9,7 @@ const runSession_1 = __importDefault(require("../run/runSession"));
9
9
  const findFreePort_1 = __importDefault(require("../run/findFreePort"));
10
10
  const run_help_1 = require("./run.help");
11
11
  const errors_1 = require("../cli/errors");
12
+ const toTerminalText_1 = __importDefault(require("../toTerminalText"));
12
13
  /** Container runtimes whose child cannot reach a host port it was not told to publish. */
13
14
  const CONTAINER_COMMANDS = new Set(["docker", "podman", "nerdctl"]);
14
15
  /** Whether `command` launches a container runtime rather than a local process. */
@@ -45,13 +46,16 @@ async function dispatchRun(rest, deps) {
45
46
  }).values;
46
47
  }
47
48
  catch (err) {
48
- deps.stderr.write(`${(0, errors_1.describeError)(err)}\n`);
49
+ deps.stderr.write(`${(0, toTerminalText_1.default)((0, errors_1.describeError)(err))}\n`);
49
50
  return 2;
50
51
  }
51
52
  if (values.help) {
52
53
  deps.stdout.write(`${run_help_1.HELP_RUN}\n`);
53
54
  return 0;
54
55
  }
56
+ if (values.secret !== undefined) {
57
+ deps.stderr.write("axe-auth run: --secret is visible to other local users via the process list; set AXE_TOKEN_REFRESH_SECRET instead\n");
58
+ }
55
59
  if (childArgv.length === 0) {
56
60
  deps.stderr.write("axe-auth run needs a command after `--`, e.g. `npx @deque/axe-auth run -- docker run ... axe-mcp-server`\n");
57
61
  return 2;
@@ -64,7 +68,7 @@ async function dispatchRun(rest, deps) {
64
68
  if (portRaw) {
65
69
  port = Number(portRaw);
66
70
  if (!Number.isInteger(port) || port <= 0 || port >= 65536) {
67
- deps.stderr.write(`Invalid port ${portRaw}: must be a TCP port (1-65535)\n`);
71
+ deps.stderr.write(`Invalid port ${(0, toTerminalText_1.default)(portRaw)}: must be a TCP port (1-65535)\n`);
68
72
  return 2;
69
73
  }
70
74
  }
@@ -99,10 +103,10 @@ async function dispatchRun(rest, deps) {
99
103
  }
100
104
  catch (err) {
101
105
  if (err instanceof errors_1.CLIError) {
102
- deps.stderr.write(`${err.message}\n`);
106
+ deps.stderr.write(`${(0, toTerminalText_1.default)(err.message)}\n`);
103
107
  return err.exitCode;
104
108
  }
105
- deps.stderr.write(`${(0, errors_1.describeError)(err)}\n`);
109
+ deps.stderr.write(`${(0, toTerminalText_1.default)((0, errors_1.describeError)(err))}\n`);
106
110
  return 2;
107
111
  }
108
112
  }
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ const token_1 = __importDefault(require("./commands/token"));
15
15
  const run_1 = require("./commands/run");
16
16
  const errors_1 = require("./cli/errors");
17
17
  const safeExit_1 = require("./cli/safeExit");
18
+ const toTerminalText_1 = __importDefault(require("./toTerminalText"));
18
19
  const pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "..", "package.json"), "utf-8"));
19
20
  const COMMANDS = [
20
21
  login_1.default,
@@ -60,7 +61,7 @@ async function dispatch(argv) {
60
61
  return 0;
61
62
  }
62
63
  if (first.startsWith("-")) {
63
- process.stderr.write(`Unknown option: ${first}. Run \`axe-auth --help\` for usage.\n`);
64
+ process.stderr.write(`Unknown option: ${(0, toTerminalText_1.default)(first)}. Run \`axe-auth --help\` for usage.\n`);
64
65
  return 2;
65
66
  }
66
67
  // `run` wraps a launch command after `--`; it can't go through the shared
@@ -74,7 +75,7 @@ async function dispatch(argv) {
74
75
  }
75
76
  const command = findCommand(first);
76
77
  if (!command) {
77
- process.stderr.write(`Unknown command: ${first}. Run \`axe-auth --help\` for usage.\n`);
78
+ process.stderr.write(`Unknown command: ${(0, toTerminalText_1.default)(first)}. Run \`axe-auth --help\` for usage.\n`);
78
79
  return 2;
79
80
  }
80
81
  let parsed;
@@ -91,7 +92,7 @@ async function dispatch(argv) {
91
92
  });
92
93
  }
93
94
  catch (err) {
94
- process.stderr.write(`${(0, errors_1.describeError)(err)}\n`);
95
+ process.stderr.write(`${(0, toTerminalText_1.default)((0, errors_1.describeError)(err))}\n`);
95
96
  return 2;
96
97
  }
97
98
  if (parsed.values.help) {
@@ -121,7 +122,7 @@ async function dispatch(argv) {
121
122
  common = (0, commonArgs_1.parseCommonArgs)(parsed.values, process.env, defaults);
122
123
  }
123
124
  catch (err) {
124
- process.stderr.write(`${(0, errors_1.describeError)(err)}\n`);
125
+ process.stderr.write(`${(0, toTerminalText_1.default)((0, errors_1.describeError)(err))}\n`);
125
126
  return 2;
126
127
  }
127
128
  const deps = {
@@ -136,14 +137,14 @@ async function dispatch(argv) {
136
137
  }
137
138
  catch (err) {
138
139
  if (err instanceof errors_1.CLIError) {
139
- process.stderr.write(`${err.message}\n`);
140
+ process.stderr.write(`${(0, toTerminalText_1.default)(err.message)}\n`);
140
141
  return err.exitCode;
141
142
  }
142
- process.stderr.write(`${(0, errors_1.describeError)(err)}\n`);
143
+ process.stderr.write(`${(0, toTerminalText_1.default)((0, errors_1.describeError)(err))}\n`);
143
144
  return 2;
144
145
  }
145
146
  }
146
147
  dispatch(process.argv.slice(2)).then((code) => (0, safeExit_1.safeExit)(code), (err) => {
147
- process.stderr.write(`${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
148
+ process.stderr.write(`${(0, toTerminalText_1.default)(err instanceof Error ? (err.stack ?? err.message) : String(err))}\n`);
148
149
  return (0, safeExit_1.safeExit)(1);
149
150
  });
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.buildAuthorizationURL = buildAuthorizationURL;
4
7
  const errors_1 = require("./errors");
8
+ const toPrintableASCII_1 = __importDefault(require("../toPrintableASCII"));
5
9
  // Pre-existing values for these on the authorization endpoint are a
6
10
  // security trap: the auth server's disambiguation is unspecified.
7
11
  const OAUTH_REQUIRED_PARAMS = [
@@ -29,7 +33,7 @@ function buildAuthorizationURL(options) {
29
33
  collisions.push(name);
30
34
  }
31
35
  if (collisions.length > 0) {
32
- throw new errors_1.OAuthFlowError("INVALID_AUTHORIZATION_ENDPOINT", `Authorization endpoint ${options.authorizationEndpoint} already carries OAuth-required param(s) ${collisions.join(", ")}; refusing to ship a URL the server may disambiguate unpredictably.`, { details: { params: collisions.join(",") } });
36
+ throw new errors_1.OAuthFlowError("INVALID_AUTHORIZATION_ENDPOINT", `Authorization endpoint ${(0, toPrintableASCII_1.default)(options.authorizationEndpoint)} already carries OAuth-required param(s) ${collisions.join(", ")}; refusing to ship a URL the server may disambiguate unpredictably.`, { details: { params: collisions.join(",") } });
33
37
  }
34
38
  // `set` each required OAuth param. Non-OAuth params the discovery
35
39
  // endpoint already carries (e.g. Keycloak's `kc_idp_hint=github`)
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.authorize = authorize;
4
7
  const callbackServer_1 = require("./callbackServer");
@@ -9,14 +12,16 @@ const openBrowser_1 = require("./openBrowser");
9
12
  const tokenExchange_1 = require("./tokenExchange");
10
13
  const tokenStore_1 = require("./tokenStore");
11
14
  const errors_1 = require("./errors");
15
+ const toTerminalText_1 = __importDefault(require("../toTerminalText"));
16
+ const toPrintableASCII_1 = __importDefault(require("../toPrintableASCII"));
12
17
  function defaultOnAuthorizationURL(url) {
13
18
  if (process.stderr.isTTY) {
14
- console.error(`Authorization URL: ${url}`);
19
+ console.error(`Authorization URL: ${(0, toTerminalText_1.default)(url)}`);
15
20
  }
16
21
  }
17
22
  function defaultOnWarning(message) {
18
23
  if (process.stderr.isTTY) {
19
- console.error(`axe-auth: ${message}`);
24
+ console.error(`axe-auth: ${(0, toTerminalText_1.default)(message)}`);
20
25
  }
21
26
  }
22
27
  /**
@@ -98,7 +103,7 @@ async function authorize(options) {
98
103
  // screen may have dropped the scope.
99
104
  if (scopes.includes("offline_access") && !tokens.refreshToken) {
100
105
  const grantedSuffix = tokens.grantedScope
101
- ? ` (server granted: ${tokens.grantedScope})`
106
+ ? ` (server granted: ${(0, toPrintableASCII_1.default)(tokens.grantedScope)})`
102
107
  : "";
103
108
  onWarning(`'offline_access' was requested but no refresh_token was returned${grantedSuffix}. Cross-session refresh will not be available.`);
104
109
  }
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.bindLoopback = bindLoopback;
4
7
  exports.startCallbackServer = startCallbackServer;
@@ -6,6 +9,7 @@ const node_events_1 = require("node:events");
6
9
  const node_http_1 = require("node:http");
7
10
  const errors_1 = require("./errors");
8
11
  const renderHTML_1 = require("./renderHTML");
12
+ const toPrintableASCII_1 = __importDefault(require("../toPrintableASCII"));
9
13
  const DEFAULT_TIMEOUT_MS = 120_000;
10
14
  const LOOPBACK_ADDRESSES = new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
11
15
  function isLoopback(addr) {
@@ -177,10 +181,24 @@ async function startCallbackServer(opts) {
177
181
  }));
178
182
  return;
179
183
  }
184
+ const code = url.searchParams.get("code");
185
+ const state = url.searchParams.get("state");
186
+ // Checked before `error` so that only the authorization server, which
187
+ // echoes `state` on error redirects too (RFC 6749 §4.1.2.1), gets its
188
+ // error text shown to the user.
189
+ if (state !== expectedState) {
190
+ const error = new errors_1.OAuthCallbackError("STATE_MISMATCH", "Authorization response 'state' did not match expected value.");
191
+ deferSettleOnClose(res, () => rejectResult(error));
192
+ writeHTML(res, 400, (0, renderHTML_1.renderHTML)({
193
+ kind: "error",
194
+ reason: "The authorization response failed state validation.",
195
+ }));
196
+ return;
197
+ }
180
198
  const providerError = url.searchParams.get("error");
181
199
  if (providerError) {
182
200
  const description = url.searchParams.get("error_description") ?? undefined;
183
- const error = new errors_1.OAuthCallbackError("PROVIDER_ERROR", `Authorization server returned error: ${providerError}`, {
201
+ const error = new errors_1.OAuthCallbackError("PROVIDER_ERROR", `Authorization server returned error: ${(0, toPrintableASCII_1.default)(providerError)}`, {
184
202
  details: description
185
203
  ? { error: providerError, error_description: description }
186
204
  : { error: providerError },
@@ -193,8 +211,6 @@ async function startCallbackServer(opts) {
193
211
  }));
194
212
  return;
195
213
  }
196
- const code = url.searchParams.get("code");
197
- const state = url.searchParams.get("state");
198
214
  if (!code) {
199
215
  const error = new errors_1.OAuthCallbackError("MISSING_CODE", "Authorization response missing 'code' parameter.");
200
216
  deferSettleOnClose(res, () => rejectResult(error));
@@ -204,15 +220,6 @@ async function startCallbackServer(opts) {
204
220
  }));
205
221
  return;
206
222
  }
207
- if (state !== expectedState) {
208
- const error = new errors_1.OAuthCallbackError("STATE_MISMATCH", "Authorization response 'state' did not match expected value.");
209
- deferSettleOnClose(res, () => rejectResult(error));
210
- writeHTML(res, 400, (0, renderHTML_1.renderHTML)({
211
- kind: "error",
212
- reason: "The authorization response failed state validation.",
213
- }));
214
- return;
215
- }
216
223
  deferSettleOnClose(res, () => resolveResult({ code, state }));
217
224
  writeHTML(res, 200, (0, renderHTML_1.renderHTML)({ kind: "success" }));
218
225
  };
@@ -1,10 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.discoverOIDC = discoverOIDC;
4
7
  const errors_1 = require("./errors");
5
8
  const issuerURL_1 = require("./issuerURL");
6
9
  const predicates_1 = require("./predicates");
7
10
  const userAgent_1 = require("../userAgent");
11
+ const toPrintableASCII_1 = __importDefault(require("../toPrintableASCII"));
8
12
  const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
9
13
  function optionalString(v) {
10
14
  return (0, predicates_1.isNonEmptyString)(v) ? v : undefined;
@@ -16,7 +20,7 @@ function assertSecureURL(url, label, allowInsecurePermitted) {
16
20
  parsed = new URL(url);
17
21
  }
18
22
  catch {
19
- throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `${label} is not a valid URL: ${url}`);
23
+ throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `${label} is not a valid URL: ${(0, toPrintableASCII_1.default)(url)}`);
20
24
  }
21
25
  if (parsed.protocol === "https:")
22
26
  return;
@@ -53,7 +57,7 @@ function parseConfiguration(body, url) {
53
57
  if (!(0, predicates_1.isNonEmptyString)(body.token_endpoint))
54
58
  missing.push("token_endpoint");
55
59
  if (missing.length > 0) {
56
- throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `OpenID configuration at ${url} is missing required field(s): ${missing.join(", ")}`);
60
+ throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `OpenID configuration at ${(0, toPrintableASCII_1.default)(url)} is missing required field(s): ${missing.join(", ")}`);
57
61
  }
58
62
  return {
59
63
  issuer: body.issuer,
@@ -75,7 +79,7 @@ function assertPKCESupport(body, url) {
75
79
  return;
76
80
  if (methods.includes("S256"))
77
81
  return;
78
- throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `OpenID configuration at ${url} advertises code_challenge_methods_supported = ${JSON.stringify(methods)}, but axe-auth requires S256 (PKCE per RFC 7636). The OAuth client used by axe-auth needs PKCE enabled, or you may be on an axe server version that predates OAuth-based MCP authentication.`);
82
+ throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `OpenID configuration at ${(0, toPrintableASCII_1.default)(url)} advertises code_challenge_methods_supported = ${(0, toPrintableASCII_1.default)(JSON.stringify(methods))}, but axe-auth requires S256 (PKCE per RFC 7636). The OAuth client used by axe-auth needs PKCE enabled, or you may be on an axe server version that predates OAuth-based MCP authentication.`);
79
83
  }
80
84
  /**
81
85
  * Fetches and parses the OIDC discovery document. Fails fast (no
@@ -101,20 +105,20 @@ async function discoverOIDC(issuerURL, options = {}) {
101
105
  });
102
106
  }
103
107
  catch (cause) {
104
- throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `Could not reach the authentication server at ${url}. Check the URL and your network connection.`, { cause });
108
+ throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `Could not reach the authentication server at ${(0, toPrintableASCII_1.default)(url)}. Check the URL and your network connection.`, { cause });
105
109
  }
106
110
  if (!response.ok) {
107
- throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `Authentication server at ${url} responded with HTTP ${response.status}. Check the issuer URL.`);
111
+ throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `Authentication server at ${(0, toPrintableASCII_1.default)(url)} responded with HTTP ${response.status}. Check the issuer URL.`);
108
112
  }
109
113
  let body;
110
114
  try {
111
115
  body = await response.json();
112
116
  }
113
117
  catch (cause) {
114
- throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `Authentication server at ${url} did not return a valid JSON OpenID configuration`, { cause });
118
+ throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `Authentication server at ${(0, toPrintableASCII_1.default)(url)} did not return a valid JSON OpenID configuration`, { cause });
115
119
  }
116
120
  if (body === null || typeof body !== "object") {
117
- throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `OpenID configuration at ${url} was not a JSON object`);
121
+ throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `OpenID configuration at ${(0, toPrintableASCII_1.default)(url)} was not a JSON object`);
118
122
  }
119
123
  const config = parseConfiguration(body, url);
120
124
  // OIDC Discovery §3: the `issuer` value returned MUST equal the URL
@@ -124,7 +128,7 @@ async function discoverOIDC(issuerURL, options = {}) {
124
128
  // token endpoints to attacker hosts while still appearing to come
125
129
  // from the legitimate origin.
126
130
  if ((0, issuerURL_1.normalizeIssuerURL)(config.issuer) !== (0, issuerURL_1.normalizeIssuerURL)(issuerURL)) {
127
- throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `Issuer mismatch: requested ${issuerURL} but discovery document claims ${config.issuer}`);
131
+ throw new errors_1.OAuthFlowError("DISCOVERY_FAILED", `Issuer mismatch: requested ${(0, toPrintableASCII_1.default)(issuerURL)} but discovery document claims ${(0, toPrintableASCII_1.default)(config.issuer)}`);
128
132
  }
129
133
  // Enforce scheme on the endpoints the flow actually hits. A tampered
130
134
  // discovery response could claim the legitimate issuer while
@@ -2,11 +2,11 @@
2
2
  export type OAuthCallbackErrorCode =
3
3
  /** No callback arrived within `timeoutMs`; a retry is reasonable. */
4
4
  "TIMEOUT"
5
- /** Callback `state` did not match `expectedState`. Surface a generic failure — do NOT echo the expected value. */
5
+ /** Callback `state` did not match `expectedState`. Checked first, so it takes precedence over `?error=` and a missing `code`. Surface a generic failure — do NOT echo the expected value. */
6
6
  | "STATE_MISMATCH"
7
- /** Callback carried `?error=...`. `details` holds `error` and optionally `error_description` for display. */
7
+ /** Callback carried a matching `state` and `?error=...`. `details` holds the raw `error` and optional `error_description`. */
8
8
  | "PROVIDER_ERROR"
9
- /** Callback had no `code` parameter; treat as a malformed response. */
9
+ /** Callback carried a matching `state` but no `code`; treat as a malformed response. */
10
10
  | "MISSING_CODE"
11
11
  /** Could not bind the loopback port. No retry — environment likely blocked. */
12
12
  | "BIND_FAILED"
@@ -27,7 +27,11 @@ export interface OAuthCallbackErrorOptions {
27
27
  export declare class OAuthCallbackError extends Error {
28
28
  /** Discriminator for programmatic handling. */
29
29
  readonly code: OAuthCallbackErrorCode;
30
- /** Structured metadata carried alongside the error, if any. */
30
+ /**
31
+ * Raw, unsanitized values from the authorization server, for matching
32
+ * (e.g. `details.error === "access_denied"`). Not for display: unlike
33
+ * `message`, control characters are left in.
34
+ */
31
35
  readonly details?: Record<string, string>;
32
36
  constructor(code: OAuthCallbackErrorCode, message: string, options?: OAuthCallbackErrorOptions);
33
37
  }
@@ -71,7 +75,12 @@ export interface OAuthFlowErrorOptions {
71
75
  export declare class OAuthFlowError extends Error {
72
76
  /** Discriminator for programmatic handling. */
73
77
  readonly code: OAuthFlowErrorCode;
74
- /** Structured metadata carried alongside the error, if any. */
78
+ /**
79
+ * Structured metadata, for matching (e.g. `details.error ===
80
+ * "invalid_grant"`). Values from the authorization server are raw and
81
+ * unsanitized, so do not display them: unlike `message`, control
82
+ * characters are left in.
83
+ */
75
84
  readonly details?: Record<string, string>;
76
85
  constructor(code: OAuthFlowErrorCode, message: string, options?: OAuthFlowErrorOptions);
77
86
  }
@@ -9,7 +9,11 @@ exports.OAuthFlowError = exports.OAuthCallbackError = void 0;
9
9
  class OAuthCallbackError extends Error {
10
10
  /** Discriminator for programmatic handling. */
11
11
  code;
12
- /** Structured metadata carried alongside the error, if any. */
12
+ /**
13
+ * Raw, unsanitized values from the authorization server, for matching
14
+ * (e.g. `details.error === "access_denied"`). Not for display: unlike
15
+ * `message`, control characters are left in.
16
+ */
13
17
  details;
14
18
  constructor(code, message, options = {}) {
15
19
  super(message, { cause: options.cause });
@@ -36,7 +40,12 @@ exports.OAuthCallbackError = OAuthCallbackError;
36
40
  class OAuthFlowError extends Error {
37
41
  /** Discriminator for programmatic handling. */
38
42
  code;
39
- /** Structured metadata carried alongside the error, if any. */
43
+ /**
44
+ * Structured metadata, for matching (e.g. `details.error ===
45
+ * "invalid_grant"`). Values from the authorization server are raw and
46
+ * unsanitized, so do not display them: unlike `message`, control
47
+ * characters are left in.
48
+ */
40
49
  details;
41
50
  constructor(code, message, options = {}) {
42
51
  super(message, { cause: options.cause });
@@ -1,14 +1,19 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.getValidAccessToken = getValidAccessToken;
4
7
  const discoverOIDC_1 = require("./discoverOIDC");
5
8
  const errors_1 = require("./errors");
6
9
  const refreshTokens_1 = require("./refreshTokens");
10
+ const toPrintableASCII_1 = __importDefault(require("../toPrintableASCII"));
7
11
  const tokenStore_1 = require("./tokenStore");
12
+ const toTerminalText_1 = __importDefault(require("../toTerminalText"));
8
13
  const DEFAULT_EXPIRY_BUFFER_MS = 60_000;
9
14
  function defaultOnWarning(message) {
10
15
  if (process.stderr.isTTY) {
11
- console.error(`axe-auth: ${message}`);
16
+ console.error(`axe-auth: ${(0, toTerminalText_1.default)(message)}`);
12
17
  }
13
18
  }
14
19
  function isInvalidGrant(err) {
@@ -64,7 +69,7 @@ async function getValidAccessToken(options) {
64
69
  // server.
65
70
  if (loaded.entry.issuerURL !== issuerURL ||
66
71
  loaded.entry.clientId !== clientId) {
67
- throw notAuthenticated(`Stored credentials are for issuer ${loaded.entry.issuerURL} (client ${loaded.entry.clientId}), but the requested issuer is ${issuerURL} (client ${clientId}). Run \`axe-auth login\` to re-authenticate.`);
72
+ throw notAuthenticated(`Stored credentials are for issuer ${(0, toPrintableASCII_1.default)(loaded.entry.issuerURL)} (client ${(0, toPrintableASCII_1.default)(loaded.entry.clientId)}), but the requested issuer is ${(0, toPrintableASCII_1.default)(issuerURL)} (client ${(0, toPrintableASCII_1.default)(clientId)}). Run \`axe-auth login\` to re-authenticate.`);
68
73
  }
69
74
  const tokens = loaded.entry.tokens;
70
75
  if (now() + expiryBufferMs < tokens.expiresAt) {
@@ -1,10 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.refreshTokens = refreshTokens;
4
7
  const errors_1 = require("./errors");
5
8
  const retry_1 = require("./retry");
6
9
  const tokenResponse_1 = require("./tokenResponse");
7
10
  const userAgent_1 = require("../userAgent");
11
+ const toPrintableASCII_1 = __importDefault(require("../toPrintableASCII"));
8
12
  /**
9
13
  * Exchanges a refresh token for a fresh access token via RFC 6749 §6.
10
14
  *
@@ -44,11 +48,14 @@ async function refreshTokens(options) {
44
48
  "User-Agent": userAgent_1.USER_AGENT,
45
49
  },
46
50
  body,
51
+ // A 307/308 would replay the refresh token to a URL discovery never vetted;
52
+ // "manual" surfaces the 3xx as an HTTP error rather than a network one.
53
+ redirect: "manual",
47
54
  signal: options.signal,
48
55
  });
49
56
  }
50
57
  catch (cause) {
51
- throw new errors_1.OAuthFlowError("TOKEN_EXCHANGE_FAILED", `Could not reach the token endpoint at ${options.tokenEndpoint}. Check your network connection.`, { cause });
58
+ throw new errors_1.OAuthFlowError("TOKEN_EXCHANGE_FAILED", `Could not reach the token endpoint at ${(0, toPrintableASCII_1.default)(options.tokenEndpoint)}. Check your network connection.`, { cause });
52
59
  }
53
60
  if (!response.ok) {
54
61
  await (0, tokenResponse_1.throwTokenEndpointError)(response, "Token refresh");
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.revokeRefreshToken = revokeRefreshToken;
4
7
  const retry_1 = require("./retry");
5
8
  const userAgent_1 = require("../userAgent");
9
+ const toPrintableASCII_1 = __importDefault(require("../toPrintableASCII"));
6
10
  /**
7
11
  * Revokes a refresh token via RFC 7009. Servers SHOULD return 200
8
12
  * regardless of whether the token was valid (the spec doesn't want
@@ -34,12 +38,15 @@ async function revokeRefreshToken(options) {
34
38
  "User-Agent": userAgent_1.USER_AGENT,
35
39
  },
36
40
  body,
41
+ // A 307/308 would replay the refresh token to a URL discovery never vetted;
42
+ // "manual" surfaces the 3xx as an HTTP error rather than a network one.
43
+ redirect: "manual",
37
44
  signal: options.signal,
38
45
  });
39
46
  }
40
47
  catch (cause) {
41
48
  const reason = cause instanceof Error ? cause.message : String(cause);
42
- throw new Error(`Could not reach the revocation endpoint at ${options.revocationEndpoint}: ${reason}`, { cause });
49
+ throw new Error(`Could not reach the revocation endpoint at ${(0, toPrintableASCII_1.default)(options.revocationEndpoint)}: ${reason}`, { cause });
43
50
  }
44
51
  if (!response.ok) {
45
52
  // Deliberately do NOT include the response body. The request
@@ -59,6 +66,6 @@ async function revokeRefreshToken(options) {
59
66
  catch {
60
67
  // ignore — body is purely diagnostic
61
68
  }
62
- throw new Error(`Revocation endpoint at ${options.revocationEndpoint} returned HTTP ${response.status}`);
69
+ throw new Error(`Revocation endpoint at ${(0, toPrintableASCII_1.default)(options.revocationEndpoint)} returned HTTP ${response.status}`);
63
70
  }
64
71
  }
@@ -33,3 +33,23 @@ export declare const KEYCLOAK_ISSUER = "http://localhost:8080/auth/realms/local"
33
33
  * (Bearer, positive `expires_in`) are what most tests want.
34
34
  */
35
35
  export declare function tokenResponseBody(overrides?: Record<string, unknown>): Record<string, unknown>;
36
+ /** A loopback endpoint that answers every request with a 307 to a second server. */
37
+ export interface RedirectingEndpoint {
38
+ /** URL of the endpoint that issues the redirect. */
39
+ url: string;
40
+ /** Request bodies received by the redirect target. */
41
+ targetBodies: string[];
42
+ /** Stops both servers. */
43
+ close(): Promise<void>;
44
+ }
45
+ /**
46
+ * Starts two loopback servers: one that 307-redirects everything to the
47
+ * other, and a target that records each request body it receives.
48
+ */
49
+ export declare function startRedirectingEndpoint(): Promise<RedirectingEndpoint>;
50
+ /**
51
+ * Lists the control characters (C0, DEL, C1) in `text`, as code points, so
52
+ * a test can assert none reached a terminal-bound message. `allowed` names
53
+ * characters the caller expects to keep, such as `"\n"`.
54
+ */
55
+ export declare function controlCharacters(text: string, allowed?: string): number[];
@@ -7,6 +7,9 @@ exports.KEYCLOAK_ISSUER = exports.FIXED_NOW = void 0;
7
7
  exports.withFetch = withFetch;
8
8
  exports.jsonResponse = jsonResponse;
9
9
  exports.tokenResponseBody = tokenResponseBody;
10
+ exports.startRedirectingEndpoint = startRedirectingEndpoint;
11
+ exports.controlCharacters = controlCharacters;
12
+ const node_http_1 = require("node:http");
10
13
  /**
11
14
  * A fixed "now" timestamp used by token-endpoint tests that need
12
15
  * determinism for `expiresAt` assertions. Any constant would do;
@@ -59,3 +62,53 @@ function tokenResponseBody(overrides = {}) {
59
62
  ...overrides,
60
63
  };
61
64
  }
65
+ /**
66
+ * Starts two loopback servers: one that 307-redirects everything to the
67
+ * other, and a target that records each request body it receives.
68
+ */
69
+ async function startRedirectingEndpoint() {
70
+ const targetBodies = [];
71
+ const target = (0, node_http_1.createServer)((req, res) => {
72
+ let body = "";
73
+ req.on("data", (chunk) => (body += chunk));
74
+ req.on("end", () => {
75
+ targetBodies.push(body);
76
+ res.statusCode = 200;
77
+ res.setHeader("content-type", "application/json");
78
+ res.end(JSON.stringify(tokenResponseBody()));
79
+ });
80
+ });
81
+ await listen(target);
82
+ const targetURL = `http://127.0.0.1:${target.address().port}/elsewhere`;
83
+ const origin = (0, node_http_1.createServer)((req, res) => {
84
+ req.resume();
85
+ res.statusCode = 307;
86
+ res.setHeader("location", targetURL);
87
+ res.end();
88
+ });
89
+ await listen(origin);
90
+ return {
91
+ url: `http://127.0.0.1:${origin.address().port}/token`,
92
+ targetBodies,
93
+ close: async () => {
94
+ await Promise.all([target, origin].map(closeServer));
95
+ },
96
+ };
97
+ }
98
+ function listen(server) {
99
+ return new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
100
+ }
101
+ function closeServer(server) {
102
+ return new Promise((resolve) => server.close(() => resolve()));
103
+ }
104
+ /**
105
+ * Lists the control characters (C0, DEL, C1) in `text`, as code points, so
106
+ * a test can assert none reached a terminal-bound message. `allowed` names
107
+ * characters the caller expects to keep, such as `"\n"`.
108
+ */
109
+ function controlCharacters(text, allowed = "") {
110
+ return [...text]
111
+ .filter((c) => !allowed.includes(c))
112
+ .map((c) => c.codePointAt(0))
113
+ .filter((cp) => cp < 0x20 || (cp >= 0x7f && cp <= 0x9f));
114
+ }
@@ -1,10 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.exchangeCodeForTokens = exchangeCodeForTokens;
4
7
  const errors_1 = require("./errors");
5
8
  const retry_1 = require("./retry");
6
9
  const tokenResponse_1 = require("./tokenResponse");
7
10
  const userAgent_1 = require("../userAgent");
11
+ const toPrintableASCII_1 = __importDefault(require("../toPrintableASCII"));
8
12
  /**
9
13
  * Exchanges an authorization code for a `TokenSet` via the
10
14
  * authorization server's token endpoint (RFC 6749 §4.1.3 + RFC 7636
@@ -32,11 +36,14 @@ async function exchangeCodeForTokens(options) {
32
36
  "User-Agent": userAgent_1.USER_AGENT,
33
37
  },
34
38
  body,
39
+ // A 307/308 would replay the code verifier to a URL discovery never vetted;
40
+ // "manual" surfaces the 3xx as an HTTP error rather than a network one.
41
+ redirect: "manual",
35
42
  signal: options.signal,
36
43
  });
37
44
  }
38
45
  catch (cause) {
39
- throw new errors_1.OAuthFlowError("TOKEN_EXCHANGE_FAILED", `Could not reach the token endpoint at ${options.tokenEndpoint}. Check your network connection.`, { cause });
46
+ throw new errors_1.OAuthFlowError("TOKEN_EXCHANGE_FAILED", `Could not reach the token endpoint at ${(0, toPrintableASCII_1.default)(options.tokenEndpoint)}. Check your network connection.`, { cause });
40
47
  }
41
48
  if (!response.ok) {
42
49
  await (0, tokenResponse_1.throwTokenEndpointError)(response, "Token exchange");
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.throwTokenEndpointError = throwTokenEndpointError;
4
7
  exports.parseTokenResponse = parseTokenResponse;
5
8
  const errors_1 = require("./errors");
6
9
  const predicates_1 = require("./predicates");
10
+ const toPrintableASCII_1 = __importDefault(require("../toPrintableASCII"));
7
11
  // RFC 6749 §5.1 doesn't pin the JSON type and some providers send
8
12
  // numeric strings; accept both, reject non-positive or non-finite.
9
13
  function parseExpiresIn(v) {
@@ -43,8 +47,8 @@ async function throwTokenEndpointError(response, context) {
43
47
  const { error, description } = parseErrorBody(body);
44
48
  const suffix = error
45
49
  ? description
46
- ? `: ${error}: ${description}`
47
- : `: ${error}`
50
+ ? `: ${(0, toPrintableASCII_1.default)(error)}: ${(0, toPrintableASCII_1.default)(description)}`
51
+ : `: ${(0, toPrintableASCII_1.default)(error)}`
48
52
  : "";
49
53
  const details = {};
50
54
  if (error)
@@ -64,10 +68,10 @@ async function parseTokenResponse(response, issuedAt, endpointURL) {
64
68
  parsed = await response.json();
65
69
  }
66
70
  catch (cause) {
67
- throw new errors_1.OAuthFlowError("TOKEN_EXCHANGE_FAILED", `Token endpoint at ${endpointURL} returned a non-JSON response`, { cause });
71
+ throw new errors_1.OAuthFlowError("TOKEN_EXCHANGE_FAILED", `Token endpoint at ${(0, toPrintableASCII_1.default)(endpointURL)} returned a non-JSON response`, { cause });
68
72
  }
69
73
  if (parsed === null || typeof parsed !== "object") {
70
- throw new errors_1.OAuthFlowError("TOKEN_EXCHANGE_FAILED", `Token endpoint at ${endpointURL} returned a non-object response`);
74
+ throw new errors_1.OAuthFlowError("TOKEN_EXCHANGE_FAILED", `Token endpoint at ${(0, toPrintableASCII_1.default)(endpointURL)} returned a non-object response`);
71
75
  }
72
76
  const raw = parsed;
73
77
  if (!(0, predicates_1.isNonEmptyString)(raw.access_token)) {
@@ -85,7 +89,7 @@ async function parseTokenResponse(response, issuedAt, endpointURL) {
85
89
  throw new errors_1.OAuthFlowError("TOKEN_EXCHANGE_FAILED", `Token response missing required 'token_type'`);
86
90
  }
87
91
  if (raw.token_type.toLowerCase() !== "bearer") {
88
- throw new errors_1.OAuthFlowError("TOKEN_EXCHANGE_FAILED", `Unsupported token_type '${raw.token_type}'; this library only handles Bearer.`);
92
+ throw new errors_1.OAuthFlowError("TOKEN_EXCHANGE_FAILED", `Unsupported token_type '${(0, toPrintableASCII_1.default)(raw.token_type)}'; this library only handles Bearer.`);
89
93
  }
90
94
  const tokens = {
91
95
  accessToken: raw.access_token,
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.REFRESH_SECRET_HEADER = void 0;
4
7
  exports.default = pushToken;
8
+ const toPrintableASCII_1 = __importDefault(require("../toPrintableASCII"));
5
9
  /** Header carrying the shared secret on a token-refresh push. Matches the server's listener. */
6
10
  exports.REFRESH_SECRET_HEADER = "x-refresh-secret";
7
11
  /** Bound the push so a stalled server can't hang the caller. */
@@ -21,14 +25,10 @@ async function pushToken({ port, secret, accessToken, }) {
21
25
  });
22
26
  if (!res.ok) {
23
27
  // Whatever holds this port wrote the body, and it reaches the client's
24
- // stderr, so take a little of it and strip anything that could forge log
25
- // lines or redraw the terminal.
28
+ // stderr, so repeat only a little of it.
26
29
  const detail = await res
27
30
  .text()
28
- .then((text) => text
29
- .slice(0, MAX_DETAIL_LEN)
30
- .replace(/[^\t\x20-\x7e]/g, " ")
31
- .trim())
31
+ .then((text) => (0, toPrintableASCII_1.default)(text.slice(0, MAX_DETAIL_LEN)).trim())
32
32
  .catch(() => "");
33
33
  throw new Error(`server responded ${res.status}${detail ? `: ${detail}` : ""}`);
34
34
  }
@@ -44,6 +44,7 @@ const tokenStore_1 = require("../oauth/tokenStore");
44
44
  const errors_2 = require("../cli/errors");
45
45
  const supervise_1 = __importStar(require("./supervise"));
46
46
  const pushToken_1 = __importDefault(require("./pushToken"));
47
+ const toTerminalText_1 = __importDefault(require("../toTerminalText"));
47
48
  /**
48
49
  * Cadence of the refresh-and-push loop. Must be shorter than the access-token
49
50
  * TTL: each tick mints (which refreshes when the token is near expiry) and
@@ -130,7 +131,7 @@ async function runSession(options) {
130
131
  // louder line (they will keep failing every tick otherwise). The session
131
132
  // stays alive on its last good token.
132
133
  const noteRecoverableFailure = (message) => {
133
- stderr.write(`axe-auth run: token refresh failed: ${message}\n`);
134
+ stderr.write(`axe-auth run: token refresh failed: ${(0, toTerminalText_1.default)(message)}\n`);
134
135
  consecutiveFailures += 1;
135
136
  if (consecutiveFailures === CONSECUTIVE_FAILURE_WARNING_THRESHOLD) {
136
137
  stderr.write(`axe-auth run: token refresh has failed ${consecutiveFailures} times in a row; the server's access token will expire and calls will start failing — check that the refresh listener is reachable\n`);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Replaces everything outside printable ASCII (tab excepted) with a space,
3
+ * for remote-supplied text bound for the terminal: control sequences in it
4
+ * could forge log lines or redraw the screen.
5
+ */
6
+ export default function toPrintableASCII(text: string): string;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = toPrintableASCII;
4
+ /**
5
+ * Replaces everything outside printable ASCII (tab excepted) with a space,
6
+ * for remote-supplied text bound for the terminal: control sequences in it
7
+ * could forge log lines or redraw the screen.
8
+ */
9
+ function toPrintableASCII(text) {
10
+ return text.replace(/[^\t\x20-\x7e]/g, " ");
11
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Replaces control characters other than tab and newline, and bidirectional
3
+ * controls, with a space, for text bound for the terminal: control sequences
4
+ * in remote-supplied text could forge log lines, redraw the screen, or reorder
5
+ * what is displayed. Unlike `toPrintableASCII`,
6
+ * newlines and non-ASCII survive, so it can wrap the CLI's own multi-line
7
+ * messages at the point they are written.
8
+ */
9
+ export default function toTerminalText(text: string): string;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = toTerminalText;
4
+ /**
5
+ * Replaces control characters other than tab and newline, and bidirectional
6
+ * controls, with a space, for text bound for the terminal: control sequences
7
+ * in remote-supplied text could forge log lines, redraw the screen, or reorder
8
+ * what is displayed. Unlike `toPrintableASCII`,
9
+ * newlines and non-ASCII survive, so it can wrap the CLI's own multi-line
10
+ * messages at the point they are written.
11
+ */
12
+ function toTerminalText(text) {
13
+ return text.replace(/[^\t\n\x20-\x7e\u00a0-\uffff]|[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, " ");
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deque/axe-auth",
3
- "version": "1.7.1-next.783fc70a",
3
+ "version": "1.7.1-next.81f1793b",
4
4
  "description": "CLI authentication utility for Deque services",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {