@framedash/cli 0.1.2 → 0.1.3

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 (72) hide show
  1. package/README.md +41 -7
  2. package/dist/commands/alerts.d.ts.map +1 -1
  3. package/dist/commands/alerts.js +2 -1
  4. package/dist/commands/alerts.js.map +1 -1
  5. package/dist/commands/auth.d.ts.map +1 -1
  6. package/dist/commands/auth.js +25 -2
  7. package/dist/commands/auth.js.map +1 -1
  8. package/dist/commands/login.d.ts +2 -0
  9. package/dist/commands/login.d.ts.map +1 -0
  10. package/dist/commands/login.js +142 -0
  11. package/dist/commands/login.js.map +1 -0
  12. package/dist/commands/logout.d.ts +2 -0
  13. package/dist/commands/logout.d.ts.map +1 -0
  14. package/dist/commands/logout.js +79 -0
  15. package/dist/commands/logout.js.map +1 -0
  16. package/dist/commands/map-capture.d.ts +2 -1
  17. package/dist/commands/map-capture.d.ts.map +1 -1
  18. package/dist/commands/map-capture.js +38 -9
  19. package/dist/commands/map-capture.js.map +1 -1
  20. package/dist/commands/query.d.ts.map +1 -1
  21. package/dist/commands/query.js +12 -0
  22. package/dist/commands/query.js.map +1 -1
  23. package/dist/commands/run-profile-test.d.ts.map +1 -1
  24. package/dist/commands/run-profile-test.js +143 -26
  25. package/dist/commands/run-profile-test.js.map +1 -1
  26. package/dist/commands/threshold-profiles.d.ts +2 -0
  27. package/dist/commands/threshold-profiles.d.ts.map +1 -0
  28. package/dist/commands/threshold-profiles.js +24 -0
  29. package/dist/commands/threshold-profiles.js.map +1 -0
  30. package/dist/index.js +10 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/lib/config.d.ts +42 -1
  33. package/dist/lib/config.d.ts.map +1 -1
  34. package/dist/lib/config.js +44 -14
  35. package/dist/lib/config.js.map +1 -1
  36. package/dist/lib/create-client.d.ts +25 -7
  37. package/dist/lib/create-client.d.ts.map +1 -1
  38. package/dist/lib/create-client.js +160 -33
  39. package/dist/lib/create-client.js.map +1 -1
  40. package/dist/lib/formatters.js +93 -1
  41. package/dist/lib/formatters.js.map +1 -1
  42. package/dist/lib/oauth/loopback-server.d.ts +27 -0
  43. package/dist/lib/oauth/loopback-server.d.ts.map +1 -0
  44. package/dist/lib/oauth/loopback-server.js +164 -0
  45. package/dist/lib/oauth/loopback-server.js.map +1 -0
  46. package/dist/lib/oauth/manager.d.ts +36 -0
  47. package/dist/lib/oauth/manager.d.ts.map +1 -0
  48. package/dist/lib/oauth/manager.js +130 -0
  49. package/dist/lib/oauth/manager.js.map +1 -0
  50. package/dist/lib/oauth/pkce.d.ts +15 -0
  51. package/dist/lib/oauth/pkce.d.ts.map +1 -0
  52. package/dist/lib/oauth/pkce.js +24 -0
  53. package/dist/lib/oauth/pkce.js.map +1 -0
  54. package/dist/lib/oauth/token-endpoint.d.ts +39 -0
  55. package/dist/lib/oauth/token-endpoint.d.ts.map +1 -0
  56. package/dist/lib/oauth/token-endpoint.js +117 -0
  57. package/dist/lib/oauth/token-endpoint.js.map +1 -0
  58. package/dist/lib/oauth/token-store.d.ts +34 -0
  59. package/dist/lib/oauth/token-store.d.ts.map +1 -0
  60. package/dist/lib/oauth/token-store.js +164 -0
  61. package/dist/lib/oauth/token-store.js.map +1 -0
  62. package/dist/lib/run-command.js +2 -2
  63. package/dist/lib/run-command.js.map +1 -1
  64. package/dist/lib/run-profile-test-lib.d.ts +39 -0
  65. package/dist/lib/run-profile-test-lib.d.ts.map +1 -1
  66. package/dist/lib/run-profile-test-lib.js +47 -0
  67. package/dist/lib/run-profile-test-lib.js.map +1 -1
  68. package/dist/lib/uploader.d.ts +14 -1
  69. package/dist/lib/uploader.d.ts.map +1 -1
  70. package/dist/lib/uploader.js +26 -12
  71. package/dist/lib/uploader.js.map +1 -1
  72. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"create-client.d.ts","sourceRoot":"","sources":["../../src/lib/create-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,uBAAuB,CAAC;AAGjE;;;;;GAKG;AACH,wBAAgB,YAAY,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAClC,SAAS,CAmCX"}
