mirror of
https://github.com/github/codeql-action.git
synced 2025-12-27 01:30:10 +08:00
462 lines
13 KiB
JavaScript
462 lines
13 KiB
JavaScript
// pkg/dist-src/index.js
|
|
import { getUserAgent } from "universal-user-agent";
|
|
import { request as defaultRequest } from "@octokit/request";
|
|
import { createOAuthAppAuth } from "@octokit/auth-oauth-app";
|
|
|
|
// pkg/dist-src/get-app-authentication.js
|
|
import githubAppJwt from "universal-github-app-jwt";
|
|
async function getAppAuthentication({
|
|
appId,
|
|
privateKey,
|
|
timeDifference
|
|
}) {
|
|
try {
|
|
const authOptions = {
|
|
id: appId,
|
|
privateKey
|
|
};
|
|
if (timeDifference) {
|
|
Object.assign(authOptions, {
|
|
now: Math.floor(Date.now() / 1e3) + timeDifference
|
|
});
|
|
}
|
|
const appAuthentication = await githubAppJwt(authOptions);
|
|
return {
|
|
type: "app",
|
|
token: appAuthentication.token,
|
|
appId: appAuthentication.appId,
|
|
expiresAt: new Date(appAuthentication.expiration * 1e3).toISOString()
|
|
};
|
|
} catch (error) {
|
|
if (privateKey === "-----BEGIN RSA PRIVATE KEY-----") {
|
|
throw new Error(
|
|
"The 'privateKey` option contains only the first line '-----BEGIN RSA PRIVATE KEY-----'. If you are setting it using a `.env` file, make sure it is set on a single line with newlines replaced by '\n'"
|
|
);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
// pkg/dist-src/cache.js
|
|
import { Lru } from "toad-cache";
|
|
function getCache() {
|
|
return new Lru(
|
|
// cache max. 15000 tokens, that will use less than 10mb memory
|
|
15e3,
|
|
// Cache for 1 minute less than GitHub expiry
|
|
1e3 * 60 * 59
|
|
);
|
|
}
|
|
async function get(cache, options) {
|
|
const cacheKey = optionsToCacheKey(options);
|
|
const result = await cache.get(cacheKey);
|
|
if (!result) {
|
|
return;
|
|
}
|
|
const [
|
|
token,
|
|
createdAt,
|
|
expiresAt,
|
|
repositorySelection,
|
|
permissionsString,
|
|
singleFileName
|
|
] = result.split("|");
|
|
const permissions = options.permissions || permissionsString.split(/,/).reduce((permissions2, string) => {
|
|
if (/!$/.test(string)) {
|
|
permissions2[string.slice(0, -1)] = "write";
|
|
} else {
|
|
permissions2[string] = "read";
|
|
}
|
|
return permissions2;
|
|
}, {});
|
|
return {
|
|
token,
|
|
createdAt,
|
|
expiresAt,
|
|
permissions,
|
|
repositoryIds: options.repositoryIds,
|
|
repositoryNames: options.repositoryNames,
|
|
singleFileName,
|
|
repositorySelection
|
|
};
|
|
}
|
|
async function set(cache, options, data) {
|
|
const key = optionsToCacheKey(options);
|
|
const permissionsString = options.permissions ? "" : Object.keys(data.permissions).map(
|
|
(name) => `${name}${data.permissions[name] === "write" ? "!" : ""}`
|
|
).join(",");
|
|
const value = [
|
|
data.token,
|
|
data.createdAt,
|
|
data.expiresAt,
|
|
data.repositorySelection,
|
|
permissionsString,
|
|
data.singleFileName
|
|
].join("|");
|
|
await cache.set(key, value);
|
|
}
|
|
function optionsToCacheKey({
|
|
installationId,
|
|
permissions = {},
|
|
repositoryIds = [],
|
|
repositoryNames = []
|
|
}) {
|
|
const permissionsString = Object.keys(permissions).sort().map((name) => permissions[name] === "read" ? name : `${name}!`).join(",");
|
|
const repositoryIdsString = repositoryIds.sort().join(",");
|
|
const repositoryNamesString = repositoryNames.join(",");
|
|
return [
|
|
installationId,
|
|
repositoryIdsString,
|
|
repositoryNamesString,
|
|
permissionsString
|
|
].filter(Boolean).join("|");
|
|
}
|
|
|
|
// pkg/dist-src/to-token-authentication.js
|
|
function toTokenAuthentication({
|
|
installationId,
|
|
token,
|
|
createdAt,
|
|
expiresAt,
|
|
repositorySelection,
|
|
permissions,
|
|
repositoryIds,
|
|
repositoryNames,
|
|
singleFileName
|
|
}) {
|
|
return Object.assign(
|
|
{
|
|
type: "token",
|
|
tokenType: "installation",
|
|
token,
|
|
installationId,
|
|
permissions,
|
|
createdAt,
|
|
expiresAt,
|
|
repositorySelection
|
|
},
|
|
repositoryIds ? { repositoryIds } : null,
|
|
repositoryNames ? { repositoryNames } : null,
|
|
singleFileName ? { singleFileName } : null
|
|
);
|
|
}
|
|
|
|
// pkg/dist-src/get-installation-authentication.js
|
|
async function getInstallationAuthentication(state, options, customRequest) {
|
|
const installationId = Number(options.installationId || state.installationId);
|
|
if (!installationId) {
|
|
throw new Error(
|
|
"[@octokit/auth-app] installationId option is required for installation authentication."
|
|
);
|
|
}
|
|
if (options.factory) {
|
|
const { type, factory, oauthApp, ...factoryAuthOptions } = {
|
|
...state,
|
|
...options
|
|
};
|
|
return factory(factoryAuthOptions);
|
|
}
|
|
const optionsWithInstallationTokenFromState = Object.assign(
|
|
{ installationId },
|
|
options
|
|
);
|
|
if (!options.refresh) {
|
|
const result = await get(
|
|
state.cache,
|
|
optionsWithInstallationTokenFromState
|
|
);
|
|
if (result) {
|
|
const {
|
|
token: token2,
|
|
createdAt: createdAt2,
|
|
expiresAt: expiresAt2,
|
|
permissions: permissions2,
|
|
repositoryIds: repositoryIds2,
|
|
repositoryNames: repositoryNames2,
|
|
singleFileName: singleFileName2,
|
|
repositorySelection: repositorySelection2
|
|
} = result;
|
|
return toTokenAuthentication({
|
|
installationId,
|
|
token: token2,
|
|
createdAt: createdAt2,
|
|
expiresAt: expiresAt2,
|
|
permissions: permissions2,
|
|
repositorySelection: repositorySelection2,
|
|
repositoryIds: repositoryIds2,
|
|
repositoryNames: repositoryNames2,
|
|
singleFileName: singleFileName2
|
|
});
|
|
}
|
|
}
|
|
const appAuthentication = await getAppAuthentication(state);
|
|
const request = customRequest || state.request;
|
|
const payload = {
|
|
installation_id: installationId,
|
|
mediaType: {
|
|
previews: ["machine-man"]
|
|
},
|
|
headers: {
|
|
authorization: `bearer ${appAuthentication.token}`
|
|
}
|
|
};
|
|
if (options.repositoryIds) {
|
|
Object.assign(payload, { repository_ids: options.repositoryIds });
|
|
}
|
|
if (options.repositoryNames) {
|
|
Object.assign(payload, {
|
|
repositories: options.repositoryNames
|
|
});
|
|
}
|
|
if (options.permissions) {
|
|
Object.assign(payload, { permissions: options.permissions });
|
|
}
|
|
const {
|
|
data: {
|
|
token,
|
|
expires_at: expiresAt,
|
|
repositories,
|
|
permissions: permissionsOptional,
|
|
repository_selection: repositorySelectionOptional,
|
|
single_file: singleFileName
|
|
}
|
|
} = await request(
|
|
"POST /app/installations/{installation_id}/access_tokens",
|
|
payload
|
|
);
|
|
const permissions = permissionsOptional || {};
|
|
const repositorySelection = repositorySelectionOptional || "all";
|
|
const repositoryIds = repositories ? repositories.map((r) => r.id) : void 0;
|
|
const repositoryNames = repositories ? repositories.map((repo) => repo.name) : void 0;
|
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
const cacheOptions = {
|
|
token,
|
|
createdAt,
|
|
expiresAt,
|
|
repositorySelection,
|
|
permissions,
|
|
repositoryIds,
|
|
repositoryNames
|
|
};
|
|
if (singleFileName) {
|
|
Object.assign(payload, { singleFileName });
|
|
}
|
|
await set(state.cache, optionsWithInstallationTokenFromState, cacheOptions);
|
|
const cacheData = {
|
|
installationId,
|
|
token,
|
|
createdAt,
|
|
expiresAt,
|
|
repositorySelection,
|
|
permissions,
|
|
repositoryIds,
|
|
repositoryNames
|
|
};
|
|
if (singleFileName) {
|
|
Object.assign(cacheData, { singleFileName });
|
|
}
|
|
return toTokenAuthentication(cacheData);
|
|
}
|
|
|
|
// pkg/dist-src/auth.js
|
|
async function auth(state, authOptions) {
|
|
switch (authOptions.type) {
|
|
case "app":
|
|
return getAppAuthentication(state);
|
|
case "oauth-app":
|
|
return state.oauthApp({ type: "oauth-app" });
|
|
case "installation":
|
|
authOptions;
|
|
return getInstallationAuthentication(state, {
|
|
...authOptions,
|
|
type: "installation"
|
|
});
|
|
case "oauth-user":
|
|
return state.oauthApp(authOptions);
|
|
default:
|
|
throw new Error(`Invalid auth type: ${authOptions.type}`);
|
|
}
|
|
}
|
|
|
|
// pkg/dist-src/hook.js
|
|
import { requiresBasicAuth } from "@octokit/auth-oauth-user";
|
|
import { RequestError } from "@octokit/request-error";
|
|
|
|
// pkg/dist-src/requires-app-auth.js
|
|
var PATHS = [
|
|
"/app",
|
|
"/app/hook/config",
|
|
"/app/hook/deliveries",
|
|
"/app/hook/deliveries/{delivery_id}",
|
|
"/app/hook/deliveries/{delivery_id}/attempts",
|
|
"/app/installations",
|
|
"/app/installations/{installation_id}",
|
|
"/app/installations/{installation_id}/access_tokens",
|
|
"/app/installations/{installation_id}/suspended",
|
|
"/app/installation-requests",
|
|
"/marketplace_listing/accounts/{account_id}",
|
|
"/marketplace_listing/plan",
|
|
"/marketplace_listing/plans",
|
|
"/marketplace_listing/plans/{plan_id}/accounts",
|
|
"/marketplace_listing/stubbed/accounts/{account_id}",
|
|
"/marketplace_listing/stubbed/plan",
|
|
"/marketplace_listing/stubbed/plans",
|
|
"/marketplace_listing/stubbed/plans/{plan_id}/accounts",
|
|
"/orgs/{org}/installation",
|
|
"/repos/{owner}/{repo}/installation",
|
|
"/users/{username}/installation"
|
|
];
|
|
function routeMatcher(paths) {
|
|
const regexes = paths.map(
|
|
(p) => p.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
|
|
);
|
|
const regex = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})$`;
|
|
return new RegExp(regex, "i");
|
|
}
|
|
var REGEX = routeMatcher(PATHS);
|
|
function requiresAppAuth(url) {
|
|
return !!url && REGEX.test(url.split("?")[0]);
|
|
}
|
|
|
|
// pkg/dist-src/hook.js
|
|
var FIVE_SECONDS_IN_MS = 5 * 1e3;
|
|
function isNotTimeSkewError(error) {
|
|
return !(error.message.match(
|
|
/'Expiration time' claim \('exp'\) must be a numeric value representing the future time at which the assertion expires/
|
|
) || error.message.match(
|
|
/'Issued at' claim \('iat'\) must be an Integer representing the time that the assertion was issued/
|
|
));
|
|
}
|
|
async function hook(state, request, route, parameters) {
|
|
const endpoint = request.endpoint.merge(route, parameters);
|
|
const url = endpoint.url;
|
|
if (/\/login\/oauth\/access_token$/.test(url)) {
|
|
return request(endpoint);
|
|
}
|
|
if (requiresAppAuth(url.replace(request.endpoint.DEFAULTS.baseUrl, ""))) {
|
|
const { token: token2 } = await getAppAuthentication(state);
|
|
endpoint.headers.authorization = `bearer ${token2}`;
|
|
let response;
|
|
try {
|
|
response = await request(endpoint);
|
|
} catch (error) {
|
|
if (isNotTimeSkewError(error)) {
|
|
throw error;
|
|
}
|
|
if (typeof error.response.headers.date === "undefined") {
|
|
throw error;
|
|
}
|
|
const diff = Math.floor(
|
|
(Date.parse(error.response.headers.date) - Date.parse((/* @__PURE__ */ new Date()).toString())) / 1e3
|
|
);
|
|
state.log.warn(error.message);
|
|
state.log.warn(
|
|
`[@octokit/auth-app] GitHub API time and system time are different by ${diff} seconds. Retrying request with the difference accounted for.`
|
|
);
|
|
const { token: token3 } = await getAppAuthentication({
|
|
...state,
|
|
timeDifference: diff
|
|
});
|
|
endpoint.headers.authorization = `bearer ${token3}`;
|
|
return request(endpoint);
|
|
}
|
|
return response;
|
|
}
|
|
if (requiresBasicAuth(url)) {
|
|
const authentication = await state.oauthApp({ type: "oauth-app" });
|
|
endpoint.headers.authorization = authentication.headers.authorization;
|
|
return request(endpoint);
|
|
}
|
|
const { token, createdAt } = await getInstallationAuthentication(
|
|
state,
|
|
// @ts-expect-error TBD
|
|
{},
|
|
request.defaults({ baseUrl: endpoint.baseUrl })
|
|
);
|
|
endpoint.headers.authorization = `token ${token}`;
|
|
return sendRequestWithRetries(
|
|
state,
|
|
request,
|
|
endpoint,
|
|
createdAt
|
|
);
|
|
}
|
|
async function sendRequestWithRetries(state, request, options, createdAt, retries = 0) {
|
|
const timeSinceTokenCreationInMs = +/* @__PURE__ */ new Date() - +new Date(createdAt);
|
|
try {
|
|
return await request(options);
|
|
} catch (error) {
|
|
if (error.status !== 401) {
|
|
throw error;
|
|
}
|
|
if (timeSinceTokenCreationInMs >= FIVE_SECONDS_IN_MS) {
|
|
if (retries > 0) {
|
|
error.message = `After ${retries} retries within ${timeSinceTokenCreationInMs / 1e3}s of creating the installation access token, the response remains 401. At this point, the cause may be an authentication problem or a system outage. Please check https://www.githubstatus.com for status information`;
|
|
}
|
|
throw error;
|
|
}
|
|
++retries;
|
|
const awaitTime = retries * 1e3;
|
|
state.log.warn(
|
|
`[@octokit/auth-app] Retrying after 401 response to account for token replication delay (retry: ${retries}, wait: ${awaitTime / 1e3}s)`
|
|
);
|
|
await new Promise((resolve) => setTimeout(resolve, awaitTime));
|
|
return sendRequestWithRetries(state, request, options, createdAt, retries);
|
|
}
|
|
}
|
|
|
|
// pkg/dist-src/version.js
|
|
var VERSION = "7.1.5";
|
|
|
|
// pkg/dist-src/index.js
|
|
import { createOAuthUserAuth } from "@octokit/auth-oauth-user";
|
|
function createAppAuth(options) {
|
|
if (!options.appId) {
|
|
throw new Error("[@octokit/auth-app] appId option is required");
|
|
}
|
|
if (!options.privateKey) {
|
|
throw new Error("[@octokit/auth-app] privateKey option is required");
|
|
}
|
|
if ("installationId" in options && !options.installationId) {
|
|
throw new Error(
|
|
"[@octokit/auth-app] installationId is set to a falsy value"
|
|
);
|
|
}
|
|
const log = Object.assign(
|
|
{
|
|
warn: console.warn.bind(console)
|
|
},
|
|
options.log
|
|
);
|
|
const request = options.request || defaultRequest.defaults({
|
|
headers: {
|
|
"user-agent": `octokit-auth-app.js/${VERSION} ${getUserAgent()}`
|
|
}
|
|
});
|
|
const state = Object.assign(
|
|
{
|
|
request,
|
|
cache: getCache()
|
|
},
|
|
options,
|
|
options.installationId ? { installationId: Number(options.installationId) } : {},
|
|
{
|
|
log,
|
|
oauthApp: createOAuthAppAuth({
|
|
clientType: "github-app",
|
|
clientId: options.clientId || "",
|
|
clientSecret: options.clientSecret || "",
|
|
request
|
|
})
|
|
}
|
|
);
|
|
return Object.assign(auth.bind(null, state), {
|
|
hook: hook.bind(null, state)
|
|
});
|
|
}
|
|
export {
|
|
createAppAuth,
|
|
createOAuthUserAuth
|
|
};
|