@deque/axe-auth 1.1.0-next.97bcb8e6 → 1.1.0-next.9bc60204

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +64 -7
  2. package/credits.json +42 -0
  3. package/dist/cli/commonArgs.d.ts +82 -0
  4. package/dist/cli/commonArgs.help.d.ts +2 -0
  5. package/dist/cli/commonArgs.help.js +20 -0
  6. package/dist/cli/commonArgs.js +90 -0
  7. package/dist/cli/confirm.d.ts +17 -0
  8. package/dist/cli/confirm.js +56 -0
  9. package/dist/cli/errors.d.ts +20 -0
  10. package/dist/cli/errors.js +37 -0
  11. package/dist/cli/testUtils.d.ts +52 -0
  12. package/dist/cli/testUtils.js +100 -0
  13. package/dist/cli/types.d.ts +79 -0
  14. package/dist/cli/types.js +2 -0
  15. package/dist/commands/login.d.ts +44 -0
  16. package/dist/commands/login.help.d.ts +2 -0
  17. package/dist/commands/login.help.js +41 -0
  18. package/dist/commands/login.js +117 -0
  19. package/dist/commands/logout.d.ts +24 -0
  20. package/dist/commands/logout.help.d.ts +2 -0
  21. package/dist/commands/logout.help.js +38 -0
  22. package/dist/commands/logout.js +70 -0
  23. package/dist/commands/token.d.ts +21 -0
  24. package/dist/commands/token.help.d.ts +2 -0
  25. package/dist/commands/token.help.js +41 -0
  26. package/dist/commands/token.js +44 -0
  27. package/dist/index.js +126 -27
  28. package/dist/oauth/authorizationURL.d.ts +29 -0
  29. package/dist/oauth/authorizationURL.js +52 -0
  30. package/dist/oauth/authorize.d.ts +91 -0
  31. package/dist/oauth/authorize.js +119 -0
  32. package/dist/oauth/callbackServer.d.ts +23 -0
  33. package/dist/oauth/callbackServer.js +234 -0
  34. package/dist/oauth/discoverOIDC.d.ts +50 -0
  35. package/dist/oauth/discoverOIDC.js +173 -0
  36. package/dist/oauth/discoverSSOConfig.d.ts +47 -0
  37. package/dist/oauth/discoverSSOConfig.js +105 -0
  38. package/dist/oauth/errors.d.ts +75 -0
  39. package/dist/oauth/errors.js +48 -0
  40. package/dist/oauth/getValidAccessToken.d.ts +89 -0
  41. package/dist/oauth/getValidAccessToken.js +140 -0
  42. package/dist/oauth/index.d.ts +16 -0
  43. package/dist/oauth/index.js +19 -0
  44. package/dist/oauth/issuerURL.d.ts +22 -0
  45. package/dist/oauth/issuerURL.js +38 -0
  46. package/dist/oauth/keyringBinding.d.ts +22 -0
  47. package/dist/oauth/keyringBinding.js +41 -0
  48. package/dist/oauth/logo.generated.d.ts +1 -0
  49. package/dist/oauth/logo.generated.js +7 -0
  50. package/dist/oauth/openBrowser.d.ts +19 -0
  51. package/dist/oauth/openBrowser.js +78 -0
  52. package/dist/oauth/pkce.d.ts +17 -0
  53. package/dist/oauth/pkce.js +43 -0
  54. package/dist/oauth/predicates.d.ts +7 -0
  55. package/dist/oauth/predicates.js +15 -0
  56. package/dist/oauth/refreshTokens.d.ts +30 -0
  57. package/dist/oauth/refreshTokens.js +63 -0
  58. package/dist/oauth/renderHtml.d.ts +9 -0
  59. package/dist/oauth/renderHtml.js +60 -0
  60. package/dist/oauth/revokeToken.d.ts +28 -0
  61. package/dist/oauth/revokeToken.js +63 -0
  62. package/dist/oauth/testUtils.d.ts +35 -0
  63. package/dist/oauth/testUtils.js +61 -0
  64. package/dist/oauth/tokenExchange.d.ts +26 -0
  65. package/dist/oauth/tokenExchange.js +44 -0
  66. package/dist/oauth/tokenResponse.d.ts +54 -0
  67. package/dist/oauth/tokenResponse.js +121 -0
  68. package/dist/oauth/tokenStore.d.ts +116 -0
  69. package/dist/oauth/tokenStore.js +202 -0
  70. package/dist/userAgent.d.ts +12 -0
  71. package/dist/userAgent.js +18 -0
  72. package/docs/architecture.md +201 -0
  73. package/docs/callback-page.md +24 -0
  74. package/docs/callback-server.md +21 -0
  75. package/docs/oauth-flow.md +15 -0
  76. package/package.json +16 -3