1
+ {"version":3,"file":"create-client.d.ts","sourceRoot":"","sources":["../../src/lib/create-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAY,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAA2B,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEhF,MAAM,MAAM,mBAAmB,GAAG;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAY7D,8EAA8E;AAC9E,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,OAAO,CAAC,aAAa,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GACnD,iBAAiB,CAOnB;AAuBD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,aAAa,EACzB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,mBAAmB,GAC3B,SAAS,CAiBX"}
@@ -1,46 +1,173 @@
1
- import { ApiClient } from "@framedash/api-client";
1
+ import { ApiClient, ApiError } from "@framedash/api-client";
2
2
  import { error } from "./logger.js";
3
+ import { OAuthLoginRequiredError, OAuthTokenManager } from "./oauth/manager.js";
3
4
  /**
4
- * Create the CLI's API client. By default a request error prints a message and
5
- * exits the process (the right behavior for one-shot commands). Pass
6
- * `throwOnError` for a client used inside a retry loop (e.g. the run-profile-test
7
- * ingest poll), where a transient 429/5xx must be thrown and retried, not exit.
5
+ * ONE token manager per origin per process. Refresh tokens are single-use
6
+ * (rotation), so two independent managers over the same stored login would
7
+ * race: the second to refresh presents an already-consumed token, which the
8
+ * server treats as theft and answers by revoking the whole grant. Commands
9
+ * that build several clients (e.g. run-profile-test's exit-on-error client
10
+ * plus its throwing poll client) must therefore share rotation state.
8
11
  */
9
- export function createClient(baseUrl, apiKey, projectId, options) {
10
- if (options?.throwOnError) {
12
+ const managerCache = new Map();
13
+ /** Test-only: drop cached managers so each test starts from its own entry. */
14
+ export function resetOAuthManagerCacheForTests() {
15
+ managerCache.clear();
16
+ }
17
+ /**
18
+ * Process-shared token manager for an origin. Also used by non-ApiClient
19
+ * consumers (the map-capture uploader) so EVERY Bearer credential in the
20
+ * process rides on the same rotation state.
21
+ */
22
+ export function getSharedOAuthManager(baseUrl, credential) {
23
+ let manager = managerCache.get(credential.origin);
24
+ if (!manager) {
25
+ manager = new OAuthTokenManager(baseUrl, credential.origin, credential.entry);
26
+ managerCache.set(credential.origin, manager);
27
+ }
28
+ return manager;
29
+ }
30
+ /** Print an ApiError (with 429 rate-limit detail) and exit -- the default for one-shot commands. */
31
+ function printAndExit(err) {
32
+ if (err.status === 429) {
33
+ const retryAfter = err.retryAfter;
34
+ if (retryAfter !== undefined) {
35
+ error(`Rate limit exceeded (429). Retry after ${retryAfter}s.`);
36
+ }
37
+ else {
38
+ const reset = err.headers.get("X-RateLimit-Reset");
39
+ const resetNum = reset ? Number(reset) : Number.NaN;
40
+ const resetStr = !Number.isNaN(resetNum) && resetNum > 0
41
+ ? new Date(resetNum * 1000).toLocaleTimeString()
42
+ : "unknown";
43
+ error(`Rate limit exceeded (429). Resets at ${resetStr}.`);
44
+ }
45
+ }
46
+ else {
47
+ error(err.message);
48
+ }
49
+ process.exit(1);
50
+ }
51
+ /**
52
+ * Create the CLI's API client from the resolved credential. By default a
53
+ * request error prints a message and exits the process (the right behavior
54
+ * for one-shot commands). Pass `throwOnError` for a client used inside a
55
+ * retry loop (e.g. the run-profile-test ingest poll), where a transient
56
+ * 429/5xx must be thrown and retried, not exit.
57
+ *
58
+ * API keys go on X-API-Key as before. A stored OAuth login gets a wrapper
59
+ * that refreshes near expiry, retries exactly once after a 401, and persists
60
+ * rotated refresh tokens.
61
+ */
62
+ export function createClient(baseUrl, credential, projectId, options) {
63
+ const onError = options?.throwOnError
64
+ ? (err) => {
65
+ throw err;
66
+ }
67
+ : printAndExit;
68
+ if (credential.kind === "api-key") {
69
+ return new ApiClient({ baseUrl, apiKey: credential.apiKey, projectId, onError });
70
+ }
71
+ return new OAuthApiClient(baseUrl, projectId, getSharedOAuthManager(baseUrl, credential), options);
72
+ }
73
+ /**
74
+ * ApiClient facade for the OAuth path. Extends ApiClient so the rest of the
75
+ * CLI (typed against ApiClient) needs no changes, but every verb delegates
76
+ * to a short-lived inner client carrying the CURRENT access token from the
77
+ * token manager; the superclass instance itself never sends a request.
78
+ *
79
+ * 401 handling: refresh once, retry once. A second failure surfaces like any
80
+ * other API error. A dead grant (refresh -> invalid_grant) always prints the
81
+ * re-login instruction and exits, even in throwOnError mode -- retry loops
82
+ * (which swallow thrown errors) must not spin on an unrecoverable credential.
83
+ */
84
+ class OAuthApiClient extends ApiClient {
85
+ oauthBaseUrl;
86
+ oauthProjectId;
87
+ manager;
88
+ options;
89
+ constructor(oauthBaseUrl, oauthProjectId, manager, options) {
90
+ super({
91
+ baseUrl: oauthBaseUrl,
92
+ projectId: oauthProjectId,
93
+ // Never sent: all verbs are overridden to use the inner client.
94
+ accessToken: "oauth-managed-placeholder",
95
+ onError: (err) => {
96
+ throw err;
97
+ },
98
+ });
99
+ this.oauthBaseUrl = oauthBaseUrl;
100
+ this.oauthProjectId = oauthProjectId;
101
+ this.manager = manager;
102
+ this.options = options;
103
+ }
104
+ async get(path) {
105
+ return this.execute((client) => client.get(path));
106
+ }
107
+ async post(path, body) {
108
+ return this.execute((client) => client.post(path, body));
109
+ }
110
+ async patch(path, body) {
111
+ return this.execute((client) => client.patch(path, body));
112
+ }
113
+ async delete(path) {
114
+ return this.execute((client) => client.delete(path));
115
+ }
116
+ withProject(projectId) {
117
+ return new OAuthApiClient(this.oauthBaseUrl, projectId, this.manager, this.options);
118
+ }
119
+ buildInner(accessToken) {
11
120
  return new ApiClient({
12
- baseUrl,
13
- apiKey,
14
- projectId,
15
- onError(err) {
121
+ baseUrl: this.oauthBaseUrl,
122
+ projectId: this.oauthProjectId,
123
+ accessToken,
124
+ onError: (err) => {
16
125
  throw err;
17
126
  },
18
127
  });
19
128
  }
20
- return new ApiClient({
21
- baseUrl,
22
- apiKey,
23
- projectId,
24
- onError(err) {
25
- if (err.status === 429) {
26
- const retryAfter = err.retryAfter;
27
- if (retryAfter !== undefined) {
28
- error(`Rate limit exceeded (429). Retry after ${retryAfter}s.`);
129
+ async token(forceRefresh) {
130
+ try {
131
+ return forceRefresh ? await this.manager.forceRefresh() : await this.manager.getAccessToken();
132
+ }
133
+ catch (err) {
134
+ if (err instanceof OAuthLoginRequiredError) {
135
+ error(err.message);
136
+ process.exit(1);
137
+ }
138
+ throw err;
139
+ }
140
+ }
141
+ async execute(fn) {
142
+ const accessToken = await this.token(false);
143
+ try {
144
+ return await fn(this.buildInner(accessToken));
145
+ }
146
+ catch (err) {
147
+ if (isApiError(err) && err.status === 401) {
148
+ // The server rejected a token we believed valid (revoked access
149
+ // token, clock skew): refresh and retry exactly once.
150
+ const freshToken = await this.token(true);
151
+ try {
152
+ return await fn(this.buildInner(freshToken));
29
153
  }
30
- else {
31
- const reset = err.headers.get("X-RateLimit-Reset");
32
- const resetNum = reset ? Number(reset) : Number.NaN;
33
- const resetStr = !Number.isNaN(resetNum) && resetNum > 0
34
- ? new Date(resetNum * 1000).toLocaleTimeString()
35
- : "unknown";
36
- error(`Rate limit exceeded (429). Resets at ${resetStr}.`);
154
+ catch (retryErr) {
155
+ return this.reportError(retryErr);
37
156
  }
38
157
  }
39
- else {
40
- error(err.message);
41
- }
42
- process.exit(1);
43
- },
44
- });
158
+ return this.reportError(err);
159
+ }
160
+ }
161
+ reportError(err) {
162
+ if (isApiError(err) && !this.options?.throwOnError) {
163
+ printAndExit(err);
164
+ }
165
+ // throwOnError mode, or a non-API failure (network error): propagate,
166
+ // matching the api-key client (index.ts prints and exits).
167
+ throw err;
168
+ }
169
+ }
170
+ function isApiError(err) {
171
+ return err instanceof ApiError;
45
172
  }
46
173
  //# sourceMappingURL=create-client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-client.js","sourceRoot":"","sources":["../../src/lib/create-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC3B,OAAe,EACf,MAAc,EACd,SAAiB,EACjB,OAAoC;IAEpC,IAAI,OAAO,EAAE,YAAY,EAAE,CAAC;QAC3B,OAAO,IAAI,SAAS,CAAC;YACpB,OAAO;YACP,MAAM;YACN,SAAS;YACT,OAAO,CAAC,GAAa;gBACpB,MAAM,GAAG,CAAC;YACX,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,SAAS,CAAC;QACpB,OAAO;QACP,MAAM;QACN,SAAS;QACT,OAAO,CAAC,GAAa;YACpB,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACxB,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC;gBAClC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;oBAC9B,KAAK,CAAC,0CAA0C,UAAU,IAAI,CAAC,CAAC;gBACjE,CAAC;qBAAM,CAAC;oBACP,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;oBACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;oBACpD,MAAM,QAAQ,GACb,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC;wBACtC,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,kBAAkB,EAAE;wBAChD,CAAC,CAAC,SAAS,CAAC;oBACd,KAAK,CAAC,wCAAwC,QAAQ,GAAG,CAAC,CAAC;gBAC5D,CAAC;YACF,CAAC;iBAAM,CAAC;gBACP,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACpB,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"create-client.js","sourceRoot":"","sources":["../../src/lib/create-client.ts"],"names":[],"mappings":"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"}
@@ -10,13 +10,105 @@ export function formatOutput(data, format) {
10
10
  }
11
11
  }
12
12
  function formatTable(data) {
13
+ // A single object whose values contain nested arrays/objects (e.g. the
14
+ // dashboard's { kpis, dailyActiveUsers, topEvents }) renders one titled
15
+ // section per key rather than a single blob-cell row. Flat arrays-of-objects
16
+ // (maps list, alerts list, builds) fall through to the plain table renderer
17
+ // unchanged.
18
+ if (isSectionedPayload(data)) {
19
+ return formatSections(data);
20
+ }
13
21
  const rows = toRows(data);
14
22
  if (rows.length === 0)
15
23
  return "(no data)";
16
24
  const firstRow = rows[0];
17
25
  if (!firstRow)
18
26
  return "(no data)";
19
- const keys = Object.keys(firstRow);
27
+ return renderRows(rows, Object.keys(firstRow));
28
+ }
29
+ /**
30
+ * True for an envelope-style object whose every field is a nested array/object
31
+ * (e.g. the dashboard's { kpis, dailyActiveUsers, topEvents } or status's
32
+ * { project, kpis }). Records that mix scalar fields with a nested one -- such as
33
+ * an alert row's scalars plus channelIds: [] -- stay on the plain one-row table
34
+ * path, so `alerts create/update --format table` output does not regress.
35
+ */
36
+ function isSectionedPayload(data) {
37
+ if (Array.isArray(data))
38
+ return false;
39
+ if (typeof data !== "object" || data === null)
40
+ return false;
41
+ const values = Object.values(data);
42
+ if (values.length === 0)
43
+ return false;
44
+ return values.every((v) => v !== null && typeof v === "object");
45
+ }
46
+ /** Render each top-level key of a sectioned payload as a titled sub-table. */
47
+ function formatSections(obj) {
48
+ const sections = Object.entries(obj).map(([key, value]) => {
49
+ let table;
50
+ if (Array.isArray(value)) {
51
+ table = formatArraySection(value);
52
+ }
53
+ else if (value !== null && typeof value === "object") {
54
+ table = formatObjectSection(value);
55
+ }
56
+ else {
57
+ table = cellString(value);
58
+ }
59
+ return `## ${key}\n${table}`;
60
+ });
61
+ return sections.join("\n\n");
62
+ }
63
+ /** An array-of-objects section: normal table, nested objects flattened to dot-paths. */
64
+ function formatArraySection(arr) {
65
+ if (arr.length === 0)
66
+ return "(no data)";
67
+ const rows = arr.map((item) => typeof item === "object" && item !== null && !Array.isArray(item)
68
+ ? flattenOneLevel(item)
69
+ : { value: item });
70
+ return renderRows(rows, unionKeys(rows));
71
+ }
72
+ /** An object section: two-column key/value rows. */
73
+ function formatObjectSection(obj) {
74
+ const rows = Object.entries(obj).map(([key, value]) => ({ key, value }));
75
+ if (rows.length === 0)
76
+ return "(no data)";
77
+ return renderRows(rows, ["key", "value"]);
78
+ }
79
+ /** Flatten one level of nested objects into dot-path keys; deeper nesting stays JSON. */
80
+ function flattenOneLevel(row) {
81
+ const out = {};
82
+ for (const [key, value] of Object.entries(row)) {
83
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
84
+ for (const [subKey, subValue] of Object.entries(value)) {
85
+ out[`${key}.${subKey}`] = subValue;
86
+ }
87
+ }
88
+ else {
89
+ out[key] = value;
90
+ }
91
+ }
92
+ return out;
93
+ }
94
+ /** Union of row keys in first-seen order. */
95
+ function unionKeys(rows) {
96
+ const seen = new Set();
97
+ const keys = [];
98
+ for (const row of rows) {
99
+ for (const key of Object.keys(row)) {
100
+ if (!seen.has(key)) {
101
+ seen.add(key);
102
+ keys.push(key);
103
+ }
104
+ }
105
+ }
106
+ return keys;
107
+ }
108
+ /** Render aligned columns for the given rows/keys using cellString for each cell. */
109
+ function renderRows(rows, keys) {
110
+ if (rows.length === 0 || keys.length === 0)
111
+ return "(no data)";
20
112
  const widths = keys.map((k) => Math.max(k.length, ...rows.map((r) => cellString(r[k]).length)));
21
113
  const header = keys.map((k, i) => k.padEnd(widths[i] ?? k.length)).join(" ");
22
114
  const separator = widths.map((w) => "-".repeat(w)).join(" ");
@@ -1 +1 @@
1
- {"version":3,"file":"formatters.js","sourceRoot":"","sources":["../../src/lib/formatters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"formatters.js","sourceRoot":"","sources":["../../src/lib/formatters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The authorization server bounced back an OAuth error (e.g. the user hit
3
+ * Deny -> access_denied). Distinguished from local failures so the CLI can
4
+ * present it as a normal outcome rather than a bug.
5
+ */
6
+ export declare class OAuthCallbackError extends Error {
7
+ readonly code: string;
8
+ constructor(code: string, description: string | null);
9
+ }
10
+ export type LoopbackServer = {
11
+ /** The ephemeral port actually bound on 127.0.0.1. */
12
+ port: number;
13
+ /** The exact redirect URI to register in the authorize request. */
14
+ redirectUri: string;
15
+ /** Resolve with the authorization code, or reject on error/timeout. */
16
+ waitForCallback: (timeoutMs: number) => Promise<{
17
+ code: string;
18
+ }>;
19
+ close: () => Promise<void>;
20
+ };
21
+ /**
22
+ * Start the loopback callback receiver. `expectedState` is the CSRF state
23
+ * minted for this login attempt; only a /callback request carrying exactly
24
+ * that state can complete the flow.
25
+ */
26
+ export declare function startLoopbackServer(expectedState: string): Promise<LoopbackServer>;
27
+ //# sourceMappingURL=loopback-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopback-server.d.ts","sourceRoot":"","sources":["../../../src/lib/oauth/loopback-server.ts"],"names":[],"mappings":"AAiCA;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;aAE3B,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EAC5B,WAAW,EAAE,MAAM,GAAG,IAAI;CAK3B;AAWD,MAAM,MAAM,cAAc,GAAG;IAC5B,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA+HlF"}
@@ -0,0 +1,164 @@
1
+ import { createServer } from "node:http";
2
+ // RFC 8252 section 7.3 loopback redirect receiver for `framedash login`.
3
+ //
4
+ // SECURITY invariants:
5
+ // - Binds STRICTLY to 127.0.0.1 (never 0.0.0.0 / ::) on an ephemeral port,
6
+ // so nothing off-host can reach the callback.
7
+ // - The `state` parameter is validated FIRST, for success AND error
8
+ // callbacks alike, BEFORE the caller ever sees a code. A mismatched or
9
+ // missing state does NOT settle the login: the request gets a generic
10
+ // 400 and the flow keeps waiting for the legitimate callback -- a forged
11
+ // local request can neither abort nor complete a pending sign-in, and
12
+ // any accompanying code is discarded (no token exchange happens).
13
+ // - Responses to the browser are static HTML with no token/code material,
14
+ // and nothing from the callback URL is reflected into the page.
15
+ const HTML_HEADERS = {
16
+ "Content-Type": "text/html; charset=utf-8",
17
+ "Cache-Control": "no-store",
18
+ "Referrer-Policy": "no-referrer",
19
+ };
20
+ function page(title, body) {
21
+ return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title></head><body style="font-family: system-ui, sans-serif; margin: 4rem auto; max-width: 32rem;"><h1 style="font-size: 1.2rem;">${title}</h1><p>${body}</p></body></html>`;
22
+ }
23
+ const SUCCESS_PAGE = page("Login complete", "You can close this tab and return to the terminal.");
24
+ const FAILURE_PAGE = page("Login failed", "The sign-in did not complete. Close this tab and check the terminal for details.");
25
+ const DONE_PAGE = page("Login already completed", "You can close this tab.");
26
+ /**
27
+ * The authorization server bounced back an OAuth error (e.g. the user hit
28
+ * Deny -> access_denied). Distinguished from local failures so the CLI can
29
+ * present it as a normal outcome rather than a bug.
30
+ */
31
+ export class OAuthCallbackError extends Error {
32
+ code;
33
+ constructor(code, description) {
34
+ super(`Authorization was not granted (${code})${description ? `: ${description}` : ""}`);
35
+ this.code = code;
36
+ this.name = "OAuthCallbackError";
37
+ }
38
+ }
39
+ /**
40
+ * Keep attacker-influenced callback values printable before they reach the
41
+ * terminal: strip everything outside a conservative ASCII subset (kills
42
+ * control characters / ANSI escapes) and cap the length.
43
+ */
44
+ function sanitizeForTerminal(value) {
45
+ return value.replace(/[^a-zA-Z0-9 _.,:;/'()-]/g, "").slice(0, 200);
46
+ }
47
+ /**
48
+ * Start the loopback callback receiver. `expectedState` is the CSRF state
49
+ * minted for this login attempt; only a /callback request carrying exactly
50
+ * that state can complete the flow.
51
+ */
52
+ export function startLoopbackServer(expectedState) {
53
+ let settled = false;
54
+ let resolveCallback = () => { };
55
+ let rejectCallback = () => { };
56
+ const callbackPromise = new Promise((resolve, reject) => {
57
+ resolveCallback = resolve;
58
+ rejectCallback = reject;
59
+ });
60
+ // A rejection that nobody has awaited yet (e.g. state mismatch arriving
61
+ // before waitForCallback is called) must not crash the process.
62
+ callbackPromise.catch(() => { });
63
+ const settle = (outcome) => {
64
+ if (settled)
65
+ return;
66
+ settled = true;
67
+ if (outcome instanceof Error) {
68
+ rejectCallback(outcome);
69
+ }
70
+ else {
71
+ resolveCallback(outcome);
72
+ }
73
+ };
74
+ const server = createServer((req, res) => {
75
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
76
+ if (req.method !== "GET" || url.pathname !== "/callback") {
77
+ res.writeHead(404, HTML_HEADERS);
78
+ res.end(page("Not found", "Nothing to see here."));
79
+ return;
80
+ }
81
+ if (settled) {
82
+ res.writeHead(200, HTML_HEADERS);
83
+ res.end(DONE_PAGE);
84
+ return;
85
+ }
86
+ // STRICT state validation FIRST, for success AND error callbacks alike
87
+ // (RFC 6749 section 4.1.2.1 requires `state` in error redirects too).
88
+ // A mismatched/missing state is treated as an unexpected request and
89
+ // deliberately does NOT settle: neither a forged error= callback nor a
90
+ // forged code may abort or complete someone's pending login, and the
91
+ // accompanying code (if any) is never handed to the caller, so no
92
+ // token exchange can happen for a forged/replayed callback.
93
+ const state = url.searchParams.get("state");
94
+ if (state !== expectedState) {
95
+ res.writeHead(400, HTML_HEADERS);
96
+ res.end(page("Unexpected request", "This request did not match a pending sign-in."));
97
+ return;
98
+ }
99
+ const errorParam = url.searchParams.get("error");
100
+ if (errorParam !== null) {
101
+ res.writeHead(200, HTML_HEADERS);
102
+ res.end(FAILURE_PAGE);
103
+ const description = url.searchParams.get("error_description");
104
+ settle(new OAuthCallbackError(sanitizeForTerminal(errorParam) || "unknown_error", description ? sanitizeForTerminal(description) : null));
105
+ return;
106
+ }
107
+ const code = url.searchParams.get("code");
108
+ if (!code) {
109
+ res.writeHead(400, HTML_HEADERS);
110
+ res.end(FAILURE_PAGE);
111
+ settle(new Error("Login callback carried no authorization code."));
112
+ return;
113
+ }
114
+ res.writeHead(200, HTML_HEADERS);
115
+ res.end(SUCCESS_PAGE);
116
+ settle({ code });
117
+ });
118
+ // Refuse to linger: once the single callback settles the login, keep-alive
119
+ // sockets must not hold the process open.
120
+ server.keepAliveTimeout = 1000;
121
+ return new Promise((resolve, reject) => {
122
+ server.once("error", reject);
123
+ // STRICT loopback bind: 127.0.0.1 only (never 0.0.0.0/::), ephemeral port.
124
+ server.listen({ host: "127.0.0.1", port: 0, exclusive: true }, () => {
125
+ const address = server.address();
126
+ if (address === null || typeof address === "string") {
127
+ reject(new Error("Loopback server failed to report a bound port"));
128
+ server.close();
129
+ return;
130
+ }
131
+ const port = address.port;
132
+ resolve({
133
+ port,
134
+ redirectUri: `http://127.0.0.1:${port}/callback`,
135
+ waitForCallback: (timeoutMs) => {
136
+ return new Promise((resolveWait, rejectWait) => {
137
+ const timer = setTimeout(() => {
138
+ settle(new Error(`Timed out after ${Math.round(timeoutMs / 1000)}s waiting for the browser login callback.`));
139
+ }, timeoutMs);
140
+ timer.unref();
141
+ callbackPromise
142
+ .then((value) => {
143
+ clearTimeout(timer);
144
+ resolveWait(value);
145
+ })
146
+ .catch((err) => {
147
+ clearTimeout(timer);
148
+ rejectWait(err);
149
+ });
150
+ });
151
+ },
152
+ close: () => new Promise((resolveClose) => {
153
+ server.close(() => resolveClose());
154
+ // Node >= 18.2 only; on older 18.x the short keepAliveTimeout
155
+ // set above still lets close() finish promptly.
156
+ if (typeof server.closeAllConnections === "function") {
157
+ server.closeAllConnections();
158
+ }
159
+ }),
160
+ });
161
+ });
162
+ });
163
+ }
164
+ //# sourceMappingURL=loopback-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopback-server.js","sourceRoot":"","sources":["../../../src/lib/oauth/loopback-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;AAEpF,yEAAyE;AACzE,EAAE;AACF,uBAAuB;AACvB,4EAA4E;AAC5E,iDAAiD;AACjD,qEAAqE;AACrE,0EAA0E;AAC1E,yEAAyE;AACzE,4EAA4E;AAC5E,yEAAyE;AACzE,qEAAqE;AACrE,2EAA2E;AAC3E,mEAAmE;AAEnE,MAAM,YAAY,GAAG;IACpB,cAAc,EAAE,0BAA0B;IAC1C,eAAe,EAAE,UAAU;IAC3B,iBAAiB,EAAE,aAAa;CACvB,CAAC;AAEX,SAAS,IAAI,CAAC,KAAa,EAAE,IAAY;IACxC,OAAO,2DAA2D,KAAK,wIAAwI,KAAK,WAAW,IAAI,oBAAoB,CAAC;AACzP,CAAC;AAED,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,EAAE,oDAAoD,CAAC,CAAC;AAClG,MAAM,YAAY,GAAG,IAAI,CACxB,cAAc,EACd,kFAAkF,CAClF,CAAC;AACF,MAAM,SAAS,GAAG,IAAI,CAAC,yBAAyB,EAAE,yBAAyB,CAAC,CAAC;AAE7E;;;;GAIG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAE3B;IADjB,YACiB,IAAY,EAC5B,WAA0B;QAE1B,KAAK,CAAC,kCAAkC,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAHzE,SAAI,GAAJ,IAAI,CAAQ;QAI5B,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IAClC,CAAC;CACD;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,OAAO,CAAC,0BAA0B,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACpE,CAAC;AAYD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,aAAqB;IACxD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,eAAe,GAAsC,GAAG,EAAE,GAAE,CAAC,CAAC;IAClE,IAAI,cAAc,GAA4B,GAAG,EAAE,GAAE,CAAC,CAAC;IACvD,MAAM,eAAe,GAAG,IAAI,OAAO,CAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACzE,eAAe,GAAG,OAAO,CAAC;QAC1B,cAAc,GAAG,MAAM,CAAC;IACzB,CAAC,CAAC,CAAC;IACH,wEAAwE;IACxE,gEAAgE;IAChE,eAAe,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAEhC,MAAM,MAAM,GAAG,CAAC,OAAiC,EAAQ,EAAE;QAC1D,IAAI,OAAO;YAAE,OAAO;QACpB,OAAO,GAAG,IAAI,CAAC;QACf,IAAI,OAAO,YAAY,KAAK,EAAE,CAAC;YAC9B,cAAc,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;aAAM,CAAC;YACP,eAAe,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;IACF,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,GAAoB,EAAE,GAAmB,EAAE,EAAE;QACzE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxD,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;YAC1D,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;YACjC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,sBAAsB,CAAC,CAAC,CAAC;YACnD,OAAO;QACR,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACb,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;YACjC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACnB,OAAO;QACR,CAAC;QAED,uEAAuE;QACvE,sEAAsE;QACtE,qEAAqE;QACrE,uEAAuE;QACvE,qEAAqE;QACrE,kEAAkE;QAClE,4DAA4D;QAC5D,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;YAC7B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;YACjC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,oBAAoB,EAAE,+CAA+C,CAAC,CAAC,CAAC;YACrF,OAAO;QACR,CAAC;QAED,MAAM,UAAU,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACjD,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACzB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;YACjC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACtB,MAAM,WAAW,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;YAC9D,MAAM,CACL,IAAI,kBAAkB,CACrB,mBAAmB,CAAC,UAAU,CAAC,IAAI,eAAe,EAClD,WAAW,CAAC,CAAC,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CACrD,CACD,CAAC;YACF,OAAO;QACR,CAAC;QAED,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,EAAE,CAAC;YACX,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;YACjC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACtB,MAAM,CAAC,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC,CAAC;YACnE,OAAO;QACR,CAAC;QAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QACjC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACtB,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,2EAA2E;IAC3E,0CAA0C;IAC1C,MAAM,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAE/B,OAAO,IAAI,OAAO,CAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACtD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7B,2EAA2E;QAC3E,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE;YACnE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACrD,MAAM,CAAC,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC,CAAC;gBACnE,MAAM,CAAC,KAAK,EAAE,CAAC;gBACf,OAAO;YACR,CAAC;YACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;YAC1B,OAAO,CAAC;gBACP,IAAI;gBACJ,WAAW,EAAE,oBAAoB,IAAI,WAAW;gBAChD,eAAe,EAAE,CAAC,SAAiB,EAAE,EAAE;oBACtC,OAAO,IAAI,OAAO,CAAmB,CAAC,WAAW,EAAE,UAAU,EAAE,EAAE;wBAChE,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;4BAC7B,MAAM,CACL,IAAI,KAAK,CACR,mBAAmB,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,2CAA2C,CAC1F,CACD,CAAC;wBACH,CAAC,EAAE,SAAS,CAAC,CAAC;wBACd,KAAK,CAAC,KAAK,EAAE,CAAC;wBACd,eAAe;6BACb,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;4BACf,YAAY,CAAC,KAAK,CAAC,CAAC;4BACpB,WAAW,CAAC,KAAK,CAAC,CAAC;wBACpB,CAAC,CAAC;6BACD,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;4BACrB,YAAY,CAAC,KAAK,CAAC,CAAC;4BACpB,UAAU,CAAC,GAAG,CAAC,CAAC;wBACjB,CAAC,CAAC,CAAC;oBACL,CAAC,CAAC,CAAC;gBACJ,CAAC;gBACD,KAAK,EAAE,GAAG,EAAE,CACX,IAAI,OAAO,CAAO,CAAC,YAAY,EAAE,EAAE;oBAClC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,YAAY,EAAE,CAAC,CAAC;oBACnC,8DAA8D;oBAC9D,gDAAgD;oBAChD,IAAI,OAAO,MAAM,CAAC,mBAAmB,KAAK,UAAU,EAAE,CAAC;wBACtD,MAAM,CAAC,mBAAmB,EAAE,CAAC;oBAC9B,CAAC;gBACF,CAAC,CAAC;aACH,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { type StoredTokenEntry } from "./token-store.js";
2
+ /**
3
+ * The stored grant is dead (refresh returned invalid_grant: revoked, expired,
4
+ * or rotated out from under us). The stored entry has already been cleared;
5
+ * the only recovery is an interactive re-login, so callers must surface the
6
+ * message and stop -- retrying cannot succeed.
7
+ */
8
+ export declare class OAuthLoginRequiredError extends Error {
9
+ constructor(origin: string);
10
+ }
11
+ export declare class OAuthTokenManager {
12
+ private readonly baseUrl;
13
+ private readonly origin;
14
+ private entry;
15
+ private refreshPromise;
16
+ constructor(baseUrl: string, origin: string, entry: StoredTokenEntry);
17
+ /** Space-delimited granted scopes (safe to display). */
18
+ get scope(): string;
19
+ /** Access-token expiry, epoch ms (safe to display). */
20
+ get expiresAt(): number;
21
+ /**
22
+ * Current access token, refreshing first when it expires within 60s so an
23
+ * about-to-expire token is not sent only to bounce with a 401.
24
+ */
25
+ getAccessToken(): Promise<string>;
26
+ /** Force a refresh (after a 401 despite a locally-unexpired token). */
27
+ forceRefresh(): Promise<string>;
28
+ /** Coalesce concurrent callers onto a single refresh request. */
29
+ private refresh;
30
+ /**
31
+ * Refresh with the CURRENT stored token, then persist the rotated pair
32
+ * (atomic write, last-writer-wins).
33
+ */
34
+ private doRefresh;
35
+ }
36
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../src/lib/oauth/manager.ts"],"names":[],"mappings":"AACA,OAAO,EAGN,KAAK,gBAAgB,EAErB,MAAM,kBAAkB,CAAC;AAgB1B;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,MAAM,EAAE,MAAM;CAO1B;AAED,qBAAa,iBAAiB;IAK5B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IALxB,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,cAAc,CAAgC;gBAGpC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EAC/B,KAAK,EAAE,gBAAgB;IAKxB,wDAAwD;IACxD,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,uDAAuD;IACvD,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAOvC,uEAAuE;IACjE,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAIrC,iEAAiE;IACjE,OAAO,CAAC,OAAO;IASf;;;OAGG;YACW,SAAS;CAsDvB"}