@levr-one/cli 0.12.10 → 0.12.11
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/dist/{addHandler-CD1pda0z.js → addHandler-CJOweVZf.js} +2 -2
- package/dist/{appendHandler-BYOoCNXJ.js → appendHandler-mYa2KKNC.js} +3 -3
- package/dist/{attachHandler-Ojk7mc2E.js → attachHandler-DFlDUObZ.js} +3 -3
- package/dist/{bindingsHandler-Dxx2LIci.js → bindingsHandler-BR_vk1eC.js} +2 -2
- package/dist/cli.js +12 -12
- package/dist/{connect-A5VSJUv5.js → connect-B_Bva8BM.js} +1 -1
- package/dist/{importHandler-86cjc2Zs.js → importHandler-t4UNZ-tS.js} +2 -2
- package/dist/{listHandler-BoCzGUDK.js → listHandler-D-W39_YC.js} +2 -2
- package/dist/{pushHandler-BkCmzQz7.js → pushHandler-D02XXrQZ.js} +2 -2
- package/dist/{resolve-token-D1zteEfm.js → resolve-token-BeX-Xegl.js} +1 -1
- package/dist/{runHandler-BLkPzImG.js → runHandler-DMJO--tc.js} +2 -2
- package/dist/{selectHandler-Dp74pefc.js → selectHandler-9pNaEvpf.js} +4 -4
- package/dist/{statusHandler-ByTUMdKK.js → statusHandler-BgQTktJm.js} +1 -1
- package/dist/token-refresh-cvAydkok.js +116 -0
- package/package.json +1 -1
- package/dist/token-refresh-DEmYbJ8V.js +0 -72
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { configureClient } from "./sdk-client-CSy2LOEh.js";
|
|
2
|
-
import "./token-refresh-
|
|
2
|
+
import "./token-refresh-cvAydkok.js";
|
|
3
3
|
import { getApiUrl } from "./env-Cv-anxJA.js";
|
|
4
|
-
import { resolveToken } from "./resolve-token-
|
|
4
|
+
import { resolveToken } from "./resolve-token-BeX-Xegl.js";
|
|
5
5
|
import "./workspace-store-BJyB_WEG.js";
|
|
6
6
|
import { fetchSites } from "./resolve-workspace-CJN1plpd.js";
|
|
7
7
|
import { describeWorkspaces, findWorkspaceByKeyOrName, isScopedMcpUrl, resolveMcpUrl, scopedMcpUrl, servedBySessionApi } from "./find-workspace-IWolSyrf.js";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { appendActualResult } from "./sdk-client-CSy2LOEh.js";
|
|
2
|
-
import "./token-refresh-
|
|
2
|
+
import "./token-refresh-cvAydkok.js";
|
|
3
3
|
import "./env-Cv-anxJA.js";
|
|
4
|
-
import "./resolve-token-
|
|
4
|
+
import "./resolve-token-BeX-Xegl.js";
|
|
5
5
|
import "./workspace-store-BJyB_WEG.js";
|
|
6
6
|
import "./resolve-workspace-CJN1plpd.js";
|
|
7
|
-
import { connect } from "./connect-
|
|
7
|
+
import { connect } from "./connect-B_Bva8BM.js";
|
|
8
8
|
import "./sleep-DNLj0FMk.js";
|
|
9
9
|
import { embedMarkdown, parseTarget, uploadArtifactFile } from "./artifact-upload-t6kL20b5.js";
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { appendActualResult, createComment } from "./sdk-client-CSy2LOEh.js";
|
|
2
|
-
import "./token-refresh-
|
|
2
|
+
import "./token-refresh-cvAydkok.js";
|
|
3
3
|
import "./env-Cv-anxJA.js";
|
|
4
|
-
import "./resolve-token-
|
|
4
|
+
import "./resolve-token-BeX-Xegl.js";
|
|
5
5
|
import "./workspace-store-BJyB_WEG.js";
|
|
6
6
|
import "./resolve-workspace-CJN1plpd.js";
|
|
7
|
-
import { connect } from "./connect-
|
|
7
|
+
import { connect } from "./connect-B_Bva8BM.js";
|
|
8
8
|
import "./sleep-DNLj0FMk.js";
|
|
9
9
|
import { TargetParseError, embedMarkdown, parseTarget, uploadArtifactFile } from "./artifact-upload-t6kL20b5.js";
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "./gate-op-BINrIT2l.js";
|
|
2
2
|
import { client, configureClient } from "./sdk-client-CSy2LOEh.js";
|
|
3
|
-
import "./token-refresh-
|
|
3
|
+
import "./token-refresh-cvAydkok.js";
|
|
4
4
|
import "./env-Cv-anxJA.js";
|
|
5
|
-
import { resolveToken } from "./resolve-token-
|
|
5
|
+
import { resolveToken } from "./resolve-token-BeX-Xegl.js";
|
|
6
6
|
import "./workspace-store-BJyB_WEG.js";
|
|
7
7
|
import { resolveWorkspaceFor } from "./resolve-workspace-CJN1plpd.js";
|
|
8
8
|
import { z } from "zod";
|
package/dist/cli.js
CHANGED
|
@@ -226,7 +226,7 @@ Examples:
|
|
|
226
226
|
aliases: { y: "yes" }
|
|
227
227
|
},
|
|
228
228
|
loader: async () => {
|
|
229
|
-
const { mcpAddHandler } = await import("./addHandler-
|
|
229
|
+
const { mcpAddHandler } = await import("./addHandler-CJOweVZf.js");
|
|
230
230
|
return mcpAddHandler;
|
|
231
231
|
}
|
|
232
232
|
});
|
|
@@ -314,7 +314,7 @@ Examples:
|
|
|
314
314
|
},
|
|
315
315
|
parameters: {},
|
|
316
316
|
loader: async () => {
|
|
317
|
-
const { statusHandler } = await import("./statusHandler-
|
|
317
|
+
const { statusHandler } = await import("./statusHandler-BgQTktJm.js");
|
|
318
318
|
return statusHandler;
|
|
319
319
|
}
|
|
320
320
|
});
|
|
@@ -443,7 +443,7 @@ levr attach --manifest or later uploads.`
|
|
|
443
443
|
}
|
|
444
444
|
},
|
|
445
445
|
loader: async () => {
|
|
446
|
-
const { pushHandler } = await import("./pushHandler-
|
|
446
|
+
const { pushHandler } = await import("./pushHandler-D02XXrQZ.js");
|
|
447
447
|
return pushHandler;
|
|
448
448
|
}
|
|
449
449
|
});
|
|
@@ -563,7 +563,7 @@ Examples:
|
|
|
563
563
|
}
|
|
564
564
|
},
|
|
565
565
|
loader: async () => {
|
|
566
|
-
const { importHandler } = await import("./importHandler-
|
|
566
|
+
const { importHandler } = await import("./importHandler-t4UNZ-tS.js");
|
|
567
567
|
return importHandler;
|
|
568
568
|
}
|
|
569
569
|
});
|
|
@@ -584,7 +584,7 @@ Examples:
|
|
|
584
584
|
},
|
|
585
585
|
parameters: {},
|
|
586
586
|
loader: async () => {
|
|
587
|
-
const { listHandler } = await import("./listHandler-
|
|
587
|
+
const { listHandler } = await import("./listHandler-D-W39_YC.js");
|
|
588
588
|
return listHandler;
|
|
589
589
|
}
|
|
590
590
|
});
|
|
@@ -622,7 +622,7 @@ Examples:
|
|
|
622
622
|
flags: {}
|
|
623
623
|
},
|
|
624
624
|
loader: async () => {
|
|
625
|
-
const { selectHandler } = await import("./selectHandler-
|
|
625
|
+
const { selectHandler } = await import("./selectHandler-9pNaEvpf.js");
|
|
626
626
|
return selectHandler;
|
|
627
627
|
}
|
|
628
628
|
});
|
|
@@ -688,7 +688,7 @@ Examples:
|
|
|
688
688
|
}
|
|
689
689
|
},
|
|
690
690
|
loader: async () => {
|
|
691
|
-
const { gatesBindingsExportHandler } = await import("./bindingsHandler-
|
|
691
|
+
const { gatesBindingsExportHandler } = await import("./bindingsHandler-BR_vk1eC.js");
|
|
692
692
|
return gatesBindingsExportHandler;
|
|
693
693
|
}
|
|
694
694
|
});
|
|
@@ -761,7 +761,7 @@ Examples:
|
|
|
761
761
|
aliases: { w: "workspace-id" }
|
|
762
762
|
},
|
|
763
763
|
loader: async () => {
|
|
764
|
-
const { gatesBindingsImportHandler } = await import("./bindingsHandler-
|
|
764
|
+
const { gatesBindingsImportHandler } = await import("./bindingsHandler-BR_vk1eC.js");
|
|
765
765
|
return gatesBindingsImportHandler;
|
|
766
766
|
}
|
|
767
767
|
});
|
|
@@ -1052,7 +1052,7 @@ Examples:
|
|
|
1052
1052
|
}
|
|
1053
1053
|
},
|
|
1054
1054
|
loader: async () => {
|
|
1055
|
-
const { gatesRunHandler } = await import("./runHandler-
|
|
1055
|
+
const { gatesRunHandler } = await import("./runHandler-DMJO--tc.js");
|
|
1056
1056
|
return gatesRunHandler;
|
|
1057
1057
|
}
|
|
1058
1058
|
});
|
|
@@ -1165,7 +1165,7 @@ Examples:
|
|
|
1165
1165
|
}
|
|
1166
1166
|
},
|
|
1167
1167
|
loader: async () => {
|
|
1168
|
-
const { attachHandler } = await import("./attachHandler-
|
|
1168
|
+
const { attachHandler } = await import("./attachHandler-DFlDUObZ.js");
|
|
1169
1169
|
return attachHandler;
|
|
1170
1170
|
}
|
|
1171
1171
|
});
|
|
@@ -1241,14 +1241,14 @@ Examples:
|
|
|
1241
1241
|
}
|
|
1242
1242
|
},
|
|
1243
1243
|
loader: async () => {
|
|
1244
|
-
const { resultAppendHandler } = await import("./appendHandler-
|
|
1244
|
+
const { resultAppendHandler } = await import("./appendHandler-mYa2KKNC.js");
|
|
1245
1245
|
return resultAppendHandler;
|
|
1246
1246
|
}
|
|
1247
1247
|
});
|
|
1248
1248
|
|
|
1249
1249
|
//#endregion
|
|
1250
1250
|
//#region package.json
|
|
1251
|
-
var version = "0.12.
|
|
1251
|
+
var version = "0.12.11";
|
|
1252
1252
|
|
|
1253
1253
|
//#endregion
|
|
1254
1254
|
//#region src/app.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { client, configureClient } from "./sdk-client-CSy2LOEh.js";
|
|
2
|
-
import { resolveToken } from "./resolve-token-
|
|
2
|
+
import { resolveToken } from "./resolve-token-BeX-Xegl.js";
|
|
3
3
|
import { resolveWorkspaceFor } from "./resolve-workspace-CJN1plpd.js";
|
|
4
4
|
|
|
5
5
|
//#region src/utils/connect.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { authGetProfileV1, client, configureClient } from "./sdk-client-CSy2LOEh.js";
|
|
2
|
-
import "./token-refresh-
|
|
2
|
+
import "./token-refresh-cvAydkok.js";
|
|
3
3
|
import { getApiUrl, getTeamId } from "./env-Cv-anxJA.js";
|
|
4
|
-
import { resolveToken } from "./resolve-token-
|
|
4
|
+
import { resolveToken } from "./resolve-token-BeX-Xegl.js";
|
|
5
5
|
import "./workspace-store-BJyB_WEG.js";
|
|
6
6
|
import { resolveWorkspaceFor } from "./resolve-workspace-CJN1plpd.js";
|
|
7
7
|
import chalk from "chalk";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { authGetSitesV1, configureClient } from "./sdk-client-CSy2LOEh.js";
|
|
2
|
-
import "./token-refresh-
|
|
2
|
+
import "./token-refresh-cvAydkok.js";
|
|
3
3
|
import "./env-Cv-anxJA.js";
|
|
4
|
-
import { resolveToken } from "./resolve-token-
|
|
4
|
+
import { resolveToken } from "./resolve-token-BeX-Xegl.js";
|
|
5
5
|
import { loadWorkspace } from "./workspace-store-BJyB_WEG.js";
|
|
6
6
|
|
|
7
7
|
//#region src/commands/workspace/listHandler.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { client, configureClient, uploadImport } from "./sdk-client-CSy2LOEh.js";
|
|
2
|
-
import "./token-refresh-
|
|
2
|
+
import "./token-refresh-cvAydkok.js";
|
|
3
3
|
import { getApiUrl, getAutomationSourceIdOverride, getSourceOverride, getTeamId } from "./env-Cv-anxJA.js";
|
|
4
|
-
import { resolveToken } from "./resolve-token-
|
|
4
|
+
import { resolveToken } from "./resolve-token-BeX-Xegl.js";
|
|
5
5
|
import "./workspace-store-BJyB_WEG.js";
|
|
6
6
|
import { resolveWorkspaceFor } from "./resolve-workspace-CJN1plpd.js";
|
|
7
7
|
import "./sleep-DNLj0FMk.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { classifyToken, isTokenExpired, refreshToken } from "./token-refresh-
|
|
1
|
+
import { classifyToken, isTokenExpired, refreshToken } from "./token-refresh-cvAydkok.js";
|
|
2
2
|
import { getApiUrl, getPatToken, readCredentials } from "./env-Cv-anxJA.js";
|
|
3
3
|
|
|
4
4
|
//#region src/auth/resolve-token.ts
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "./gate-op-BINrIT2l.js";
|
|
2
2
|
import { executeInspect } from "./inspect-CinRBSv6.js";
|
|
3
3
|
import { client, configureClient } from "./sdk-client-CSy2LOEh.js";
|
|
4
|
-
import "./token-refresh-
|
|
4
|
+
import "./token-refresh-cvAydkok.js";
|
|
5
5
|
import "./env-Cv-anxJA.js";
|
|
6
|
-
import { resolveToken } from "./resolve-token-
|
|
6
|
+
import { resolveToken } from "./resolve-token-BeX-Xegl.js";
|
|
7
7
|
import "./workspace-store-BJyB_WEG.js";
|
|
8
8
|
import { resolveWorkspaceFor } from "./resolve-workspace-CJN1plpd.js";
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { authGetSitesV1, configureClient } from "./sdk-client-CSy2LOEh.js";
|
|
2
|
-
import "./token-refresh-
|
|
2
|
+
import "./token-refresh-cvAydkok.js";
|
|
3
3
|
import "./env-Cv-anxJA.js";
|
|
4
|
-
import { resolveToken } from "./resolve-token-
|
|
4
|
+
import { resolveToken } from "./resolve-token-BeX-Xegl.js";
|
|
5
5
|
import { saveWorkspace } from "./workspace-store-BJyB_WEG.js";
|
|
6
6
|
|
|
7
7
|
//#region src/commands/workspace/selectHandler.ts
|
|
@@ -9,8 +9,8 @@ async function selectHandler(_flags, workspaceId) {
|
|
|
9
9
|
let auth;
|
|
10
10
|
try {
|
|
11
11
|
auth = await resolveToken();
|
|
12
|
-
} catch {
|
|
13
|
-
this.logger.error("Not authenticated. Run 'levr auth login' first.");
|
|
12
|
+
} catch (err) {
|
|
13
|
+
this.logger.error(err instanceof Error ? err.message : "Not authenticated. Run 'levr auth login' first.");
|
|
14
14
|
this.process.exitCode = 1;
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { authGetProfileV1, configureClient } from "./sdk-client-CSy2LOEh.js";
|
|
2
|
-
import { classifyToken, isTokenExpired } from "./token-refresh-
|
|
2
|
+
import { classifyToken, isTokenExpired } from "./token-refresh-cvAydkok.js";
|
|
3
3
|
import { getApiUrl, getPatToken, readCredentials } from "./env-Cv-anxJA.js";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { CLI_CLIENT_ID, deleteCredentials, getApiUrl, readCredentials, writeCredentials } from "./env-Cv-anxJA.js";
|
|
2
|
+
|
|
3
|
+
//#region src/auth/token-shape.ts
|
|
4
|
+
/**
|
|
5
|
+
* Classify a bearer token by its SHAPE — [issue:ENG-6068].
|
|
6
|
+
*
|
|
7
|
+
* The server decides how to verify a token by looking at the token, not at
|
|
8
|
+
* where it came from (`isPersonalAccessToken` in the backend's auth
|
|
9
|
+
* middleware): a Levr personal access token starts with `tq_`, and anything
|
|
10
|
+
* else is handed to the JWT verifier. `LEVR_TOKEN` carries either kind — a
|
|
11
|
+
* PAT minted in Settings, or an OAuth access token a device-code login
|
|
12
|
+
* printed for CI — so the CLI must apply the same rule. Until ENG-6068 every
|
|
13
|
+
* `LEVR_TOKEN` was called a PAT, and the JWT-only steps (resolving
|
|
14
|
+
* `--workspace-id` / `LEVR_WORKSPACE_ID`, `levr workspace list|select`) were
|
|
15
|
+
* silently skipped for an OAuth token, which has no workspace of its own.
|
|
16
|
+
*
|
|
17
|
+
* Leaf module on purpose: no imports, so `resolve-token`, the handlers and
|
|
18
|
+
* their tests can all read it without pulling in credentials or the SDK.
|
|
19
|
+
*/
|
|
20
|
+
const PAT_PREFIX = "tq_";
|
|
21
|
+
/** `tq_…` is a personal access token; everything else is treated as a JWT. */
|
|
22
|
+
function classifyToken(token) {
|
|
23
|
+
return token.startsWith(PAT_PREFIX) ? "pat" : "jwt";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/auth/token-refresh.ts
|
|
28
|
+
const REFRESH_BUFFER_MS = 300 * 1e3;
|
|
29
|
+
/**
|
|
30
|
+
* Check if stored JWT credentials are expired (or within 5 min of expiry).
|
|
31
|
+
*/
|
|
32
|
+
function isTokenExpired(creds) {
|
|
33
|
+
const expiresAt = new Date(creds.expires_at).getTime();
|
|
34
|
+
return Date.now() >= expiresAt - REFRESH_BUFFER_MS;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Refresh JWT credentials using the stored refresh token.
|
|
38
|
+
*
|
|
39
|
+
* Returns the updated credentials, or `null` only when the server REFUSED
|
|
40
|
+
* the refresh token itself (400/401 `invalid_grant`): those credentials are
|
|
41
|
+
* dead and are deleted, and the caller asks for `levr auth login`. Every
|
|
42
|
+
* other failure (a 5xx, a 429, another error code, a proxy's HTML page, an
|
|
43
|
+
* older backend's 400, a network error, an unusable success body) THROWS and
|
|
44
|
+
* keeps the stored credentials, so the next command retries: deleting them
|
|
45
|
+
* signed the user out on any backend hiccup (ENG-7829). Same rule as the web
|
|
46
|
+
* client (`apps/client/app/services/tokenRefresh.ts`), including its
|
|
47
|
+
* compare-before-clear: a refusal of a token another process already
|
|
48
|
+
* rotated returns the newer stored pair.
|
|
49
|
+
*/
|
|
50
|
+
async function refreshToken(creds) {
|
|
51
|
+
const apiUrl = getApiUrl();
|
|
52
|
+
let res;
|
|
53
|
+
try {
|
|
54
|
+
res = await fetch(`${apiUrl}/v1/oauth/token`, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
57
|
+
body: new URLSearchParams({
|
|
58
|
+
grant_type: "refresh_token",
|
|
59
|
+
refresh_token: creds.refresh_token,
|
|
60
|
+
client_id: CLI_CLIENT_ID
|
|
61
|
+
})
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw transientFailure(`network error: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
|
+
}
|
|
66
|
+
if (!res.ok) {
|
|
67
|
+
if (await isRefusal(res)) {
|
|
68
|
+
const stored = readCredentials();
|
|
69
|
+
if (stored && stored.refresh_token !== creds.refresh_token) return sameServer(stored, creds.api_url) ? stored : null;
|
|
70
|
+
deleteCredentials();
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
throw transientFailure(`HTTP ${res.status}`);
|
|
74
|
+
}
|
|
75
|
+
const data = await readTokenResponse(res);
|
|
76
|
+
if (!data) throw transientFailure("unusable response");
|
|
77
|
+
const updated = {
|
|
78
|
+
...creds,
|
|
79
|
+
access_token: data.access_token,
|
|
80
|
+
refresh_token: data.refresh_token,
|
|
81
|
+
expires_at: new Date(Date.now() + data.expires_in * 1e3).toISOString()
|
|
82
|
+
};
|
|
83
|
+
writeCredentials(updated);
|
|
84
|
+
return updated;
|
|
85
|
+
}
|
|
86
|
+
function sameServer(stored, apiUrl) {
|
|
87
|
+
return stored.api_url.replace(/\/+$/, "") === apiUrl.replace(/\/+$/, "");
|
|
88
|
+
}
|
|
89
|
+
function transientFailure(reason) {
|
|
90
|
+
return /* @__PURE__ */ new Error(`Token refresh failed (${reason}). Your login is kept; try again, or run 'levr auth login' if it keeps failing.`);
|
|
91
|
+
}
|
|
92
|
+
/** The server refused the refresh token itself: a 400/401 `invalid_grant`. */
|
|
93
|
+
async function isRefusal(res) {
|
|
94
|
+
if (res.status !== 400 && res.status !== 401) return false;
|
|
95
|
+
try {
|
|
96
|
+
return (await res.json())?.error === "invalid_grant";
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The token response, or `null` when it is not one (a captive portal's 200,
|
|
103
|
+
* a body missing a token): writing it would store a login that cannot be
|
|
104
|
+
* refreshed.
|
|
105
|
+
*/
|
|
106
|
+
async function readTokenResponse(res) {
|
|
107
|
+
try {
|
|
108
|
+
const data = await res.json();
|
|
109
|
+
return typeof data?.access_token === "string" && data.access_token !== "" && typeof data.refresh_token === "string" && data.refresh_token !== "" && typeof data.expires_in === "number" && Number.isFinite(data.expires_in) ? data : null;
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
export { classifyToken, isTokenExpired, refreshToken };
|
package/package.json
CHANGED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { CLI_CLIENT_ID, deleteCredentials, getApiUrl, writeCredentials } from "./env-Cv-anxJA.js";
|
|
2
|
-
|
|
3
|
-
//#region src/auth/token-shape.ts
|
|
4
|
-
/**
|
|
5
|
-
* Classify a bearer token by its SHAPE — [issue:ENG-6068].
|
|
6
|
-
*
|
|
7
|
-
* The server decides how to verify a token by looking at the token, not at
|
|
8
|
-
* where it came from (`isPersonalAccessToken` in the backend's auth
|
|
9
|
-
* middleware): a Levr personal access token starts with `tq_`, and anything
|
|
10
|
-
* else is handed to the JWT verifier. `LEVR_TOKEN` carries either kind — a
|
|
11
|
-
* PAT minted in Settings, or an OAuth access token a device-code login
|
|
12
|
-
* printed for CI — so the CLI must apply the same rule. Until ENG-6068 every
|
|
13
|
-
* `LEVR_TOKEN` was called a PAT, and the JWT-only steps (resolving
|
|
14
|
-
* `--workspace-id` / `LEVR_WORKSPACE_ID`, `levr workspace list|select`) were
|
|
15
|
-
* silently skipped for an OAuth token, which has no workspace of its own.
|
|
16
|
-
*
|
|
17
|
-
* Leaf module on purpose: no imports, so `resolve-token`, the handlers and
|
|
18
|
-
* their tests can all read it without pulling in credentials or the SDK.
|
|
19
|
-
*/
|
|
20
|
-
const PAT_PREFIX = "tq_";
|
|
21
|
-
/** `tq_…` is a personal access token; everything else is treated as a JWT. */
|
|
22
|
-
function classifyToken(token) {
|
|
23
|
-
return token.startsWith(PAT_PREFIX) ? "pat" : "jwt";
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
//#endregion
|
|
27
|
-
//#region src/auth/token-refresh.ts
|
|
28
|
-
const REFRESH_BUFFER_MS = 300 * 1e3;
|
|
29
|
-
/**
|
|
30
|
-
* Check if stored JWT credentials are expired (or within 5 min of expiry).
|
|
31
|
-
*/
|
|
32
|
-
function isTokenExpired(creds) {
|
|
33
|
-
const expiresAt = new Date(creds.expires_at).getTime();
|
|
34
|
-
return Date.now() >= expiresAt - REFRESH_BUFFER_MS;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Refresh JWT credentials using the stored refresh token.
|
|
38
|
-
* Returns updated credentials or null if refresh failed.
|
|
39
|
-
*/
|
|
40
|
-
async function refreshToken(creds) {
|
|
41
|
-
const apiUrl = getApiUrl();
|
|
42
|
-
try {
|
|
43
|
-
const res = await fetch(`${apiUrl}/v1/oauth/token`, {
|
|
44
|
-
method: "POST",
|
|
45
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
46
|
-
body: new URLSearchParams({
|
|
47
|
-
grant_type: "refresh_token",
|
|
48
|
-
refresh_token: creds.refresh_token,
|
|
49
|
-
client_id: CLI_CLIENT_ID
|
|
50
|
-
})
|
|
51
|
-
});
|
|
52
|
-
if (!res.ok) {
|
|
53
|
-
deleteCredentials();
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
const data = await res.json();
|
|
57
|
-
const updated = {
|
|
58
|
-
...creds,
|
|
59
|
-
access_token: data.access_token,
|
|
60
|
-
refresh_token: data.refresh_token,
|
|
61
|
-
expires_at: new Date(Date.now() + data.expires_in * 1e3).toISOString()
|
|
62
|
-
};
|
|
63
|
-
writeCredentials(updated);
|
|
64
|
-
return updated;
|
|
65
|
-
} catch {
|
|
66
|
-
deleteCredentials();
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
//#endregion
|
|
72
|
-
export { classifyToken, isTokenExpired, refreshToken };
|