securewithtrace 0.18.0 → 0.18.1
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/index.js +155 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -26548,6 +26548,101 @@ function collectFilterExpression(value, previous) {
|
|
|
26548
26548
|
previous.push(value);
|
|
26549
26549
|
return previous;
|
|
26550
26550
|
}
|
|
26551
|
+
function extractErrorMessage(body) {
|
|
26552
|
+
if (typeof body === "object" && body !== null && "message" in body && typeof body.message === "string") {
|
|
26553
|
+
const message = body.message.trim();
|
|
26554
|
+
return message.length > 0 ? message : null;
|
|
26555
|
+
}
|
|
26556
|
+
return null;
|
|
26557
|
+
}
|
|
26558
|
+
function decodeJwtExpiration(token) {
|
|
26559
|
+
if (!token) {
|
|
26560
|
+
return null;
|
|
26561
|
+
}
|
|
26562
|
+
const parts = token.split(".");
|
|
26563
|
+
const payload = parts[1];
|
|
26564
|
+
if (!payload) {
|
|
26565
|
+
return null;
|
|
26566
|
+
}
|
|
26567
|
+
try {
|
|
26568
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
26569
|
+
if (typeof decoded.exp !== "number" || !Number.isFinite(decoded.exp)) {
|
|
26570
|
+
return null;
|
|
26571
|
+
}
|
|
26572
|
+
return new Date(decoded.exp * 1e3);
|
|
26573
|
+
} catch {
|
|
26574
|
+
return null;
|
|
26575
|
+
}
|
|
26576
|
+
}
|
|
26577
|
+
function resolveLocalCloneAuthFailure() {
|
|
26578
|
+
const token = readTokenSafe();
|
|
26579
|
+
if (!token) {
|
|
26580
|
+
return { kind: "not_logged_in", hasRefreshToken: readRefreshToken() !== null };
|
|
26581
|
+
}
|
|
26582
|
+
const expiresAt = decodeJwtExpiration(token);
|
|
26583
|
+
if (expiresAt && expiresAt.getTime() <= Date.now()) {
|
|
26584
|
+
return { kind: "expired_access_token", hasRefreshToken: readRefreshToken() !== null };
|
|
26585
|
+
}
|
|
26586
|
+
return { kind: "organization_unresolved" };
|
|
26587
|
+
}
|
|
26588
|
+
function buildCloneTokenFailureWarning(cloneUrl, failure) {
|
|
26589
|
+
const fallbackSuffix = "Falling back to standard git authentication for this clone attempt.";
|
|
26590
|
+
const quotedMessage = failure.message ? ` (${failure.message})` : "";
|
|
26591
|
+
switch (failure.kind) {
|
|
26592
|
+
case "not_logged_in":
|
|
26593
|
+
return `Trace clone credentials are unavailable for ${cloneUrl}: you are not logged in. Run \`tracecli auth login\`. ${fallbackSuffix}`;
|
|
26594
|
+
case "expired_access_token":
|
|
26595
|
+
return `Trace clone credentials are unavailable for ${cloneUrl}: your CLI access token is expired${failure.hasRefreshToken ? ", and refresh did not complete" : ""}. Run \`tracecli auth logout && tracecli auth login\`, then retry. ${fallbackSuffix}`;
|
|
26596
|
+
case "organization_unresolved":
|
|
26597
|
+
return `Trace clone credentials are unavailable for ${cloneUrl}: the active organization could not be resolved from your session. Run \`tracecli org current\` and \`tracecli org switch <organization>\`, or re-authenticate with \`tracecli auth logout && tracecli auth login\`. ${fallbackSuffix}`;
|
|
26598
|
+
case "request_failed":
|
|
26599
|
+
return `Trace clone credentials are temporarily unavailable for ${cloneUrl}: the request to the Trace API failed. Check connectivity, then retry. ${fallbackSuffix}`;
|
|
26600
|
+
case "unauthorized":
|
|
26601
|
+
return `Trace rejected clone credential generation for ${cloneUrl} (401${quotedMessage}). Re-authenticate with \`tracecli auth logout && tracecli auth login\`, then retry. ${fallbackSuffix}`;
|
|
26602
|
+
case "forbidden":
|
|
26603
|
+
return `Trace denied clone credential generation for ${cloneUrl} (403${quotedMessage}). Confirm you can access this organization and repository in Trace. ${fallbackSuffix}`;
|
|
26604
|
+
case "not_found":
|
|
26605
|
+
return `Trace could not generate clone credentials for ${cloneUrl} (404${quotedMessage}). Ensure the repository is connected in the active organization and the GitHub App installation is active. ${fallbackSuffix}`;
|
|
26606
|
+
case "server_error":
|
|
26607
|
+
return `Trace failed to generate clone credentials for ${cloneUrl} due to a server error${quotedMessage}. Retry shortly. ${fallbackSuffix}`;
|
|
26608
|
+
case "unexpected_status":
|
|
26609
|
+
return `Trace returned an unexpected response while generating clone credentials for ${cloneUrl}${quotedMessage ? ` ${quotedMessage}` : ""}. ${fallbackSuffix}`;
|
|
26610
|
+
}
|
|
26611
|
+
}
|
|
26612
|
+
function withOptionalMessage(base, message) {
|
|
26613
|
+
if (typeof message === "string") {
|
|
26614
|
+
return { ...base, message };
|
|
26615
|
+
}
|
|
26616
|
+
return base;
|
|
26617
|
+
}
|
|
26618
|
+
async function listOrganizationsSafe() {
|
|
26619
|
+
const result = await authenticatedRequestSafe(
|
|
26620
|
+
(client) => client.organization.listMyOrganizations()
|
|
26621
|
+
);
|
|
26622
|
+
if (!result || result.status !== 200) {
|
|
26623
|
+
return null;
|
|
26624
|
+
}
|
|
26625
|
+
return result.body;
|
|
26626
|
+
}
|
|
26627
|
+
function resolveWorkosOrganizationId(organizations, organizationId) {
|
|
26628
|
+
const organization = organizations.find((item) => item.organizationId === organizationId);
|
|
26629
|
+
const workosOrganizationId = organization?.workosOrganizationId;
|
|
26630
|
+
if (!workosOrganizationId || workosOrganizationId.trim().length === 0) {
|
|
26631
|
+
return null;
|
|
26632
|
+
}
|
|
26633
|
+
return workosOrganizationId;
|
|
26634
|
+
}
|
|
26635
|
+
async function maybeRefreshCloneTokenSessionForOrganization(organizationId, listOrganizations = listOrganizationsSafe, refreshForOrganization = (workosOrganizationId) => refreshStoredAccessTokenForOrganization(workosOrganizationId)) {
|
|
26636
|
+
const organizations = await listOrganizations();
|
|
26637
|
+
if (!organizations) {
|
|
26638
|
+
return false;
|
|
26639
|
+
}
|
|
26640
|
+
const workosOrganizationId = resolveWorkosOrganizationId(organizations, organizationId);
|
|
26641
|
+
if (!workosOrganizationId) {
|
|
26642
|
+
return false;
|
|
26643
|
+
}
|
|
26644
|
+
return refreshForOrganization(workosOrganizationId);
|
|
26645
|
+
}
|
|
26551
26646
|
function toRepositoryListFilterOptions(options) {
|
|
26552
26647
|
return {
|
|
26553
26648
|
...options.platform ? { platform: options.platform } : {},
|
|
@@ -26764,24 +26859,66 @@ async function fetchCloneToken(repoUrl) {
|
|
|
26764
26859
|
try {
|
|
26765
26860
|
const organizationId = await resolveOrganizationIdSafe();
|
|
26766
26861
|
if (!organizationId) {
|
|
26767
|
-
return null;
|
|
26862
|
+
return { token: null, failure: resolveLocalCloneAuthFailure() };
|
|
26768
26863
|
}
|
|
26769
|
-
|
|
26864
|
+
let result = await authenticatedRequestSafe(
|
|
26770
26865
|
(client) => client.repository.getCloneToken({
|
|
26771
26866
|
params: { organizationId },
|
|
26772
26867
|
body: { repositoryUrl: repoUrl }
|
|
26773
26868
|
})
|
|
26774
26869
|
);
|
|
26870
|
+
if (result && (result.status === 401 || result.status === 403)) {
|
|
26871
|
+
const refreshed = await maybeRefreshCloneTokenSessionForOrganization(organizationId);
|
|
26872
|
+
if (refreshed) {
|
|
26873
|
+
result = await authenticatedRequestSafe(
|
|
26874
|
+
(client) => client.repository.getCloneToken({
|
|
26875
|
+
params: { organizationId },
|
|
26876
|
+
body: { repositoryUrl: repoUrl }
|
|
26877
|
+
})
|
|
26878
|
+
);
|
|
26879
|
+
}
|
|
26880
|
+
}
|
|
26775
26881
|
if (!result) {
|
|
26776
|
-
return null;
|
|
26882
|
+
return { token: null, failure: { kind: "request_failed" } };
|
|
26777
26883
|
}
|
|
26778
26884
|
if (result.status === 200) {
|
|
26779
26885
|
const token = result.body.token.trim();
|
|
26780
|
-
|
|
26886
|
+
if (token.length > 0) {
|
|
26887
|
+
return { token, failure: null };
|
|
26888
|
+
}
|
|
26889
|
+
return { token: null, failure: { kind: "unexpected_status", status: 200 } };
|
|
26781
26890
|
}
|
|
26782
|
-
|
|
26891
|
+
const message = extractErrorMessage(result.body) ?? void 0;
|
|
26892
|
+
if (result.status === 401) {
|
|
26893
|
+
return {
|
|
26894
|
+
token: null,
|
|
26895
|
+
failure: withOptionalMessage({ kind: "unauthorized", status: 401 }, message)
|
|
26896
|
+
};
|
|
26897
|
+
}
|
|
26898
|
+
if (result.status === 403) {
|
|
26899
|
+
return {
|
|
26900
|
+
token: null,
|
|
26901
|
+
failure: withOptionalMessage({ kind: "forbidden", status: 403 }, message)
|
|
26902
|
+
};
|
|
26903
|
+
}
|
|
26904
|
+
if (result.status === 404) {
|
|
26905
|
+
return {
|
|
26906
|
+
token: null,
|
|
26907
|
+
failure: withOptionalMessage({ kind: "not_found", status: 404 }, message)
|
|
26908
|
+
};
|
|
26909
|
+
}
|
|
26910
|
+
if (result.status >= 500) {
|
|
26911
|
+
return {
|
|
26912
|
+
token: null,
|
|
26913
|
+
failure: withOptionalMessage({ kind: "server_error", status: result.status }, message)
|
|
26914
|
+
};
|
|
26915
|
+
}
|
|
26916
|
+
return {
|
|
26917
|
+
token: null,
|
|
26918
|
+
failure: withOptionalMessage({ kind: "unexpected_status", status: result.status }, message)
|
|
26919
|
+
};
|
|
26783
26920
|
} catch {
|
|
26784
|
-
return null;
|
|
26921
|
+
return { token: null, failure: { kind: "request_failed" } };
|
|
26785
26922
|
}
|
|
26786
26923
|
}
|
|
26787
26924
|
function createCloneCommand() {
|
|
@@ -26797,8 +26934,14 @@ Examples:
|
|
|
26797
26934
|
const cloneUrl = resolveRepoUrl(repository);
|
|
26798
26935
|
const targetDir = directory ?? extractRepoName(repository);
|
|
26799
26936
|
let installationToken = null;
|
|
26937
|
+
let cloneTokenFailure = null;
|
|
26800
26938
|
if (cloneUrl.startsWith("https://github.com/")) {
|
|
26801
|
-
|
|
26939
|
+
const cloneTokenResult = await fetchCloneToken(cloneUrl);
|
|
26940
|
+
installationToken = cloneTokenResult.token;
|
|
26941
|
+
cloneTokenFailure = cloneTokenResult.failure;
|
|
26942
|
+
if (cloneTokenFailure) {
|
|
26943
|
+
console.warn(buildCloneTokenFailureWarning(cloneUrl, cloneTokenFailure));
|
|
26944
|
+
}
|
|
26802
26945
|
}
|
|
26803
26946
|
const effectiveUrl = installationToken ? urlWithUsername(cloneUrl) : cloneUrl;
|
|
26804
26947
|
let askPassScript;
|
|
@@ -26827,6 +26970,11 @@ Examples:
|
|
|
26827
26970
|
});
|
|
26828
26971
|
const exitCode = await proc.exited;
|
|
26829
26972
|
if (exitCode !== 0) {
|
|
26973
|
+
if (!installationToken && cloneTokenFailure) {
|
|
26974
|
+
console.error(
|
|
26975
|
+
"Clone failed while Trace clone credentials were unavailable. Resolve the warning above and retry."
|
|
26976
|
+
);
|
|
26977
|
+
}
|
|
26830
26978
|
process.exit(1);
|
|
26831
26979
|
}
|
|
26832
26980
|
if (installationToken) {
|