@deque/axe-auth 1.1.0-next.fb07beab → 1.1.0-next.fea0aa8a
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 +59 -12
- package/credits.json +53 -0
- package/dist/cli/commonArgs.d.ts +35 -0
- package/dist/cli/commonArgs.help.d.ts +2 -0
- package/dist/cli/commonArgs.help.js +20 -0
- package/dist/cli/commonArgs.js +63 -0
- package/dist/cli/confirm.d.ts +17 -0
- package/dist/cli/confirm.js +53 -0
- package/dist/cli/errors.d.ts +13 -0
- package/dist/cli/errors.js +30 -0
- package/dist/cli/testUtils.d.ts +52 -0
- package/dist/cli/testUtils.js +100 -0
- package/dist/cli/types.d.ts +39 -0
- package/dist/cli/types.js +2 -0
- package/dist/commands/login.d.ts +41 -0
- package/dist/commands/login.help.d.ts +2 -0
- package/dist/commands/login.help.js +41 -0
- package/dist/commands/login.js +108 -0
- package/dist/commands/logout.d.ts +24 -0
- package/dist/commands/logout.help.d.ts +2 -0
- package/dist/commands/logout.help.js +38 -0
- package/dist/commands/logout.js +68 -0
- package/dist/commands/token.d.ts +21 -0
- package/dist/commands/token.help.d.ts +2 -0
- package/dist/commands/token.help.js +41 -0
- package/dist/commands/token.js +40 -0
- package/dist/index.js +107 -22
- package/dist/oauth/authorizationURL.d.ts +24 -0
- package/dist/oauth/authorizationURL.js +48 -0
- package/dist/oauth/authorize.d.ts +53 -0
- package/dist/oauth/authorize.js +117 -0
- package/dist/oauth/discoverOIDC.d.ts +33 -0
- package/dist/oauth/discoverOIDC.js +144 -0
- package/dist/oauth/discoverSSOConfig.d.ts +37 -0
- package/dist/oauth/discoverSSOConfig.js +105 -0
- package/dist/oauth/errors.d.ts +57 -2
- package/dist/oauth/errors.js +35 -1
- package/dist/oauth/getValidAccessToken.d.ts +54 -0
- package/dist/oauth/getValidAccessToken.js +131 -0
- package/dist/oauth/index.d.ts +14 -2
- package/dist/oauth/index.js +13 -1
- package/dist/oauth/issuerURL.d.ts +22 -0
- package/dist/oauth/issuerURL.js +38 -0
- package/dist/oauth/keyringBinding.d.ts +22 -0
- package/dist/oauth/keyringBinding.js +41 -0
- package/dist/oauth/openBrowser.d.ts +30 -0
- package/dist/oauth/openBrowser.js +95 -0
- package/dist/oauth/pkce.d.ts +17 -0
- package/dist/oauth/pkce.js +43 -0
- package/dist/oauth/predicates.d.ts +7 -0
- package/dist/oauth/predicates.js +15 -0
- package/dist/oauth/refreshTokens.d.ts +30 -0
- package/dist/oauth/refreshTokens.js +60 -0
- package/dist/oauth/revokeToken.d.ts +28 -0
- package/dist/oauth/revokeToken.js +63 -0
- package/dist/oauth/testUtils.d.ts +35 -0
- package/dist/oauth/testUtils.js +61 -0
- package/dist/oauth/tokenExchange.d.ts +26 -0
- package/dist/oauth/tokenExchange.js +44 -0
- package/dist/oauth/tokenResponse.d.ts +22 -0
- package/dist/oauth/tokenResponse.js +101 -0
- package/dist/oauth/tokenStore.d.ts +183 -0
- package/dist/oauth/tokenStore.js +560 -0
- package/dist/userAgent.d.ts +12 -0
- package/dist/userAgent.js +18 -0
- package/docs/architecture.md +201 -0
- package/docs/callback-page.md +24 -0
- package/docs/callback-server.md +21 -0
- package/docs/oauth-flow.md +15 -0
- package/package.json +19 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @deque/axe-auth
|
|
2
2
|
|
|
3
|
-
CLI for authenticating with Deque
|
|
3
|
+
CLI for authenticating with Deque services via the OAuth 2.0 Authorization Code + PKCE flow (RFC 6749, RFC 7636, RFC 8252 §7.3). Tokens are persisted to the OS keychain so subsequent invocations can refresh silently.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -17,22 +17,69 @@ npx @deque/axe-auth
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
axe-auth [options]
|
|
20
|
+
axe-auth <command> [options]
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Commands:
|
|
24
24
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
25
|
+
| Command | Description |
|
|
26
|
+
| -------- | ------------------------------------------------------------------------------- |
|
|
27
|
+
| `login` | Open a browser, complete the OAuth flow, persist tokens to the OS keychain. |
|
|
28
|
+
| `logout` | Revoke the stored refresh token server-side and clear the local keychain entry. |
|
|
29
|
+
| `token` | Print a currently-valid access token to stdout, refreshing silently if needed. |
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
Run `axe-auth <command> --help` for command-specific options.
|
|
32
|
+
|
|
33
|
+
### Common configuration
|
|
34
|
+
|
|
35
|
+
`axe-auth` discovers its OAuth coordinates by calling `<server>/api/sso-config` on the axe server. Users only supply (or default to) the axe server URL — never the underlying Keycloak URL, realm, or client ID.
|
|
36
|
+
|
|
37
|
+
| Flag | Env var | Notes |
|
|
38
|
+
| ---------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `--server` | `AXE_SERVER_URL` | axe server URL. Defaults to `https://axe.deque.com` (SaaS prod) when neither flag nor env var is set, so SaaS users pass no flags at all. Customers on other deployments override with their own axe server URL. |
|
|
40
|
+
| `--allow-insecure-issuer` | — | Permit non-loopback http URLs (default is https only; loopback http is always allowed). Applies to `login` only; `token` and `logout` use the policy persisted at login. |
|
|
41
|
+
| `--no-allow-insecure-issuer` | — | Force `allowInsecureIssuer=false` for the new `login` (and the entry it persists). Mutually exclusive with `--allow-insecure-issuer`. `token` and `logout` ignore this flag. |
|
|
42
|
+
|
|
43
|
+
`axe-auth` stores one set of credentials per machine. On a successful `login`, the discovered issuer / client / insecure-issuer values are persisted alongside the tokens, so subsequent `axe-auth token` and `axe-auth logout` invocations work flag-free — a typical scripted call is just `$(axe-auth token)`.
|
|
44
|
+
|
|
45
|
+
There is no concurrent multi-issuer support. Logging in to a second deployment overwrites the previous tokens; an interactive prompt confirms the switch before destroying the existing session, and `--force` skips the prompt.
|
|
46
|
+
|
|
47
|
+
### Exit codes
|
|
48
|
+
|
|
49
|
+
| Code | Meaning |
|
|
50
|
+
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| `0` | Success. |
|
|
52
|
+
| `1` | Not authenticated: `axe-auth token` with no stored credentials, or stored credentials are unusable (corrupt, version-mismatch, expired without a usable refresh token, or refresh rejected by the server). Branch on this in scripts that need to trigger a `login`. |
|
|
53
|
+
| `2` | Usage or runtime error: unknown command, bad flag, missing required configuration, OAuth flow failure, or keychain failure. Details written to stderr. |
|
|
54
|
+
| `3` | Cancelled: `axe-auth login` declined at the re-authentication prompt. Distinct from `1` so scripts can tell "needs login" from "user bailed." |
|
|
55
|
+
|
|
56
|
+
### Examples
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
# First-time login on Deque SaaS prod (opens your browser, no flags needed)
|
|
60
|
+
axe-auth login
|
|
61
|
+
|
|
62
|
+
# First-time login on a non-SaaS-prod deployment
|
|
63
|
+
axe-auth login --server https://axe.customer.dequecloud.com
|
|
64
|
+
|
|
65
|
+
# Pull a fresh access token for use in shell substitution
|
|
66
|
+
docker run -e AXE_ACCESS_TOKEN="$(axe-auth token)" axe-mcp-server
|
|
67
|
+
|
|
68
|
+
# Sign out
|
|
69
|
+
axe-auth logout
|
|
70
|
+
```
|
|
31
71
|
|
|
32
72
|
## Architecture
|
|
33
73
|
|
|
34
|
-
|
|
74
|
+
See [`docs/architecture.md`](./docs/architecture.md) for the system architecture: components, per-verb data flow, communication security, and persisted data.
|
|
75
|
+
|
|
76
|
+
Deeper design notes:
|
|
77
|
+
|
|
78
|
+
- [`oauth-flow.md`](./docs/oauth-flow.md) — protocol-level walkthrough of the OAuth 2.0 + PKCE flow.
|
|
79
|
+
- [`callback-server.md`](./docs/callback-server.md) — `startCallbackServer` API and RFC 8252 conformance.
|
|
80
|
+
- [`callback-page.md`](./docs/callback-page.md) — HTML response design, branding, and CSP rationale.
|
|
81
|
+
|
|
82
|
+
## Caveats
|
|
35
83
|
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
- [`callback-page.md`](https://github.com/dequelabs/axe-mcp-server/blob/develop/packages/axe-auth/docs/callback-page.md) — HTML response design, branding, and CSP rationale.
|
|
84
|
+
- **`axe-auth token` exposes the access token in the shell process list and terminal scrollback.** When used as `$(axe-auth token)` the token briefly appears in the parent process's argument list (observable via `ps`); printed directly to a terminal it also persists in the scrollback buffer (iTerm2, Terminal.app, tmux all retain output by default), which can outlast the token's TTL on a shared machine. OAuth access tokens are short-lived (typically minutes), which limits exposure compared to a static API key. Prefer redirecting into a file (`axe-auth token > /tmp/tok && chmod 600 /tmp/tok`) or directly into an env var (`export AXE_ACCESS_TOKEN=$(axe-auth token)`) on platforms where this matters.
|
|
85
|
+
- **Linux keychain support is untested.** `@napi-rs/keyring` requires a working D-Bus Secret Service (GNOME Keyring, KWallet, etc.). Users on headless or minimal-desktop Linux environments may see `KEYRING_UNAVAILABLE`; a file-backed `TokenStore` fallback is tracked as a follow-up (internal issue #464).
|
package/credits.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@napi-rs/keyring@1.2.0": {
|
|
3
|
+
"name": "@napi-rs/keyring",
|
|
4
|
+
"version": "1.2.0",
|
|
5
|
+
"licenses": "MIT",
|
|
6
|
+
"path": "/home/runner/work/axe-mcp-server/axe-mcp-server/node_modules/.pnpm/@napi-rs+keyring@1.2.0/node_modules/@napi-rs/keyring",
|
|
7
|
+
"licenseText": "MIT License\n\nCopyright (c) 2020 N-API for Rust\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
|
|
8
|
+
"licenseFile": "/home/runner/work/axe-mcp-server/axe-mcp-server/node_modules/.pnpm/@napi-rs+keyring@1.2.0/node_modules/@napi-rs/keyring/LICENSE",
|
|
9
|
+
"copyright": "Copyright (c) 2020 N-API for Rust"
|
|
10
|
+
},
|
|
11
|
+
"@napi-rs/keyring-linux-x64-gnu@1.2.0": {
|
|
12
|
+
"name": "@napi-rs/keyring-linux-x64-gnu",
|
|
13
|
+
"version": "1.2.0",
|
|
14
|
+
"licenses": "MIT",
|
|
15
|
+
"path": "/home/runner/work/axe-mcp-server/axe-mcp-server/node_modules/.pnpm/@napi-rs+keyring-linux-x64-gnu@1.2.0/node_modules/@napi-rs/keyring-linux-x64-gnu",
|
|
16
|
+
"licenseText": "# `@napi-rs/keyring-linux-x64-gnu`\n\nThis is the **x86_64-unknown-linux-gnu** binary for `@napi-rs/keyring`\n",
|
|
17
|
+
"licenseFile": "/home/runner/work/axe-mcp-server/axe-mcp-server/node_modules/.pnpm/@napi-rs+keyring-linux-x64-gnu@1.2.0/node_modules/@napi-rs/keyring-linux-x64-gnu/README.md"
|
|
18
|
+
},
|
|
19
|
+
"remove-trailing-slash@0.1.1": {
|
|
20
|
+
"name": "remove-trailing-slash",
|
|
21
|
+
"version": "0.1.1",
|
|
22
|
+
"licenses": "MIT",
|
|
23
|
+
"path": "/home/runner/work/axe-mcp-server/axe-mcp-server/node_modules/.pnpm/remove-trailing-slash@0.1.1/node_modules/remove-trailing-slash",
|
|
24
|
+
"licenseText": "# remove-trailing-slash\n\nremoves trailing slashes\n\n## Installation\n\nwith [component(1)](http://component.io):\n\n $ component install stephenmathieson/remove-trailing-slash\n\nwith [npm](https://npmjs.org/):\n\n $ npm install remove-trailing-slash\n\n## API\n\n### `removeTrailingSlash(str)`\n\nRemoves trailing slashes from the given `str`\n\n## Example\n\n```js\nvar slashes = require('remove-trailing-slash')\n\nslashes('http://google.com/').should.be.equal('http://google.com');\n```\n\n## License\n\nMIT\n",
|
|
25
|
+
"licenseFile": "/home/runner/work/axe-mcp-server/axe-mcp-server/node_modules/.pnpm/remove-trailing-slash@0.1.1/node_modules/remove-trailing-slash/readme.md",
|
|
26
|
+
"repository": "https://github.com/stephenmathieson/remove-trailing-slash",
|
|
27
|
+
"publisher": "Stephen Mathieson",
|
|
28
|
+
"email": "me@stephenmathieson.com"
|
|
29
|
+
},
|
|
30
|
+
"shlex@3.0.0": {
|
|
31
|
+
"name": "shlex",
|
|
32
|
+
"version": "3.0.0",
|
|
33
|
+
"licenses": "MIT",
|
|
34
|
+
"path": "/home/runner/work/axe-mcp-server/axe-mcp-server/node_modules/.pnpm/shlex@3.0.0/node_modules/shlex",
|
|
35
|
+
"licenseText": "MIT License\n\nCopyright (c) 2018 Ryan Govostes\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
|
|
36
|
+
"licenseFile": "/home/runner/work/axe-mcp-server/axe-mcp-server/node_modules/.pnpm/shlex@3.0.0/node_modules/shlex/LICENSE",
|
|
37
|
+
"repository": "https://github.com/rgov/node-shlex",
|
|
38
|
+
"publisher": "Ryan Govostes",
|
|
39
|
+
"copyright": "Copyright (c) 2018 Ryan Govostes"
|
|
40
|
+
},
|
|
41
|
+
"ts-dedent@2.2.0": {
|
|
42
|
+
"name": "ts-dedent",
|
|
43
|
+
"version": "2.2.0",
|
|
44
|
+
"licenses": "MIT",
|
|
45
|
+
"path": "/home/runner/work/axe-mcp-server/axe-mcp-server/node_modules/.pnpm/ts-dedent@2.2.0/node_modules/ts-dedent",
|
|
46
|
+
"licenseText": "MIT License\n\nCopyright (c) 2018 Tamino Martinius\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
|
|
47
|
+
"licenseFile": "/home/runner/work/axe-mcp-server/axe-mcp-server/node_modules/.pnpm/ts-dedent@2.2.0/node_modules/ts-dedent/LICENSE",
|
|
48
|
+
"repository": "https://github.com/tamino-martinius/node-ts-dedent",
|
|
49
|
+
"publisher": "Tamino Martinius",
|
|
50
|
+
"email": "dev@zaku.eu",
|
|
51
|
+
"copyright": "Copyright (c) 2018 Tamino Martinius"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ParseArgsConfig } from "node:util";
|
|
2
|
+
/** Default axe server URL for Deque SaaS prod. */
|
|
3
|
+
export declare const DEFAULT_WALNUT_URL = "https://axe.deque.com";
|
|
4
|
+
/** Common configuration the CLI verbs share. */
|
|
5
|
+
export interface CommonArgs {
|
|
6
|
+
/** axe server URL (walnut). */
|
|
7
|
+
walnutURL: string;
|
|
8
|
+
/** Whether non-loopback http walnut/issuer URLs are permitted. */
|
|
9
|
+
allowInsecureIssuer: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `parseArgs`-shaped options shared by every CLI verb. `parseArgs`
|
|
13
|
+
* doesn't support `--no-` boolean negation natively, so the opt-out
|
|
14
|
+
* is registered as its own flag and `parseCommonArgs` rejects passing
|
|
15
|
+
* both together.
|
|
16
|
+
*/
|
|
17
|
+
export declare const COMMON_OPTIONS: NonNullable<ParseArgsConfig["options"]>;
|
|
18
|
+
/** Subset of `parseArgs(...).values` this helper consumes. */
|
|
19
|
+
export interface ParsedCommonValues {
|
|
20
|
+
server?: string;
|
|
21
|
+
"allow-insecure-issuer"?: boolean;
|
|
22
|
+
"no-allow-insecure-issuer"?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Stored fallback for `allowInsecureIssuer` + the `walnutURL` it was minted against. */
|
|
25
|
+
export interface StoredCommonDefaults {
|
|
26
|
+
walnutURL: string;
|
|
27
|
+
allowInsecureIssuer: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolves common configuration from parsed flag values, falling
|
|
31
|
+
* back to `AXE_SERVER_URL` and finally to `DEFAULT_WALNUT_URL`.
|
|
32
|
+
* `allowInsecureIssuer` falls back to `defaults` only when the
|
|
33
|
+
* resolved walnut URL matches `defaults.walnutURL`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseCommonArgs(values: ParsedCommonValues, env?: NodeJS.ProcessEnv, defaults?: StoredCommonDefaults | null): CommonArgs;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Help-text fragment describing the flags every CLI verb shares. */
|
|
2
|
+
export declare const HELP_COMMON_OPTIONS = " --server <url> axe server URL. Used by `login` to fetch\n /api/sso-config and derive the OAuth\n coordinates. Falls back to AXE_SERVER_URL,\n then to https://axe.deque.com (SaaS prod).\n --allow-insecure-issuer Permit non-loopback http URLs (default is\n https only; loopback http is always\n allowed). Applies to `login` only;\n `token` and `logout` use the policy\n persisted at login.\n --no-allow-insecure-issuer\n Force allowInsecureIssuer=false for the new\n `login` (and the entry it persists).\n Ignored by `token` and `logout`.\n Mutually exclusive with\n --allow-insecure-issuer.\n -h, --help Show this help.";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HELP_COMMON_OPTIONS = void 0;
|
|
4
|
+
/** Help-text fragment describing the flags every CLI verb shares. */
|
|
5
|
+
exports.HELP_COMMON_OPTIONS = ` --server <url> axe server URL. Used by \`login\` to fetch
|
|
6
|
+
/api/sso-config and derive the OAuth
|
|
7
|
+
coordinates. Falls back to AXE_SERVER_URL,
|
|
8
|
+
then to https://axe.deque.com (SaaS prod).
|
|
9
|
+
--allow-insecure-issuer Permit non-loopback http URLs (default is
|
|
10
|
+
https only; loopback http is always
|
|
11
|
+
allowed). Applies to \`login\` only;
|
|
12
|
+
\`token\` and \`logout\` use the policy
|
|
13
|
+
persisted at login.
|
|
14
|
+
--no-allow-insecure-issuer
|
|
15
|
+
Force allowInsecureIssuer=false for the new
|
|
16
|
+
\`login\` (and the entry it persists).
|
|
17
|
+
Ignored by \`token\` and \`logout\`.
|
|
18
|
+
Mutually exclusive with
|
|
19
|
+
--allow-insecure-issuer.
|
|
20
|
+
-h, --help Show this help.`;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.COMMON_OPTIONS = exports.DEFAULT_WALNUT_URL = void 0;
|
|
7
|
+
exports.parseCommonArgs = parseCommonArgs;
|
|
8
|
+
const remove_trailing_slash_1 = __importDefault(require("remove-trailing-slash"));
|
|
9
|
+
/** Default axe server URL for Deque SaaS prod. */
|
|
10
|
+
exports.DEFAULT_WALNUT_URL = "https://axe.deque.com";
|
|
11
|
+
/**
|
|
12
|
+
* `parseArgs`-shaped options shared by every CLI verb. `parseArgs`
|
|
13
|
+
* doesn't support `--no-` boolean negation natively, so the opt-out
|
|
14
|
+
* is registered as its own flag and `parseCommonArgs` rejects passing
|
|
15
|
+
* both together.
|
|
16
|
+
*/
|
|
17
|
+
exports.COMMON_OPTIONS = {
|
|
18
|
+
server: { type: "string" },
|
|
19
|
+
"allow-insecure-issuer": { type: "boolean" },
|
|
20
|
+
"no-allow-insecure-issuer": { type: "boolean" },
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Resolves common configuration from parsed flag values, falling
|
|
24
|
+
* back to `AXE_SERVER_URL` and finally to `DEFAULT_WALNUT_URL`.
|
|
25
|
+
* `allowInsecureIssuer` falls back to `defaults` only when the
|
|
26
|
+
* resolved walnut URL matches `defaults.walnutURL`.
|
|
27
|
+
*/
|
|
28
|
+
function parseCommonArgs(values, env = process.env, defaults = null) {
|
|
29
|
+
const walnutURL = (0, remove_trailing_slash_1.default)(values.server ?? env.AXE_SERVER_URL ?? exports.DEFAULT_WALNUT_URL);
|
|
30
|
+
// Only inherit the stored `allowInsecureIssuer` when the incoming
|
|
31
|
+
// walnut URL matches the stored one. A user logging in to a
|
|
32
|
+
// different deployment must opt back in via `--allow-insecure-issuer`
|
|
33
|
+
// explicitly; otherwise a dev-time HTTP-allow setting would silently
|
|
34
|
+
// carry over to a prod login.
|
|
35
|
+
const matchingDefaults = defaults && defaults.walnutURL === walnutURL ? defaults : null;
|
|
36
|
+
const allowInsecureIssuer = resolveAllowInsecureIssuer(values, matchingDefaults);
|
|
37
|
+
return { walnutURL, allowInsecureIssuer };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolves `allowInsecureIssuer` from the positive flag, its
|
|
41
|
+
* negation, and the keychain default — in that precedence order.
|
|
42
|
+
* Throws when both flags are passed together.
|
|
43
|
+
*/
|
|
44
|
+
function resolveAllowInsecureIssuer(values, defaults) {
|
|
45
|
+
// Truthy checks (rather than `!== undefined`) are deliberate:
|
|
46
|
+
// `parseArgs` with `type: "boolean"` only ever produces `true` or
|
|
47
|
+
// `undefined`, but `ParsedCommonValues` types these as
|
|
48
|
+
// `boolean | undefined` so a programmatic caller could thread in
|
|
49
|
+
// `false`. Treating `false` as "flag not set" lets such a caller
|
|
50
|
+
// mix `{ "allow-insecure-issuer": false, "no-allow-insecure-issuer":
|
|
51
|
+
// true }` without tripping the mutex — `false` here is equivalent
|
|
52
|
+
// to "absent", which is what parseArgs would have produced anyway.
|
|
53
|
+
const allow = values["allow-insecure-issuer"];
|
|
54
|
+
const deny = values["no-allow-insecure-issuer"];
|
|
55
|
+
if (allow && deny) {
|
|
56
|
+
throw new Error("--allow-insecure-issuer and --no-allow-insecure-issuer are mutually exclusive.");
|
|
57
|
+
}
|
|
58
|
+
if (allow)
|
|
59
|
+
return true;
|
|
60
|
+
if (deny)
|
|
61
|
+
return false;
|
|
62
|
+
return defaults?.allowInsecureIssuer ?? false;
|
|
63
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
/** Options for `confirm`. */
|
|
3
|
+
export interface ConfirmOptions {
|
|
4
|
+
/** Question to display. A trailing space is added if absent. */
|
|
5
|
+
question: string;
|
|
6
|
+
/** Stream to read the answer from. Defaults to `process.stdin`. */
|
|
7
|
+
input?: Readable;
|
|
8
|
+
/** Stream to print the question on. Defaults to `process.stderr`. */
|
|
9
|
+
output?: Writable;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Reads a single line from `input` and returns `true` for an
|
|
13
|
+
* affirmative answer (`y` / `yes`, case-insensitive), `false`
|
|
14
|
+
* otherwise. Empty / EOF / Ctrl-C are all treated as "no" so the
|
|
15
|
+
* default action stays conservative.
|
|
16
|
+
*/
|
|
17
|
+
export declare function confirm(options: ConfirmOptions): Promise<boolean>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.confirm = confirm;
|
|
4
|
+
const node_readline_1 = require("node:readline");
|
|
5
|
+
/**
|
|
6
|
+
* Reads a single line from `input` and returns `true` for an
|
|
7
|
+
* affirmative answer (`y` / `yes`, case-insensitive), `false`
|
|
8
|
+
* otherwise. Empty / EOF / Ctrl-C are all treated as "no" so the
|
|
9
|
+
* default action stays conservative.
|
|
10
|
+
*/
|
|
11
|
+
async function confirm(options) {
|
|
12
|
+
const input = options.input ?? process.stdin;
|
|
13
|
+
const output = options.output ?? process.stderr;
|
|
14
|
+
const question = options.question.endsWith(" ")
|
|
15
|
+
? options.question
|
|
16
|
+
: `${options.question} `;
|
|
17
|
+
// Use createInterface rather than the higher-level `rl.question`
|
|
18
|
+
// promise API because the latter wires up SIGINT handling that
|
|
19
|
+
// doesn't compose well when this is called from a CLI dispatcher
|
|
20
|
+
// that owns its own signals.
|
|
21
|
+
const rl = (0, node_readline_1.createInterface)({ input, output });
|
|
22
|
+
try {
|
|
23
|
+
output.write(question);
|
|
24
|
+
const line = await waitForLineOrClose(rl);
|
|
25
|
+
if (line === null)
|
|
26
|
+
return false;
|
|
27
|
+
const trimmed = line.trim().toLowerCase();
|
|
28
|
+
return trimmed === "y" || trimmed === "yes";
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
rl.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolves with the next line emitted by `rl`, or `null` if the
|
|
36
|
+
* underlying stream closes first (EOF / Ctrl-D / Ctrl-C). Both
|
|
37
|
+
* listeners detach themselves on the other event so we never hold
|
|
38
|
+
* references to a closed interface.
|
|
39
|
+
*/
|
|
40
|
+
function waitForLineOrClose(rl) {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
const onLine = (line) => {
|
|
43
|
+
rl.off("close", onClose);
|
|
44
|
+
resolve(line);
|
|
45
|
+
};
|
|
46
|
+
const onClose = () => {
|
|
47
|
+
rl.off("line", onLine);
|
|
48
|
+
resolve(null);
|
|
49
|
+
};
|
|
50
|
+
rl.once("line", onLine);
|
|
51
|
+
rl.once("close", onClose);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Discriminator for `CLIError`, used by tests and the dispatcher. */
|
|
2
|
+
export type CLIErrorCode = "NOT_AUTHENTICATED" | "USER_CANCELLED" | "ALREADY_AUTHENTICATED" | "OAUTH_FAILED" | "KEYCHAIN_FAILURE";
|
|
3
|
+
/**
|
|
4
|
+
* Thrown from a verb's `run` to signal a known failure. The
|
|
5
|
+
* dispatcher writes `message` to stderr and exits with `exitCode`.
|
|
6
|
+
*/
|
|
7
|
+
export declare class CLIError extends Error {
|
|
8
|
+
readonly code: CLIErrorCode;
|
|
9
|
+
readonly exitCode: number;
|
|
10
|
+
constructor(code: CLIErrorCode, message: string);
|
|
11
|
+
}
|
|
12
|
+
/** Returns the `message` of an `Error`, or its `String` coercion otherwise. */
|
|
13
|
+
export declare function describeError(err: unknown): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CLIError = void 0;
|
|
4
|
+
exports.describeError = describeError;
|
|
5
|
+
const EXIT_CODE_BY_ERROR_CODE = {
|
|
6
|
+
NOT_AUTHENTICATED: 1,
|
|
7
|
+
USER_CANCELLED: 3,
|
|
8
|
+
ALREADY_AUTHENTICATED: 2,
|
|
9
|
+
OAUTH_FAILED: 2,
|
|
10
|
+
KEYCHAIN_FAILURE: 2,
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Thrown from a verb's `run` to signal a known failure. The
|
|
14
|
+
* dispatcher writes `message` to stderr and exits with `exitCode`.
|
|
15
|
+
*/
|
|
16
|
+
class CLIError extends Error {
|
|
17
|
+
code;
|
|
18
|
+
exitCode;
|
|
19
|
+
constructor(code, message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "CLIError";
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.exitCode = EXIT_CODE_BY_ERROR_CODE[code];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.CLIError = CLIError;
|
|
27
|
+
/** Returns the `message` of an `Error`, or its `String` coercion otherwise. */
|
|
28
|
+
function describeError(err) {
|
|
29
|
+
return err instanceof Error ? err.message : String(err);
|
|
30
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Readable, Writable } from "node:stream";
|
|
2
|
+
import type { CommonArgs } from "./commonArgs";
|
|
3
|
+
import type { CommandDeps } from "./types";
|
|
4
|
+
import type { LoadResult, StoredEntry, TokenStore } from "../oauth/tokenStore";
|
|
5
|
+
import type { TokenSet } from "../oauth/tokenResponse";
|
|
6
|
+
/** A `Writable` that accumulates everything written into a string. */
|
|
7
|
+
export interface CapturedStream extends Writable {
|
|
8
|
+
/** Concatenation of every chunk written to the stream so far. */
|
|
9
|
+
readonly value: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Returns a `Writable` that records every write into a string for
|
|
13
|
+
* assertion. Reads via `.value`. Defined as a real `Writable`
|
|
14
|
+
* subclass so it can be passed straight into `CommandDeps.stdout` /
|
|
15
|
+
* `CommandDeps.stderr` without casts.
|
|
16
|
+
*/
|
|
17
|
+
export declare function captureStream(): CapturedStream;
|
|
18
|
+
/** Convenience: an empty `Readable` to stand in for stdin in tests. */
|
|
19
|
+
export declare function emptyStdin(): Readable;
|
|
20
|
+
/** A `TokenStore` plus instrumentation for assertions. */
|
|
21
|
+
export interface FakeStore extends TokenStore {
|
|
22
|
+
/** Number of times `load()` was called. */
|
|
23
|
+
readonly loadedTimes: number;
|
|
24
|
+
/** Number of times `clear()` was called. */
|
|
25
|
+
readonly clearedTimes: number;
|
|
26
|
+
/** Current state, observable from tests. */
|
|
27
|
+
readonly current: LoadResult;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* In-memory `TokenStore` that starts in `initial`, accepts
|
|
31
|
+
* `save()` / `clear()`, and exposes `loadedTimes` / `clearedTimes`
|
|
32
|
+
* / `current` for assertions.
|
|
33
|
+
*/
|
|
34
|
+
export declare function makeStore(initial: LoadResult): FakeStore;
|
|
35
|
+
/**
|
|
36
|
+
* Builds a `StoredEntry` for the standard test issuer/client. Pass a
|
|
37
|
+
* `TokenSet` (from a per-test fixture) and override the
|
|
38
|
+
* issuer/client/insecure fields when a test cares about them.
|
|
39
|
+
*/
|
|
40
|
+
export declare function entry(tokens: TokenSet, overrides?: Partial<Omit<StoredEntry, "tokens">>): StoredEntry;
|
|
41
|
+
/** A `CommandDeps` shape with the captured streams exposed. */
|
|
42
|
+
export interface CapturedDeps extends CommandDeps {
|
|
43
|
+
stdout: CapturedStream;
|
|
44
|
+
stderr: CapturedStream;
|
|
45
|
+
}
|
|
46
|
+
/** Returns a `CommandDeps` populated with capturing streams. */
|
|
47
|
+
export declare function captureDeps(overrides?: Partial<CapturedDeps>): CapturedDeps;
|
|
48
|
+
/**
|
|
49
|
+
* Returns a fully-resolved `CommonArgs` for the standard test
|
|
50
|
+
* issuer/client. Override individual fields as needed.
|
|
51
|
+
*/
|
|
52
|
+
export declare function commonArgs(overrides?: Partial<CommonArgs>): CommonArgs;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Shared test helpers for the command tests. Excluded from c8
|
|
3
|
+
// coverage in `.c8rc.json` since this is test-only code.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.captureStream = captureStream;
|
|
6
|
+
exports.emptyStdin = emptyStdin;
|
|
7
|
+
exports.makeStore = makeStore;
|
|
8
|
+
exports.entry = entry;
|
|
9
|
+
exports.captureDeps = captureDeps;
|
|
10
|
+
exports.commonArgs = commonArgs;
|
|
11
|
+
const node_stream_1 = require("node:stream");
|
|
12
|
+
/**
|
|
13
|
+
* Returns a `Writable` that records every write into a string for
|
|
14
|
+
* assertion. Reads via `.value`. Defined as a real `Writable`
|
|
15
|
+
* subclass so it can be passed straight into `CommandDeps.stdout` /
|
|
16
|
+
* `CommandDeps.stderr` without casts.
|
|
17
|
+
*/
|
|
18
|
+
function captureStream() {
|
|
19
|
+
let value = "";
|
|
20
|
+
const stream = new node_stream_1.Writable({
|
|
21
|
+
write(chunk, _encoding, cb) {
|
|
22
|
+
value +=
|
|
23
|
+
typeof chunk === "string" ? chunk : Buffer.from(chunk).toString();
|
|
24
|
+
cb();
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
Object.defineProperty(stream, "value", { get: () => value });
|
|
28
|
+
return stream;
|
|
29
|
+
}
|
|
30
|
+
/** Convenience: an empty `Readable` to stand in for stdin in tests. */
|
|
31
|
+
function emptyStdin() {
|
|
32
|
+
return node_stream_1.Readable.from([]);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* In-memory `TokenStore` that starts in `initial`, accepts
|
|
36
|
+
* `save()` / `clear()`, and exposes `loadedTimes` / `clearedTimes`
|
|
37
|
+
* / `current` for assertions.
|
|
38
|
+
*/
|
|
39
|
+
function makeStore(initial) {
|
|
40
|
+
let current = initial;
|
|
41
|
+
let loadedTimes = 0;
|
|
42
|
+
let clearedTimes = 0;
|
|
43
|
+
return {
|
|
44
|
+
save: async (entry) => {
|
|
45
|
+
current = { ok: true, entry };
|
|
46
|
+
},
|
|
47
|
+
load: async () => {
|
|
48
|
+
loadedTimes++;
|
|
49
|
+
return current;
|
|
50
|
+
},
|
|
51
|
+
clear: async () => {
|
|
52
|
+
clearedTimes++;
|
|
53
|
+
current = { ok: false, reason: "empty" };
|
|
54
|
+
},
|
|
55
|
+
get loadedTimes() {
|
|
56
|
+
return loadedTimes;
|
|
57
|
+
},
|
|
58
|
+
get clearedTimes() {
|
|
59
|
+
return clearedTimes;
|
|
60
|
+
},
|
|
61
|
+
get current() {
|
|
62
|
+
return current;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Builds a `StoredEntry` for the standard test issuer/client. Pass a
|
|
68
|
+
* `TokenSet` (from a per-test fixture) and override the
|
|
69
|
+
* issuer/client/insecure fields when a test cares about them.
|
|
70
|
+
*/
|
|
71
|
+
function entry(tokens, overrides = {}) {
|
|
72
|
+
return {
|
|
73
|
+
tokens,
|
|
74
|
+
issuerURL: "https://auth.example.com/auth/realms/prod",
|
|
75
|
+
clientId: "axe-auth",
|
|
76
|
+
allowInsecureIssuer: false,
|
|
77
|
+
walnutURL: "https://axe.example.com",
|
|
78
|
+
...overrides,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** Returns a `CommandDeps` populated with capturing streams. */
|
|
82
|
+
function captureDeps(overrides = {}) {
|
|
83
|
+
return {
|
|
84
|
+
stdin: emptyStdin(),
|
|
85
|
+
stdout: captureStream(),
|
|
86
|
+
stderr: captureStream(),
|
|
87
|
+
...overrides,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Returns a fully-resolved `CommonArgs` for the standard test
|
|
92
|
+
* issuer/client. Override individual fields as needed.
|
|
93
|
+
*/
|
|
94
|
+
function commonArgs(overrides = {}) {
|
|
95
|
+
return {
|
|
96
|
+
walnutURL: "https://axe.example.com",
|
|
97
|
+
allowInsecureIssuer: false,
|
|
98
|
+
...overrides,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
import type { ParseArgsConfig } from "node:util";
|
|
3
|
+
import type { LoadResult } from "../oauth/tokenStore";
|
|
4
|
+
import type { CommonArgs } from "./commonArgs";
|
|
5
|
+
/** Mapping passed to `parseArgs` for verb-specific flags. */
|
|
6
|
+
export type VerbOptions = NonNullable<ParseArgsConfig["options"]>;
|
|
7
|
+
/** Injectables every CLI verb sees. Dispatcher fills with `process.*`; tests pass synthetic values. */
|
|
8
|
+
export interface CommandDeps {
|
|
9
|
+
/** `isTTY` is optional so `Readable.from([])` test fixtures coerce to non-TTY. */
|
|
10
|
+
stdin: Readable & {
|
|
11
|
+
isTTY?: boolean;
|
|
12
|
+
};
|
|
13
|
+
stdout: Writable;
|
|
14
|
+
stderr: Writable;
|
|
15
|
+
/**
|
|
16
|
+
* Pre-loaded result of `KeyringTokenStore.load()` from the
|
|
17
|
+
* dispatcher's keychain read, so a single CLI invocation hits the
|
|
18
|
+
* keychain once instead of N times.
|
|
19
|
+
*/
|
|
20
|
+
loadedEntry?: LoadResult;
|
|
21
|
+
}
|
|
22
|
+
/** Specification of a single CLI verb consumed by the dispatcher. */
|
|
23
|
+
export interface CommandSpec {
|
|
24
|
+
/** Verb name, e.g. `"login"`. */
|
|
25
|
+
readonly name: string;
|
|
26
|
+
/** One-liner shown in the top-level `axe-auth --help` listing. */
|
|
27
|
+
readonly summary: string;
|
|
28
|
+
/** Full help text printed for `axe-auth <verb> --help`. */
|
|
29
|
+
readonly helpText: string;
|
|
30
|
+
/** Verb-specific `parseArgs` options; common options are added by the dispatcher. */
|
|
31
|
+
readonly options: VerbOptions;
|
|
32
|
+
/** `true` if this verb cannot run without a usable walnut URL (login). */
|
|
33
|
+
readonly requiresConfig: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Throw `CLIError` for a known failure with an explicit exit code;
|
|
36
|
+
* any other error becomes exit code 2 with the message on stderr.
|
|
37
|
+
*/
|
|
38
|
+
run(args: CommonArgs, deps: CommandDeps): Promise<void>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { authorize } from "../oauth/authorize";
|
|
2
|
+
import { discoverSSOConfig } from "../oauth/discoverSSOConfig";
|
|
3
|
+
import { type TokenStore } from "../oauth/tokenStore";
|
|
4
|
+
import type { CommonArgs } from "../cli/commonArgs";
|
|
5
|
+
import type { CommandDeps } from "../cli/types";
|
|
6
|
+
/** Verb-specific deps for `axe-auth login`. */
|
|
7
|
+
export interface LoginDeps extends CommandDeps {
|
|
8
|
+
/** Whether to treat the session as interactive. Defaults to `stdin.isTTY`. */
|
|
9
|
+
isInteractive?: boolean;
|
|
10
|
+
/** Override `authorize` (for tests). */
|
|
11
|
+
authorize?: typeof authorize;
|
|
12
|
+
/** Override the SSO discovery helper (for tests). */
|
|
13
|
+
discoverSSOConfig?: typeof discoverSSOConfig;
|
|
14
|
+
/**
|
|
15
|
+
* Override the token store. Defaults to a fresh
|
|
16
|
+
* `KeyringTokenStore()`. The same instance is passed to `authorize`
|
|
17
|
+
* so the pre-check and post-flow save agree on the keychain entry.
|
|
18
|
+
*/
|
|
19
|
+
tokenStore?: TokenStore;
|
|
20
|
+
/** Override the confirmation prompt (for tests). */
|
|
21
|
+
confirm?: (prompt: string) => Promise<boolean>;
|
|
22
|
+
}
|
|
23
|
+
/** Verb-specific flags for `axe-auth login`. */
|
|
24
|
+
export interface LoginFlags {
|
|
25
|
+
/** Set by `--force`. Skip the "already authenticated" prompt. */
|
|
26
|
+
force?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** `axe-auth login` — drive the OAuth flow and persist tokens. */
|
|
29
|
+
declare const loginCommand: {
|
|
30
|
+
name: string;
|
|
31
|
+
summary: string;
|
|
32
|
+
helpText: string;
|
|
33
|
+
options: {
|
|
34
|
+
force: {
|
|
35
|
+
type: "boolean";
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
requiresConfig: true;
|
|
39
|
+
run(args: CommonArgs & LoginFlags, deps: LoginDeps): Promise<void>;
|
|
40
|
+
};
|
|
41
|
+
export default loginCommand;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Help text for `axe-auth login --help`. */
|
|
2
|
+
export declare const HELP_LOGIN = "axe-auth login\n\nOpen a browser, complete the OAuth 2.0 Authorization Code + PKCE\nflow against the customer's Keycloak realm, and persist the\nresulting tokens to the OS keychain.\n\nThe CLI discovers the OAuth coordinates by calling\n`<server>/api/sso-config` on the axe server, so users only need to\nsupply (or default to) the axe server URL \u2014 never the underlying\nKeycloak URL, realm, or client ID directly.\n\nUsage:\n axe-auth login [--server <url>] [--force]\n\n With no flags, the SaaS prod axe server URL (https://axe.deque.com)\n is used. Customers on other deployments pass --server (or set\n AXE_SERVER_URL).\n\nOptions:\n --server <url> axe server URL. Used by `login` to fetch\n /api/sso-config and derive the OAuth\n coordinates. Falls back to AXE_SERVER_URL,\n then to https://axe.deque.com (SaaS prod).\n --allow-insecure-issuer Permit non-loopback http URLs (default is\n https only; loopback http is always\n allowed). Applies to `login` only;\n `token` and `logout` use the policy\n persisted at login.\n --no-allow-insecure-issuer\n Force allowInsecureIssuer=false for the new\n `login` (and the entry it persists).\n Ignored by `token` and `logout`.\n Mutually exclusive with\n --allow-insecure-issuer.\n -h, --help Show this help.\n --force Re-authenticate without prompting even if\n a valid token is already stored.\n\nBehavior when already authenticated:\n axe-auth stores one entry per machine. If a valid entry already\n exists, an interactive session prompts for confirmation before\n overwriting it \u2014 even when the new login targets a different\n issuer or client (logging in to B destroys A's tokens). Pass\n --force to skip the prompt. In a non-interactive session (no TTY)\n --force is required; otherwise the command refuses to overwrite\n stored tokens and exits non-zero.\n\nExit codes:\n 0 Success; tokens persisted to the keychain.\n 2 Configuration error or flow failure.\n 3 Login cancelled at the prompt.";
|