@nebulr-group/bridge-cli 0.1.0-beta.0 → 0.1.0

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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -18
  3. package/dist/auth/api-client.d.ts +48 -0
  4. package/dist/auth/api-client.d.ts.map +1 -0
  5. package/dist/auth/api-client.js +109 -0
  6. package/dist/auth/api-client.js.map +1 -0
  7. package/dist/auth/browser-open.d.ts +12 -0
  8. package/dist/auth/browser-open.d.ts.map +1 -0
  9. package/dist/auth/browser-open.js +82 -0
  10. package/dist/auth/browser-open.js.map +1 -0
  11. package/dist/auth/loopback.d.ts +63 -0
  12. package/dist/auth/loopback.d.ts.map +1 -0
  13. package/dist/auth/loopback.js +290 -0
  14. package/dist/auth/loopback.js.map +1 -0
  15. package/dist/auth/pkce.d.ts +19 -0
  16. package/dist/auth/pkce.d.ts.map +1 -0
  17. package/dist/auth/pkce.js +46 -0
  18. package/dist/auth/pkce.js.map +1 -0
  19. package/dist/auth/relative-time.d.ts +2 -0
  20. package/dist/auth/relative-time.d.ts.map +1 -0
  21. package/dist/auth/relative-time.js +27 -0
  22. package/dist/auth/relative-time.js.map +1 -0
  23. package/dist/commands/auth/config.command.d.ts +8 -0
  24. package/dist/commands/auth/config.command.d.ts.map +1 -0
  25. package/dist/commands/auth/config.command.js +60 -0
  26. package/dist/commands/auth/config.command.js.map +1 -0
  27. package/dist/commands/auth/login.command.d.ts +20 -0
  28. package/dist/commands/auth/login.command.d.ts.map +1 -0
  29. package/dist/commands/auth/login.command.js +141 -0
  30. package/dist/commands/auth/login.command.js.map +1 -0
  31. package/dist/commands/auth/logout.command.d.ts +14 -0
  32. package/dist/commands/auth/logout.command.d.ts.map +1 -0
  33. package/dist/commands/auth/logout.command.js +57 -0
  34. package/dist/commands/auth/logout.command.js.map +1 -0
  35. package/dist/commands/auth/status.command.d.ts +14 -0
  36. package/dist/commands/auth/status.command.d.ts.map +1 -0
  37. package/dist/commands/auth/status.command.js +59 -0
  38. package/dist/commands/auth/status.command.js.map +1 -0
  39. package/dist/commands/auth.command.d.ts +10 -1
  40. package/dist/commands/auth.command.d.ts.map +1 -1
  41. package/dist/commands/auth.command.js +13 -55
  42. package/dist/commands/auth.command.js.map +1 -1
  43. package/dist/commands/guide.command.js +3 -3
  44. package/dist/config.d.ts +24 -0
  45. package/dist/config.d.ts.map +1 -1
  46. package/dist/config.js +79 -9
  47. package/dist/config.js.map +1 -1
  48. package/dist/credentials.d.ts +57 -0
  49. package/dist/credentials.d.ts.map +1 -0
  50. package/dist/credentials.js +136 -0
  51. package/dist/credentials.js.map +1 -0
  52. package/package.json +2 -2
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Single-shot loopback HTTP server for the PKCE auth flow.
3
+ *
4
+ * Per RFC 8252:
5
+ * - Bind to `127.0.0.1` (NOT `localhost` — DNS spoofing risk).
6
+ * - Use an OS-assigned ephemeral port (`port: 0`).
7
+ * - Handle exactly one request, then close the listener.
8
+ *
9
+ * The server understands two response shapes from the IdP:
10
+ * `?code=<code>&state=<state>` → success
11
+ * `?error=access_denied&state=<state>` → user cancelled (or other error)
12
+ *
13
+ * It also CSRF-checks the returned `state` against the value the caller
14
+ * generated.
15
+ */
16
+ import * as http from 'node:http';
17
+ /**
18
+ * Bridge wordmark — inlined SVG so the page renders without any network fetch.
19
+ * `fill="currentColor"` makes it themable via CSS `color`.
20
+ * Source: bridge-cloud-views/static/assets/logos/bridge_logo.svg (color stripped).
21
+ */
22
+ const BRIDGE_LOGO = `<svg viewBox="0 0 818 148" xmlns="http://www.w3.org/2000/svg" aria-label="The Bridge" role="img"><path d="M668.241 0.0799866C684.368 0.0801786 696.657 3.98507 705.105 11.7929C713.026 19.1143 717.515 29.8687 718.572 44.0547C718.691 45.659 717.471 47.0329 715.868 47.1651L686.915 49.5511C685.213 49.6914 683.752 48.3801 683.562 46.6823C682.997 41.6277 681.537 37.8701 679.183 35.4091C676.367 32.3372 672.272 30.8008 666.896 30.8007C661.392 30.8007 656.783 32.5927 653.071 36.1767C649.487 39.6326 646.8 44.5604 645.008 50.9599C643.216 57.2319 642.319 64.5929 642.319 73.0409C642.319 81.4885 643.279 88.657 645.199 94.5448C647.119 100.433 649.936 104.977 653.648 108.177C657.36 111.248 661.776 112.785 666.896 112.785C672.656 112.785 677.135 110.734 680.334 106.639C683.534 102.543 685.136 96.5923 685.136 88.7851C685.136 87.8304 684.362 87.0565 683.408 87.0565H661.447C659.79 87.0565 658.447 85.7134 658.447 84.0565V62.9833C658.447 61.3265 659.79 59.9833 661.447 59.9833H716.695C718.352 59.9833 719.695 61.3265 719.695 62.9833V143.96C719.695 145.617 718.352 146.96 716.695 146.96H692.4C690.803 146.96 689.487 145.709 689.404 144.115L687.838 113.854C687.83 113.685 687.69 113.553 687.521 113.553C687.364 113.553 687.231 113.668 687.207 113.823C685.525 124.573 681.955 132.931 676.497 138.897C671.121 144.913 663.566 147.921 653.838 147.921C644.111 147.921 635.536 144.782 628.113 138.511C620.817 132.239 615.12 123.537 611.024 112.401C607.056 101.137 605.071 88.2723 605.071 73.8085C605.071 59.0885 607.439 46.2237 612.175 35.2157C617.039 24.0798 624.145 15.4408 633.489 9.29678C642.961 3.153 654.545 0.0799866 668.241 0.0799866Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M245.083 1.04092C254.682 1.041 262.683 2.63982 269.083 5.83975C275.482 9.03973 280.154 13.3281 283.098 18.704C286.17 24.0797 287.706 29.9675 287.707 36.3671C287.707 44.5587 285.21 51.855 280.218 58.2548C275.401 64.5932 268.511 68.7991 259.55 70.8689C259.398 70.9041 259.289 71.0397 259.289 71.1961C259.289 71.3696 259.423 71.5143 259.596 71.5302C269.811 72.4674 277.582 75.9152 282.905 81.871C288.409 87.759 291.161 95.9522 291.161 106.448C291.161 119.12 286.489 129.04 277.145 136.208C267.929 143.376 255.257 146.96 239.129 146.96H187.986C186.329 146.96 184.986 145.617 184.986 143.96V4.04093C184.986 2.38407 186.329 1.04092 187.986 1.04092H245.083ZM221.657 112.472C221.657 114.128 223 115.472 224.657 115.472H235.866C241.754 115.472 246.104 114.191 248.92 111.631C251.864 108.943 253.338 105.231 253.338 100.495C253.338 95.7595 251.864 92.1125 248.92 89.5526C246.104 86.8649 241.753 85.5214 235.866 85.5214H224.657C223 85.5214 221.657 86.8645 221.657 88.5214V112.472ZM221.657 60.4403C221.657 62.0972 223 63.4403 224.657 63.4403H235.098C240.729 63.4403 244.953 62.0964 247.769 59.4091C250.585 56.5931 251.994 52.7506 251.994 47.8866C251.993 42.8953 250.585 39.0549 247.769 36.3671C244.953 33.6797 240.729 32.3359 235.098 32.3358H224.657C223 32.3358 221.657 33.679 221.657 35.3358V60.4403Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M365.258 1.04092C379.849 1.04104 390.793 4.23949 398.088 10.6386C405.384 17.0386 409.034 25.5527 409.034 36.1767C409.034 45.0084 406.537 52.7533 401.545 59.4091C396.593 65.8845 388.87 70.5342 378.376 73.357C378.227 73.397 378.123 73.5319 378.123 73.6857C378.123 73.8626 378.259 74.0098 378.436 74.0249C384.683 74.5595 389.507 76.0248 392.906 78.4169C396.489 80.8489 399.176 84.4331 400.968 89.1689C402.76 93.7768 404.362 100.562 405.77 109.521C407.434 119.12 408.905 126.863 410.185 132.751C411.588 138.62 413.246 143.283 415.158 146.735C415.171 146.758 415.177 146.783 415.177 146.809C415.177 146.892 415.11 146.96 415.026 146.96H377.595C376.304 146.96 375.152 146.135 374.778 144.899C373.912 142.04 373.171 139.015 372.553 135.824C371.913 131.728 371.019 126.031 369.867 118.735C368.715 110.288 367.689 104.335 366.793 100.879C365.897 97.4234 364.361 94.8639 362.185 93.2001C360.009 91.5363 356.746 90.7041 352.394 90.704H345.026C343.369 90.704 342.026 92.0472 342.026 93.704V143.96C342.026 145.617 340.683 146.96 339.026 146.96H308.355C306.698 146.96 305.355 145.617 305.355 143.96V4.04093C305.355 2.38407 306.698 1.04092 308.355 1.04092H365.258ZM342.026 62.1689C342.026 63.8257 343.369 65.1689 345.026 65.1689H354.313C360.073 65.1689 364.554 63.7602 367.754 60.9442C370.954 58.0003 372.553 53.9674 372.553 48.8476C372.553 43.7278 370.954 39.6948 367.754 36.7509C364.554 33.8069 360.073 32.3358 354.313 32.3358H345.026C343.369 32.3358 342.026 33.679 342.026 35.3358V62.1689Z" fill="currentColor"/><path d="M463.892 143.96C463.892 145.617 462.549 146.96 460.892 146.96H430.221C428.564 146.96 427.221 145.617 427.221 143.96V4.04093C427.221 2.38407 428.564 1.04092 430.221 1.04092H460.892C462.549 1.04092 463.892 2.38407 463.892 4.04092V143.96Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M526.892 1.04092C540.716 1.04106 552.556 4.11101 562.412 10.2548C572.395 16.3987 579.947 24.9756 585.067 35.9833C590.315 46.9913 592.939 59.6005 592.939 73.8085C592.939 88.6562 590.315 101.584 585.067 112.592C579.947 123.599 572.395 132.111 562.412 138.127C552.556 144.015 540.716 146.96 526.892 146.96H484.962C483.306 146.96 481.962 145.617 481.962 143.96V4.04093C481.962 2.38407 483.306 1.04092 484.962 1.04092H526.892ZM518.443 111.127C518.443 112.784 519.786 114.127 521.443 114.127H526.315C535.531 114.127 542.699 110.929 547.819 104.529C553.067 98.1293 555.691 87.8882 555.691 73.8085C555.691 60.1128 553.067 50.065 547.819 43.6649C542.571 37.1369 535.403 33.871 526.315 33.871H521.443C519.786 33.871 518.443 35.2141 518.443 36.871V111.127Z" fill="currentColor"/><path d="M817.409 29.3358C817.409 30.9927 816.066 32.3358 814.409 32.3358H775.866C774.209 32.3358 772.866 33.679 772.866 35.3358V55.6386C772.866 57.2954 774.209 58.6386 775.866 58.6386H811.722C813.379 58.6386 814.722 59.9817 814.722 61.6386V84.247C814.722 85.9038 813.379 87.247 811.722 87.247H775.866C774.209 87.247 772.866 88.5901 772.866 90.247V112.472C772.866 114.128 774.209 115.472 775.866 115.472H814.602C816.259 115.472 817.602 116.815 817.602 118.472V143.96C817.602 145.617 816.259 146.96 814.602 146.96H739.385C737.728 146.96 736.385 145.617 736.385 143.96V4.04093C736.385 2.38407 737.728 1.04092 739.385 1.04092H814.409C816.066 1.04092 817.409 2.38407 817.409 4.04092V29.3358Z" fill="currentColor"/><path d="M24.978 46.28C6.738 46.28 0.21 38.024 0.402 30.536C0.402 22.856 7.122 14.792 17.682 14.792C23.826 14.792 28.434 17.48 29.586 21.512C29.586 21.896 29.778 22.28 29.778 22.472C29.778 23.432 29.01 23.816 27.282 23.432C25.938 23.048 25.362 22.664 23.634 22.664C18.834 22.664 16.338 25.736 16.338 28.616C16.146 33.416 22.098 34.568 27.858 34.568C30.162 34.568 32.466 34.376 34.194 34.184C51.858 32.264 106.194 19.4 135.378 19.4C142.482 19.4 147.666 20.168 150.546 21.704C151.314 22.088 151.698 22.472 151.698 22.856C151.698 23.24 150.93 23.624 149.394 23.816C96.786 29.96 58.194 43.016 33.042 45.704C30.354 46.088 27.666 46.28 24.978 46.28ZM134.226 141.896C128.274 141.896 123.666 139.208 122.514 135.176C122.514 134.792 122.322 134.408 122.322 134.024C122.322 133.064 123.09 132.872 124.818 133.256C126.162 133.448 126.738 134.024 128.466 134.024C133.266 134.024 135.762 130.952 135.762 127.88C135.762 123.272 130.002 122.12 124.242 122.12C121.938 122.12 119.634 122.312 117.906 122.504C100.242 124.424 45.906 137.096 16.722 137.096C9.618 137.096 4.434 136.52 1.362 134.792C0.786 134.408 0.402 134.024 0.402 133.832C0.402 133.448 1.17 133.064 2.706 132.872C55.314 126.728 93.906 113.672 119.058 110.792C121.746 110.6 124.434 110.408 127.122 110.408C145.362 110.408 151.89 118.472 151.698 126.152C151.698 133.64 144.978 141.896 134.226 141.896Z" fill="currentColor"/></svg>`;
23
+ /**
24
+ * Shared <style> block — matches bridge-cloud-views / bridge-admin-ui brand tokens
25
+ * (see bridge-cloud-views/src/global.css). Light mode, Inter, Bridge purple accent,
26
+ * slate text scale, white card with subtle border.
27
+ */
28
+ const SHARED_STYLES = `
29
+ :root {
30
+ --color-bridge-purple-1: #8049ff;
31
+ --color-bridge-green-1: #c4ffb8;
32
+ --color-bridge-green-2: #1fb800;
33
+ --color-bridge-red: #d6210b;
34
+ --color-background: #ffffff;
35
+ --color-background-muted: #f8fafc;
36
+ --color-foreground: #0f172a;
37
+ --color-foreground-muted: #64748b;
38
+ --color-border: #e2e8f0;
39
+ --radius-lg: 0.75rem;
40
+ }
41
+ * { box-sizing: border-box; }
42
+ body {
43
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
44
+ background: var(--color-background-muted);
45
+ color: var(--color-foreground);
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ min-height: 100vh;
50
+ margin: 0;
51
+ -webkit-font-smoothing: antialiased;
52
+ -moz-osx-font-smoothing: grayscale;
53
+ }
54
+ .card {
55
+ text-align: center;
56
+ padding: 3rem 3.5rem;
57
+ border-radius: var(--radius-lg);
58
+ background: var(--color-background);
59
+ border: 1px solid var(--color-border);
60
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.06);
61
+ max-width: 28rem;
62
+ }
63
+ .logo {
64
+ color: var(--color-foreground);
65
+ width: 7rem;
66
+ height: auto;
67
+ margin: 0 auto 2rem;
68
+ display: block;
69
+ }
70
+ .icon {
71
+ width: 3rem;
72
+ height: 3rem;
73
+ margin: 0 auto 1.25rem;
74
+ display: block;
75
+ }
76
+ h1 {
77
+ font-size: 1.375rem;
78
+ font-weight: 600;
79
+ margin: 0 0 0.5rem;
80
+ letter-spacing: -0.01em;
81
+ color: var(--color-foreground);
82
+ }
83
+ p {
84
+ margin: 0;
85
+ color: var(--color-foreground-muted);
86
+ font-size: 0.9375rem;
87
+ line-height: 1.55;
88
+ }
89
+ `;
90
+ const FONT_LINK = `<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">`;
91
+ const SUCCESS_ICON = `<svg class="icon" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle cx="24" cy="24" r="22" fill="#c4ffb8" stroke="#1fb800" stroke-width="2"/><path d="M14 24l7 7 13-14" fill="none" stroke="#1fb800" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
92
+ const FAILURE_ICON = `<svg class="icon" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle cx="24" cy="24" r="22" fill="#fee2e2" stroke="#d6210b" stroke-width="2"/><path d="M16 16l16 16M32 16L16 32" fill="none" stroke="#d6210b" stroke-width="3" stroke-linecap="round"/></svg>`;
93
+ const DEFAULT_SUCCESS_HTML = `<!doctype html>
94
+ <html lang="en">
95
+ <head>
96
+ <meta charset="utf-8">
97
+ <meta name="viewport" content="width=device-width, initial-scale=1">
98
+ <title>Bridge CLI — Login successful</title>
99
+ ${FONT_LINK}
100
+ <style>${SHARED_STYLES}</style>
101
+ </head>
102
+ <body>
103
+ <main class="card">
104
+ <span class="logo">${BRIDGE_LOGO}</span>
105
+ ${SUCCESS_ICON}
106
+ <h1>Login successful</h1>
107
+ <p>You can close this tab and return to your terminal.</p>
108
+ </main>
109
+ </body>
110
+ </html>`;
111
+ const FAILURE_HTML = (msg) => `<!doctype html>
112
+ <html lang="en">
113
+ <head>
114
+ <meta charset="utf-8">
115
+ <meta name="viewport" content="width=device-width, initial-scale=1">
116
+ <title>Bridge CLI — Login failed</title>
117
+ ${FONT_LINK}
118
+ <style>${SHARED_STYLES} h1 { color: var(--color-bridge-red); }</style>
119
+ </head>
120
+ <body>
121
+ <main class="card">
122
+ <span class="logo">${BRIDGE_LOGO}</span>
123
+ ${FAILURE_ICON}
124
+ <h1>Login failed</h1>
125
+ <p>${escapeHtml(msg)}</p>
126
+ </main>
127
+ </body>
128
+ </html>`;
129
+ /**
130
+ * Parses the loopback callback URL to extract `code`, `state`, `error`, and
131
+ * `error_description` query parameters.
132
+ *
133
+ * Exposed for unit testing.
134
+ */
135
+ export function parseCallbackUrl(rawUrl) {
136
+ // The `?` at the front is enough; we don't care about base validity.
137
+ const url = new URL(rawUrl, 'http://127.0.0.1');
138
+ return {
139
+ code: url.searchParams.get('code') ?? undefined,
140
+ state: url.searchParams.get('state') ?? undefined,
141
+ error: url.searchParams.get('error') ?? undefined,
142
+ errorDescription: url.searchParams.get('error_description') ?? undefined,
143
+ pathname: url.pathname,
144
+ };
145
+ }
146
+ /**
147
+ * Validates a parsed callback against the expected CSRF state. Returns the
148
+ * verified `{code, state}` or throws a friendly error.
149
+ */
150
+ export function verifyCallback(parsed, expectedState) {
151
+ if (parsed.error) {
152
+ if (parsed.error === 'access_denied') {
153
+ throw new LoopbackError('Login cancelled.', 'access_denied');
154
+ }
155
+ const desc = parsed.errorDescription ? `: ${parsed.errorDescription}` : '';
156
+ throw new LoopbackError(`Authorization failed (${parsed.error})${desc}`, parsed.error);
157
+ }
158
+ if (!parsed.code) {
159
+ throw new LoopbackError('Authorization callback was missing the `code` parameter.', 'invalid_callback');
160
+ }
161
+ if (!parsed.state) {
162
+ throw new LoopbackError('Authorization callback was missing the `state` parameter.', 'invalid_callback');
163
+ }
164
+ if (!constantTimeEqual(parsed.state, expectedState)) {
165
+ throw new LoopbackError('State mismatch on authorization callback (possible CSRF).', 'state_mismatch');
166
+ }
167
+ return { code: parsed.code, state: parsed.state };
168
+ }
169
+ /**
170
+ * Boots the single-shot loopback listener on a random ephemeral port.
171
+ *
172
+ * Returns immediately with `{ redirectUri, result }`. The caller passes
173
+ * `redirectUri` to the IdP, then awaits `result` for the verified `{code, state}`.
174
+ *
175
+ * The server:
176
+ * - Closes itself after the first GET to `/callback` completes its response.
177
+ * - Closes itself if the token-exchange caller invokes `close()` (e.g. on a
178
+ * later failure that wants to abort the wait).
179
+ * - Times out after `timeoutMs` (default 5 minutes).
180
+ */
181
+ export async function startLoopback(opts) {
182
+ const { expectedState, successRedirectUrl, successHtml = DEFAULT_SUCCESS_HTML, timeoutMs = 5 * 60_000, } = opts;
183
+ let resolveResult;
184
+ let rejectResult;
185
+ const result = new Promise((res, rej) => {
186
+ resolveResult = res;
187
+ rejectResult = rej;
188
+ });
189
+ const server = http.createServer((req, res) => {
190
+ // Only one path matters; ignore favicon/etc.
191
+ const parsed = parseCallbackUrl(req.url ?? '/');
192
+ if (parsed.pathname !== '/callback') {
193
+ res.statusCode = 404;
194
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
195
+ res.end('Not found');
196
+ return;
197
+ }
198
+ try {
199
+ const verified = verifyCallback(parsed, expectedState);
200
+ if (successRedirectUrl) {
201
+ // Bounce the browser to the hosted success page on cloud-views.
202
+ // The user lands on auth.thebridge.dev/cli/login-complete instead of
203
+ // staying on 127.0.0.1 — branded UX.
204
+ res.statusCode = 302;
205
+ res.setHeader('Location', successRedirectUrl);
206
+ res.end(() => {
207
+ server.close();
208
+ });
209
+ }
210
+ else {
211
+ res.statusCode = 200;
212
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
213
+ res.end(successHtml, () => {
214
+ // Close listener AFTER the response flushes so the user actually sees the page.
215
+ server.close();
216
+ });
217
+ }
218
+ resolveResult(verified);
219
+ }
220
+ catch (err) {
221
+ const msg = err instanceof Error ? err.message : String(err);
222
+ res.statusCode = 400;
223
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
224
+ res.end(FAILURE_HTML(msg), () => {
225
+ server.close();
226
+ });
227
+ rejectResult(err instanceof Error ? err : new Error(msg));
228
+ }
229
+ });
230
+ // Bind on 127.0.0.1 explicitly (RFC 8252). port: 0 → OS picks a free port.
231
+ await new Promise((res, rej) => {
232
+ server.once('error', rej);
233
+ server.listen(0, '127.0.0.1', () => {
234
+ server.removeListener('error', rej);
235
+ res();
236
+ });
237
+ });
238
+ const addr = server.address();
239
+ const redirectUri = `http://127.0.0.1:${addr.port}/callback`;
240
+ // Hard timeout — never leave a port bound forever.
241
+ const timeoutHandle = setTimeout(() => {
242
+ rejectResult(new LoopbackError(`Timed out waiting for the browser callback after ${Math.round(timeoutMs / 1000)}s.`, 'timeout'));
243
+ server.close();
244
+ }, timeoutMs);
245
+ // Don't keep the process alive for the timeout once the server closes.
246
+ timeoutHandle.unref?.();
247
+ // Make sure the timeout is cleared whichever way the promise settles.
248
+ result.finally(() => clearTimeout(timeoutHandle)).catch(() => {
249
+ /* swallow — we only attached this for cleanup */
250
+ });
251
+ return {
252
+ redirectUri,
253
+ result,
254
+ close: () => {
255
+ try {
256
+ server.close();
257
+ }
258
+ catch {
259
+ // server already closed; ignore.
260
+ }
261
+ },
262
+ };
263
+ }
264
+ export class LoopbackError extends Error {
265
+ kind;
266
+ constructor(message, kind) {
267
+ super(message);
268
+ this.name = 'LoopbackError';
269
+ this.kind = kind;
270
+ }
271
+ }
272
+ /** Constant-time string comparison for CSRF state checking. */
273
+ function constantTimeEqual(a, b) {
274
+ if (a.length !== b.length)
275
+ return false;
276
+ let mismatch = 0;
277
+ for (let i = 0; i < a.length; i++) {
278
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
279
+ }
280
+ return mismatch === 0;
281
+ }
282
+ function escapeHtml(s) {
283
+ return s
284
+ .replace(/&/g, '&amp;')
285
+ .replace(/</g, '&lt;')
286
+ .replace(/>/g, '&gt;')
287
+ .replace(/"/g, '&quot;')
288
+ .replace(/'/g, '&#39;');
289
+ }
290
+ //# sourceMappingURL=loopback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopback.js","sourceRoot":"","sources":["../../src/auth/loopback.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /** Encodes a Buffer as base64url (RFC 4648 §5) with no `=` padding. */
2
+ export declare function base64UrlEncode(buf: Buffer): string;
3
+ /**
4
+ * Generates a cryptographically random PKCE `code_verifier`.
5
+ *
6
+ * Output: 43-character base64url string (the RFC 7636 lower bound). 32 bytes
7
+ * of entropy provides ~256 bits of security.
8
+ */
9
+ export declare function generateCodeVerifier(): string;
10
+ /**
11
+ * Computes the PKCE `code_challenge` for an S256 verifier:
12
+ * challenge = base64url(sha256(verifier)) (no padding)
13
+ */
14
+ export declare function codeChallengeFor(verifier: string): string;
15
+ /**
16
+ * Generates a CSRF `state` value (32 random bytes, base64url-encoded).
17
+ */
18
+ export declare function generateState(): string;
19
+ //# sourceMappingURL=pkce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/auth/pkce.ts"],"names":[],"mappings":"AAeA,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * PKCE (RFC 7636) and CSRF state helpers for bridge-cli's loopback auth flow.
3
+ *
4
+ * - `code_verifier`: 43–128 chars from the unreserved set, cryptographically
5
+ * random. We generate 32 bytes of entropy and base64url-encode without
6
+ * padding → 43 characters, the lower bound of the spec.
7
+ * - `code_challenge`: SHA-256 hash of the verifier, base64url-encoded without
8
+ * padding (the only `code_challenge_method` we support is S256).
9
+ * - `state`: 32 random bytes, base64url-encoded — the CSRF token the loopback
10
+ * server checks against the value the IdP echoes back.
11
+ *
12
+ * The verifier and the JWT it eventually unlocks are NEVER logged.
13
+ */
14
+ import * as crypto from 'node:crypto';
15
+ /** Encodes a Buffer as base64url (RFC 4648 §5) with no `=` padding. */
16
+ export function base64UrlEncode(buf) {
17
+ return buf
18
+ .toString('base64')
19
+ .replace(/\+/g, '-')
20
+ .replace(/\//g, '_')
21
+ .replace(/=+$/g, '');
22
+ }
23
+ /**
24
+ * Generates a cryptographically random PKCE `code_verifier`.
25
+ *
26
+ * Output: 43-character base64url string (the RFC 7636 lower bound). 32 bytes
27
+ * of entropy provides ~256 bits of security.
28
+ */
29
+ export function generateCodeVerifier() {
30
+ return base64UrlEncode(crypto.randomBytes(32));
31
+ }
32
+ /**
33
+ * Computes the PKCE `code_challenge` for an S256 verifier:
34
+ * challenge = base64url(sha256(verifier)) (no padding)
35
+ */
36
+ export function codeChallengeFor(verifier) {
37
+ const hash = crypto.createHash('sha256').update(verifier).digest();
38
+ return base64UrlEncode(hash);
39
+ }
40
+ /**
41
+ * Generates a CSRF `state` value (32 random bytes, base64url-encoded).
42
+ */
43
+ export function generateState() {
44
+ return base64UrlEncode(crypto.randomBytes(32));
45
+ }
46
+ //# sourceMappingURL=pkce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/auth/pkce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC,uEAAuE;AACvE,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,GAAG;SACP,QAAQ,CAAC,QAAQ,CAAC;SAClB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACzB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,eAAe,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;AACjD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC;IACnE,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,eAAe,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function formatRelativeTime(deltaMs: number): string;
2
+ //# sourceMappingURL=relative-time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relative-time.d.ts","sourceRoot":"","sources":["../../src/auth/relative-time.ts"],"names":[],"mappings":"AAkBA,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAS1D"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Tiny human-readable relative time formatter — avoids pulling in dayjs/date-fns.
3
+ *
4
+ * Examples:
5
+ * formatRelativeTime(+9 days) → "in 9 days"
6
+ * formatRelativeTime(+3 hours) → "in 3 hours"
7
+ * formatRelativeTime(0) → "just now"
8
+ * formatRelativeTime(-2 days) → "2 days ago"
9
+ */
10
+ const UNITS = [
11
+ { name: 'year', ms: 365 * 24 * 60 * 60 * 1000 },
12
+ { name: 'month', ms: 30 * 24 * 60 * 60 * 1000 },
13
+ { name: 'day', ms: 24 * 60 * 60 * 1000 },
14
+ { name: 'hour', ms: 60 * 60 * 1000 },
15
+ { name: 'minute', ms: 60 * 1000 },
16
+ { name: 'second', ms: 1000 },
17
+ ];
18
+ export function formatRelativeTime(deltaMs) {
19
+ const abs = Math.abs(deltaMs);
20
+ if (abs < 1000)
21
+ return 'just now';
22
+ const unit = UNITS.find((u) => abs >= u.ms) ?? UNITS[UNITS.length - 1];
23
+ const value = Math.round(abs / unit.ms);
24
+ const noun = `${unit.name}${value === 1 ? '' : 's'}`;
25
+ return deltaMs >= 0 ? `in ${value} ${noun}` : `${value} ${noun} ago`;
26
+ }
27
+ //# sourceMappingURL=relative-time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relative-time.js","sourceRoot":"","sources":["../../src/auth/relative-time.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,KAAK,GAAwC;IACjD,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE;IAC/C,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE;IAC/C,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE;IACxC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE;IACpC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE;IACjC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE;CAC7B,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC9B,IAAI,GAAG,GAAG,IAAI;QAAE,OAAO,UAAU,CAAC;IAElC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IAErD,OAAO,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,IAAI,MAAM,CAAC;AACvE,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `bridge auth config|mfa|password-policy` — the original auth subcommands
3
+ * that pre-date TBP-113. Kept as-is; just lifted out of `auth.command.ts`
4
+ * so the file stays small and testable.
5
+ */
6
+ import type { Command } from 'commander';
7
+ export declare function registerAuthConfigCommands(auth: Command): void;
8
+ //# sourceMappingURL=config.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.command.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/config.command.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAyD9D"}
@@ -0,0 +1,60 @@
1
+ import { getManagementClient } from '../../config.js';
2
+ import { outputSuccess, outputError } from '../../output.js';
3
+ export function registerAuthConfigCommands(auth) {
4
+ auth
5
+ .command('config')
6
+ .description('Get current auth configuration')
7
+ .action(async () => {
8
+ try {
9
+ const app = await getManagementClient().app.get();
10
+ outputSuccess({
11
+ mfaEnabled: app.mfaEnabled,
12
+ passkeysEnabled: app.passkeysEnabled,
13
+ magicLinkEnabled: app.magicLinkEnabled,
14
+ googleSsoEnabled: app.googleSsoEnabled,
15
+ azureAdSsoEnabled: app.azureAdSsoEnabled,
16
+ githubSsoEnabled: app.githubSsoEnabled,
17
+ linkedinSsoEnabled: app.linkedinSsoEnabled,
18
+ facebookSsoEnabled: app.facebookSsoEnabled,
19
+ appleSsoEnabled: app.appleSsoEnabled,
20
+ onboardingFlow: app.onboardingFlow,
21
+ tenantSelfSignup: app.tenantSelfSignup,
22
+ accessTokenTTL: app.accessTokenTTL,
23
+ refreshTokenTTL: app.refreshTokenTTL,
24
+ });
25
+ }
26
+ catch (err) {
27
+ outputError(err);
28
+ }
29
+ });
30
+ auth
31
+ .command('mfa')
32
+ .description('Enable or disable MFA')
33
+ .requiredOption('--enabled <bool>', 'true or false', (v) => v === 'true')
34
+ .action(async (opts) => {
35
+ try {
36
+ outputSuccess(await getManagementClient().app.update({ mfaEnabled: opts.enabled }));
37
+ }
38
+ catch (err) {
39
+ outputError(err);
40
+ }
41
+ });
42
+ auth
43
+ .command('password-policy')
44
+ .description('Update access token TTL')
45
+ .option('--access-token-ttl <seconds>', 'Access token TTL in seconds', parseInt)
46
+ .option('--refresh-token-ttl <seconds>', 'Refresh token TTL in seconds', parseInt)
47
+ .action(async (opts) => {
48
+ try {
49
+ const data = Object.fromEntries(Object.entries({
50
+ accessTokenTTL: opts.accessTokenTtl,
51
+ refreshTokenTTL: opts.refreshTokenTtl,
52
+ }).filter(([, v]) => v !== undefined));
53
+ outputSuccess(await getManagementClient().app.update(data));
54
+ }
55
+ catch (err) {
56
+ outputError(err);
57
+ }
58
+ });
59
+ }
60
+ //# sourceMappingURL=config.command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.command.js","sourceRoot":"","sources":["../../../src/commands/auth/config.command.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,UAAU,0BAA0B,CAAC,IAAa;IACtD,IAAI;SACD,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,gCAAgC,CAAC;SAC7C,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,mBAAmB,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;YAClD,aAAa,CAAC;gBACZ,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,eAAe,EAAE,GAAG,CAAC,eAAe;gBACpC,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;gBACtC,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;gBACtC,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;gBACxC,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;gBACtC,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;gBAC1C,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;gBAC1C,eAAe,EAAE,GAAG,CAAC,eAAe;gBACpC,cAAc,EAAE,GAAG,CAAC,cAAc;gBAClC,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;gBACtC,cAAc,EAAE,GAAG,CAAC,cAAc;gBAClC,eAAe,EAAE,GAAG,CAAC,eAAe;aACrC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,WAAW,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,IAAI;SACD,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CAAC,uBAAuB,CAAC;SACpC,cAAc,CAAC,kBAAkB,EAAE,eAAe,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC;SACxE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,IAAI,CAAC;YACH,aAAa,CAAC,MAAM,mBAAmB,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACtF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,WAAW,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,IAAI;SACD,OAAO,CAAC,iBAAiB,CAAC;SAC1B,WAAW,CAAC,yBAAyB,CAAC;SACtC,MAAM,CAAC,8BAA8B,EAAE,6BAA6B,EAAE,QAAQ,CAAC;SAC/E,MAAM,CAAC,+BAA+B,EAAE,8BAA8B,EAAE,QAAQ,CAAC;SACjF,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAC7B,MAAM,CAAC,OAAO,CAAC;gBACb,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,eAAe,EAAE,IAAI,CAAC,eAAe;aACtC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CACtC,CAAC;YACF,aAAa,CAAC,MAAM,mBAAmB,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,WAAW,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `bridge auth login` — browser-based loopback PKCE flow.
3
+ *
4
+ * Drives: PKCE pair → ephemeral loopback listener → open browser →
5
+ * await callback → exchange code → write credentials file.
6
+ *
7
+ * Default flow (per TBP-107 Q4): open the browser automatically, fall back
8
+ * to printing the URL if the open command fails or `--no-browser` is set.
9
+ *
10
+ * Hostnames (locked in TBP-107):
11
+ * Consent screen: `https://auth.thebridge.dev/cli/authorize`
12
+ * API: `https://api.thebridge.dev/v1/auth/cli/token`
13
+ *
14
+ * Both endpoints can be overridden for local dev:
15
+ * `BRIDGE_BASE_URL` → bridge-api host
16
+ * `BRIDGE_AUTH_BASE_URL` → consent screen host
17
+ */
18
+ import type { Command } from 'commander';
19
+ export declare function registerAuthLoginCommand(auth: Command): void;
20
+ //# sourceMappingURL=login.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.command.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/login.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAkB5D"}