@deque/axe-auth 1.1.0-next.816dc5a1 → 1.1.0-next.8e9934f2
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 +13 -17
- package/credits.json +42 -0
- package/dist/cli/commonArgs.d.ts +53 -37
- package/dist/cli/commonArgs.help.d.ts +1 -1
- package/dist/cli/commonArgs.help.js +12 -11
- package/dist/cli/commonArgs.js +37 -66
- package/dist/cli/errors.d.ts +0 -10
- package/dist/cli/errors.js +1 -16
- package/dist/cli/testUtils.js +3 -3
- package/dist/cli/types.d.ts +8 -11
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.help.d.ts +1 -1
- package/dist/commands/login.help.js +11 -5
- package/dist/commands/login.js +38 -14
- package/dist/commands/logout.d.ts +1 -1
- package/dist/commands/logout.help.d.ts +1 -1
- package/dist/commands/logout.help.js +5 -4
- package/dist/commands/logout.js +1 -15
- package/dist/commands/token.d.ts +2 -7
- package/dist/commands/token.help.d.ts +1 -1
- package/dist/commands/token.help.js +5 -5
- package/dist/commands/token.js +10 -22
- package/dist/index.js +23 -51
- package/dist/oauth/authorize.d.ts +7 -0
- package/dist/oauth/authorize.js +2 -1
- package/dist/oauth/discoverOIDC.js +26 -0
- package/dist/oauth/discoverSSOConfig.d.ts +47 -0
- package/dist/oauth/discoverSSOConfig.js +105 -0
- package/dist/oauth/getValidAccessToken.js +1 -0
- package/dist/oauth/tokenStore.d.ts +5 -0
- package/dist/oauth/tokenStore.js +5 -1
- package/docs/architecture.md +27 -18
- package/package.json +3 -2
package/dist/oauth/tokenStore.js
CHANGED
|
@@ -72,7 +72,9 @@ function isLatestBlob(blob) {
|
|
|
72
72
|
(b.refreshToken === undefined || typeof b.refreshToken === "string") &&
|
|
73
73
|
typeof b.issuerURL === "string" &&
|
|
74
74
|
typeof b.clientId === "string" &&
|
|
75
|
-
typeof b.allowInsecureIssuer === "boolean"
|
|
75
|
+
typeof b.allowInsecureIssuer === "boolean" &&
|
|
76
|
+
typeof b.walnutURL === "string" &&
|
|
77
|
+
b.walnutURL.length > 0);
|
|
76
78
|
}
|
|
77
79
|
function blobToEntry(blob) {
|
|
78
80
|
const tokens = {
|
|
@@ -86,6 +88,7 @@ function blobToEntry(blob) {
|
|
|
86
88
|
issuerURL: blob.issuerURL,
|
|
87
89
|
clientId: blob.clientId,
|
|
88
90
|
allowInsecureIssuer: blob.allowInsecureIssuer,
|
|
91
|
+
walnutURL: blob.walnutURL,
|
|
89
92
|
};
|
|
90
93
|
}
|
|
91
94
|
function entryToBlob(entry) {
|
|
@@ -96,6 +99,7 @@ function entryToBlob(entry) {
|
|
|
96
99
|
issuerURL: entry.issuerURL,
|
|
97
100
|
clientId: entry.clientId,
|
|
98
101
|
allowInsecureIssuer: entry.allowInsecureIssuer,
|
|
102
|
+
walnutURL: entry.walnutURL,
|
|
99
103
|
};
|
|
100
104
|
if (entry.tokens.refreshToken)
|
|
101
105
|
blob.refreshToken = entry.tokens.refreshToken;
|
package/docs/architecture.md
CHANGED
|
@@ -13,9 +13,11 @@ flowchart TB
|
|
|
13
13
|
browser[System browser]
|
|
14
14
|
callback[Loopback callback server<br/>127.0.0.1:ephemeral]
|
|
15
15
|
keychain[(OS keychain)]
|
|
16
|
+
axe[axe server]
|
|
16
17
|
keycloak[Customer Keycloak]
|
|
17
18
|
|
|
18
19
|
user -- "axe-auth login / token / logout" --> cli
|
|
20
|
+
cli -- "GET /api/sso-config (login only)" --> axe
|
|
19
21
|
cli -- "spawns" --> callback
|
|
20
22
|
cli -- "opens" --> browser
|
|
21
23
|
browser -- "authorize redirect<br/>(state, PKCE challenge)" --> keycloak
|
|
@@ -23,7 +25,7 @@ flowchart TB
|
|
|
23
25
|
browser -- "GET /callback?code=...&state=..." --> callback
|
|
24
26
|
callback -- "code, state" --> cli
|
|
25
27
|
cli <-- "OIDC discovery, token exchange,<br/>refresh, revoke (HTTPS)" --> keycloak
|
|
26
|
-
cli <-- "tokens + issuer/client<br/>(versioned blob)" --> keychain
|
|
28
|
+
cli <-- "tokens + issuer/client/walnutURL<br/>(versioned blob)" --> keychain
|
|
27
29
|
cli -- "access token (stdout)" --> user
|
|
28
30
|
```
|
|
29
31
|
|
|
@@ -34,7 +36,8 @@ flowchart TB
|
|
|
34
36
|
3. **System browser**: the developer's default OS browser (Chrome, Safari, Firefox, etc.). Used only for the user-interactive part of the OAuth Authorization Code flow. Runs on the host, never in a container or sandbox controlled by `axe-auth`.
|
|
35
37
|
4. **Loopback callback server**: an HTTP listener bound to `127.0.0.1` on an OS-assigned ephemeral port. Spawned by the CLI at the start of `login` and torn down as soon as the OAuth callback fires. Per RFC 8252 §7.3, this is the standard pattern for native-app OAuth.
|
|
36
38
|
5. **OS keychain**: the platform-native credential store accessed through [`@napi-rs/keyring`](https://www.npmjs.com/package/@napi-rs/keyring) — macOS Keychain, Windows Credential Manager, or Linux Secret Service (GNOME Keyring, KWallet). The CLI writes one entry per machine.
|
|
37
|
-
6. **
|
|
39
|
+
6. **axe server**: the customer's deployment of the axe API. The CLI hits its `/api/sso-config` endpoint at the start of `login` to discover the Keycloak URL, realm, and OAuth client ID; no other CLI traffic flows through the axe server.
|
|
40
|
+
7. **Customer Keycloak**: the OAuth authorization server for the customer's deployment. Issues access and refresh tokens. Federation between Keycloak and any upstream enterprise IdP (Okta, AAD, etc.) is the customer's concern and out of scope for this document.
|
|
38
41
|
|
|
39
42
|
`axe-auth` itself does **not** communicate with Deque's API services directly. The access tokens it produces are consumed by downstream tools, most notably the axe MCP server, which presents them to Deque's services as `Authorization: Bearer ...`.
|
|
40
43
|
|
|
@@ -48,9 +51,12 @@ sequenceDiagram
|
|
|
48
51
|
participant CLI as axe-auth CLI
|
|
49
52
|
participant Browser as System browser
|
|
50
53
|
participant CB as Loopback callback<br/>(127.0.0.1:port)
|
|
54
|
+
participant Axe as axe server
|
|
51
55
|
participant KC as Customer Keycloak
|
|
52
56
|
|
|
53
|
-
User->>CLI: axe-auth login [
|
|
57
|
+
User->>CLI: axe-auth login [--server <axe-url>]
|
|
58
|
+
CLI->>Axe: GET /api/sso-config
|
|
59
|
+
Axe-->>CLI: { url, realm, mcpClientId }
|
|
54
60
|
CLI->>KC: GET /.well-known/openid-configuration
|
|
55
61
|
KC-->>CLI: { authorization_endpoint, token_endpoint, ... }
|
|
56
62
|
CLI->>CB: spawn on 127.0.0.1:<ephemeral>
|
|
@@ -63,21 +69,22 @@ sequenceDiagram
|
|
|
63
69
|
CB-->>CLI: { code, state }
|
|
64
70
|
CLI->>KC: POST /token (code, code_verifier)
|
|
65
71
|
KC-->>CLI: { access_token, refresh_token, expires_in }
|
|
66
|
-
Note over CLI: save tokens + issuer/client to OS keychain
|
|
72
|
+
Note over CLI: save tokens + issuer/client/walnutURL to OS keychain
|
|
67
73
|
CLI-->>User: ✓ Authenticated.
|
|
68
74
|
```
|
|
69
75
|
|
|
70
|
-
1. The developer invokes `axe-auth login
|
|
71
|
-
2. The CLI fetches
|
|
72
|
-
3.
|
|
73
|
-
4. The CLI
|
|
74
|
-
5. The CLI
|
|
75
|
-
6. The developer
|
|
76
|
-
7.
|
|
77
|
-
8.
|
|
78
|
-
9. The
|
|
79
|
-
10. The CLI
|
|
80
|
-
11. The CLI
|
|
76
|
+
1. The developer invokes `axe-auth login`, optionally with `--server <axe-url>` (or `AXE_SERVER_URL`); when neither is set the CLI defaults to Deque's SaaS prod axe server.
|
|
77
|
+
2. The CLI fetches `<axe-server>/api/sso-config` to learn the Keycloak base URL, realm, and OAuth client ID. The axe server returns `mcpClientId: null` when the deployment has not been configured for OAuth-based MCP authentication, and the field is absent on older axe server versions; both cases surface as a clear error before any browser is opened.
|
|
78
|
+
3. With the discovered coordinates the CLI fetches the OIDC discovery document at `<issuer>/.well-known/openid-configuration` to learn the authorization, token, and revocation endpoint URLs.
|
|
79
|
+
4. The CLI generates a PKCE `code_verifier` + `code_challenge` (S256) and a random `state` value.
|
|
80
|
+
5. The CLI starts a loopback HTTP listener on `127.0.0.1` at an OS-assigned port.
|
|
81
|
+
6. The CLI opens the developer's system browser to the authorization endpoint with the PKCE challenge, the state, and the loopback `redirect_uri`.
|
|
82
|
+
7. The developer authenticates with Keycloak (typically via the customer's federated SSO).
|
|
83
|
+
8. Keycloak redirects the browser to the loopback `redirect_uri` with an authorization `code` and the original `state`.
|
|
84
|
+
9. The loopback listener validates `state`, captures the `code`, and renders a small success page so the developer knows they can close the tab.
|
|
85
|
+
10. The CLI POSTs `code` + `code_verifier` to Keycloak's token endpoint and receives an `access_token`, `refresh_token`, and `expires_in`.
|
|
86
|
+
11. The CLI persists the resulting `StoredEntry` (tokens plus the issuer/client coordinates that minted them, plus the originating axe server URL) into the OS keychain.
|
|
87
|
+
12. The CLI prints `✓ Authenticated.` on stdout and exits 0.
|
|
81
88
|
|
|
82
89
|
### `axe-auth token`
|
|
83
90
|
|
|
@@ -173,14 +180,16 @@ sequenceDiagram
|
|
|
173
180
|
"accessToken": "...",
|
|
174
181
|
"refreshToken": "...",
|
|
175
182
|
"expiresAt": 1714426800000,
|
|
176
|
-
"issuerURL": "https://auth.customer.example.com/realms/customer",
|
|
177
|
-
"clientId": "axe-auth",
|
|
178
|
-
"allowInsecureIssuer": false
|
|
183
|
+
"issuerURL": "https://auth.customer.example.com/auth/realms/customer",
|
|
184
|
+
"clientId": "axe-auth-cli",
|
|
185
|
+
"allowInsecureIssuer": false,
|
|
186
|
+
"walnutURL": "https://axe.customer.example.com"
|
|
179
187
|
}
|
|
180
188
|
```
|
|
181
189
|
|
|
182
190
|
- **Tokens** (`accessToken`, `refreshToken`, `expiresAt`): the OAuth token set returned by Keycloak. `refreshToken` is omitted if the granted scopes did not include `offline_access`.
|
|
183
191
|
- **Issuer / client coordinates** (`issuerURL`, `clientId`, `allowInsecureIssuer`): the values the tokens were minted against. Persisting them lets `token` and `logout` operate flag-free after first login: the CLI resolves the right discovery URL, token endpoint, and revocation endpoint from the stored values, with no separate "default issuer" pointer to drift out of sync with the tokens themselves.
|
|
192
|
+
- **`walnutURL`**: the originating axe server URL that the SSO discovery used to resolve the OAuth coordinates. Persisted so future verbs can re-discover `/api/sso-config` without user-supplied flags.
|
|
184
193
|
- **Schema version** (`v`): incremented when the blob shape changes incompatibly. A mismatch surfaces as `version-mismatch` from `KeyringTokenStore.load()`, and the CLI prompts re-authentication rather than guessing at unknown shapes.
|
|
185
194
|
|
|
186
195
|
No other persistent state exists. There is no filesystem cache of OIDC discovery documents (each `login` and `logout` re-fetches), no separate config file, and no logs written to disk by default.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deque/axe-auth",
|
|
3
|
-
"version": "1.1.0-next.
|
|
3
|
+
"version": "1.1.0-next.8e9934f2",
|
|
4
4
|
"description": "CLI authentication utility for Deque services",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
14
|
"!dist/**/*.test.*",
|
|
15
|
-
"docs"
|
|
15
|
+
"docs",
|
|
16
|
+
"credits.json"
|
|
16
17
|
],
|
|
17
18
|
"publishConfig": {
|
|
18
19
|
"access": "public",
|