@@ -0,0 +1,24 @@
1
+ # Callback response page
2
+
3
+ The HTML the browser renders after Keycloak redirects to the loopback URL, produced by `src/oauth/renderHtml.ts`.
4
+
5
+ ## Approach
6
+
7
+ Visually match Deque's billing-service frontend (palette, typography, centered layout) so developers recognise the page as Deque-owned, but ship no external assets — everything is inline. The logo lives at `assets/logo.png` and is embedded as a base64 data URI via the generated `src/oauth/logo.generated.ts`; regenerate with `node scripts/encode-logo.js` after replacing the PNG.
8
+
9
+ ## Security considerations
10
+
11
+ **No external stylesheets or images.** billing-service pulls Roboto from Google Fonts. The callback page deliberately does not. Loading any external resource from `http://127.0.0.1:<port>/callback?code=...&state=...` causes the browser to send a `Referer` header containing the full callback URL — leaking the auth code to the third-party origin. A `<meta name="referrer" content="no-referrer">` would suppress the header, but relying on it is brittle; avoiding external references entirely is defense in depth. The `Roboto → Helvetica → Arial` fallback renders indistinguishably on developer machines.
12
+
13
+ **Content Security Policy.** Every response sets:
14
+
15
+ ```
16
+ Content-Security-Policy: default-src 'none'; img-src data:; style-src 'sha256-<digest>'
17
+ X-Content-Type-Options: nosniff
18
+ ```
19
+
20
+ `default-src 'none'` blocks everything by default. `img-src data:` allows only the inlined logo. `style-src 'sha256-...'` allows only a `<style>` block whose contents match a digest computed at module load — stricter than `'unsafe-inline'`, and any drift between the rendered CSS and the committed hash causes the browser to refuse the stylesheet. Inline `style=""` attributes are avoided entirely (they require separate `style-src-attr` / `'unsafe-hashes'` machinery). The `renderHtml` test suite asserts the hash matches the rendered `<style>` contents to prevent silent drift.
21
+
22
+ **Auth code not echoed.** The success page deliberately does not render the received `code` in the HTML (RFC 8252 §8.1 interception mitigation).
23
+
24
+ **XSS escape.** The only untrusted input rendered into the page is `error_description` from the IdP. It is HTML-escaped before interpolation; a regression test feeds `<script>alert(1)</script>` and asserts the escaped form.
@@ -0,0 +1,21 @@
1
+ # Callback server
2
+
3
+ The loopback HTTP listener that receives the OAuth authorization code redirect, per [RFC 8252 §7.3](https://datatracker.ietf.org/doc/html/rfc8252#section-7.3). Lives in `src/oauth/callbackServer.ts`; the public surface is defined by the module's exported types.
4
+
5
+ ## Loopback bind
6
+
7
+ RFC 8252 §7.3 says clients SHOULD NOT assume a particular IP version is available and RECOMMENDS trying both. Implementation: bind `127.0.0.1` on an ephemeral port; on `EAFNOSUPPORT` / `EADDRNOTAVAIL` (no IPv4 loopback configured on this host) fall back to `[::1]` on a fresh ephemeral port. The returned `redirectUri` uses whichever literal actually got bound, so the browser connects to exactly what the authorization server redirects it to — no reliance on `localhost` DNS resolution.
8
+
9
+ Request handling additionally rejects non-loopback `remoteAddress` values with `403` as defense in depth against DNS-rebinding-style pivots — the listener only binds to a loopback interface, but enforcing it at the handler costs nothing.
10
+
11
+ ## RFC 8252 conformance
12
+
13
+ | Clause | Requirement | Handled by |
14
+ | ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
15
+ | §7.3 | IP literal, not `localhost` | Binds `127.0.0.1` or `[::1]`; redirect URI uses the literal. |
16
+ | §7.3 | Ephemeral OS-assigned port | `listen(0, ...)`; port read from `listening` event. |
17
+ | §7.3 | Attempt both IPv4 and IPv6 | IPv4-first, IPv6 fallback when the IPv4 family is unavailable. |
18
+ | §8.3 | Open the port only during the auth request | One-shot: closed on first consuming request, timeout, or abort. |
19
+ | §8.3 | Listen on loopback only | IP literal only; `remoteAddress` check as defense in depth. |
20
+ | §8.1 | PKCE | Out of scope — owned by the auth-URL + token-exchange layer. |
21
+ | §8.1 | Auth code interception mitigation | Success HTML does not echo `code`; CSP locks the page down (see [`callback-page.md`](./callback-page.md)). |
@@ -0,0 +1,15 @@
1
+ # OAuth flow — context
2
+
3
+ `@deque/axe-auth` is the native half of a browser-based OAuth 2.0 Authorization Code + PKCE flow for enterprise customers whose security policies prohibit static API keys. The MCP server itself runs in Docker with no display or interactive terminal, so per [RFC 8252 §7.3](https://datatracker.ietf.org/doc/html/rfc8252#section-7.3) authentication is delegated to this CLI on the developer's host — it opens a browser, receives the redirect on a loopback port, exchanges the code for tokens, and stores a refresh token in the system keychain. The container reads access tokens from the CLI via an environment variable.
4
+
5
+ ## Scope of this module
6
+
7
+ This package currently implements only the loopback listener and its response page (internal issue #418). PKCE, auth URL construction, browser launch, token exchange, and keychain storage are owned by sibling issues under internal epic #410.
8
+
9
+ See [`callback-server.md`](./callback-server.md) for the listener API and [`callback-page.md`](./callback-page.md) for the HTML response.
10
+
11
+ ## References
12
+
13
+ - [RFC 8252 — OAuth 2.0 for Native Apps](https://datatracker.ietf.org/doc/html/rfc8252)
14
+ - [RFC 7636 — PKCE](https://datatracker.ietf.org/doc/html/rfc7636)
15
+ - Internal epic #410
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deque/axe-auth",
3
- "version": "1.1.0-next.97bcb8e6",
3
+ "version": "1.1.0-next.9bc60204",
4
4
  "description": "CLI authentication utility for Deque services",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "commonjs",
@@ -11,7 +11,9 @@
11
11
  },
12
12
  "files": [
13
13
  "dist",
14
- "!dist/**/*.test.*"
14
+ "!dist/**/*.test.*",
15
+ "docs",
16
+ "credits.json"
15
17
  ],
16
18
  "publishConfig": {
17
19
  "access": "public",
@@ -20,13 +22,24 @@
20
22
  "engines": {
21
23
  "node": ">=22.13.0"
22
24
  },
25
+ "dependencies": {
26
+ "@napi-rs/keyring": "^1.2.0",
27
+ "remove-trailing-slash": "^0.1.1",
28
+ "ts-dedent": "^2.2.0"
29
+ },
23
30
  "devDependencies": {
24
31
  "@types/node": "^22.13.10",
32
+ "c8": "^10.1.3",
25
33
  "tsx": "^4.20.6",
26
34
  "typescript": "^5.9.3"
27
35
  },
28
36
  "scripts": {
29
37
  "build": "tsc",
30
- "test": "tsx --test 'src/**/*.test.ts'"
38
+ "test": "tsx --test 'src/**/*.test.ts'",
39
+ "coverage": "c8 pnpm test",
40
+ "register-dev-client": "tsx scripts/registerDevClient.ts",
41
+ "smoke-authorize": "tsx scripts/smokeAuthorize.ts",
42
+ "smoke-cli": "tsx scripts/smokeCLI.ts",
43
+ "manual-authorize": "tsx scripts/manualAuthorize.ts"
31
44
  }
32
45
  }