feat: permissions (#168)
- Load `app-permissions` from schema exported by `@octokit/openapi` - Update documentation in README.md - Implement the `permissions_*` inputs in the action code --------- Co-authored-by: Parker Brown <17183625+parkerbxyz@users.noreply.github.com>
This commit is contained in:
23
lib/get-permissions-from-inputs.js
Normal file
23
lib/get-permissions-from-inputs.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Finds all permissions passed via `permision-*` inputs and turns them into an object.
|
||||
*
|
||||
* @see https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#inputs
|
||||
* @param {NodeJS.ProcessEnv} env
|
||||
* @returns {undefined | Record<string, string>}
|
||||
*/
|
||||
export function getPermissionsFromInputs(env) {
|
||||
return Object.entries(env).reduce((permissions, [key, value]) => {
|
||||
if (!key.startsWith("INPUT_PERMISSION_")) return permissions;
|
||||
|
||||
const permission = key.slice("INPUT_PERMISSION_".length).toLowerCase();
|
||||
if (permissions === undefined) {
|
||||
return { [permission]: value };
|
||||
}
|
||||
|
||||
return {
|
||||
// @ts-expect-error - needs to be typed correctly
|
||||
...permissions,
|
||||
[permission]: value,
|
||||
};
|
||||
}, undefined);
|
||||
}
|
||||
38
lib/main.js
38
lib/main.js
@@ -6,6 +6,7 @@ import pRetry from "p-retry";
|
||||
* @param {string} privateKey
|
||||
* @param {string} owner
|
||||
* @param {string[]} repositories
|
||||
* @param {undefined | Record<string, string>} permissions
|
||||
* @param {import("@actions/core")} core
|
||||
* @param {import("@octokit/auth-app").createAppAuth} createAppAuth
|
||||
* @param {import("@octokit/request").request} request
|
||||
@@ -16,10 +17,11 @@ export async function main(
|
||||
privateKey,
|
||||
owner,
|
||||
repositories,
|
||||
permissions,
|
||||
core,
|
||||
createAppAuth,
|
||||
request,
|
||||
skipTokenRevoke
|
||||
skipTokenRevoke,
|
||||
) {
|
||||
let parsedOwner = "";
|
||||
let parsedRepositoryNames = [];
|
||||
@@ -31,7 +33,7 @@ export async function main(
|
||||
parsedRepositoryNames = [repo];
|
||||
|
||||
core.info(
|
||||
`owner and repositories not set, creating token for the current repository ("${repo}")`
|
||||
`owner and repositories not set, creating token for the current repository ("${repo}")`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +42,7 @@ export async function main(
|
||||
parsedOwner = owner;
|
||||
|
||||
core.info(
|
||||
`repositories not set, creating token for all repositories for given owner "${owner}"`
|
||||
`repositories not set, creating token for all repositories for given owner "${owner}"`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,8 +53,8 @@ export async function main(
|
||||
|
||||
core.info(
|
||||
`owner not set, creating owner for given repositories "${repositories.join(
|
||||
","
|
||||
)}" in current owner ("${parsedOwner}")`
|
||||
",",
|
||||
)}" in current owner ("${parsedOwner}")`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,8 +65,8 @@ export async function main(
|
||||
|
||||
core.info(
|
||||
`owner and repositories set, creating token for repositories "${repositories.join(
|
||||
","
|
||||
)}" owned by "${owner}"`
|
||||
",",
|
||||
)}" owned by "${owner}"`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,31 +86,32 @@ export async function main(
|
||||
request,
|
||||
auth,
|
||||
parsedOwner,
|
||||
parsedRepositoryNames
|
||||
parsedRepositoryNames,
|
||||
permissions,
|
||||
),
|
||||
{
|
||||
onFailedAttempt: (error) => {
|
||||
core.info(
|
||||
`Failed to create token for "${parsedRepositoryNames.join(
|
||||
","
|
||||
)}" (attempt ${error.attemptNumber}): ${error.message}`
|
||||
",",
|
||||
)}" (attempt ${error.attemptNumber}): ${error.message}`,
|
||||
);
|
||||
},
|
||||
retries: 3,
|
||||
}
|
||||
},
|
||||
));
|
||||
} else {
|
||||
// Otherwise get the installation for the owner, which can either be an organization or a user account
|
||||
({ authentication, installationId, appSlug } = await pRetry(
|
||||
() => getTokenFromOwner(request, auth, parsedOwner),
|
||||
() => getTokenFromOwner(request, auth, parsedOwner, permissions),
|
||||
{
|
||||
onFailedAttempt: (error) => {
|
||||
core.info(
|
||||
`Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}`
|
||||
`Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}`,
|
||||
);
|
||||
},
|
||||
retries: 3,
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -126,7 +129,7 @@ export async function main(
|
||||
}
|
||||
}
|
||||
|
||||
async function getTokenFromOwner(request, auth, parsedOwner) {
|
||||
async function getTokenFromOwner(request, auth, parsedOwner, permissions) {
|
||||
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-user-installation-for-the-authenticated-app
|
||||
// This endpoint works for both users and organizations
|
||||
const response = await request("GET /users/{username}/installation", {
|
||||
@@ -140,6 +143,7 @@ async function getTokenFromOwner(request, auth, parsedOwner) {
|
||||
const authentication = await auth({
|
||||
type: "installation",
|
||||
installationId: response.data.id,
|
||||
permissions,
|
||||
});
|
||||
|
||||
const installationId = response.data.id;
|
||||
@@ -152,7 +156,8 @@ async function getTokenFromRepository(
|
||||
request,
|
||||
auth,
|
||||
parsedOwner,
|
||||
parsedRepositoryNames
|
||||
parsedRepositoryNames,
|
||||
permissions,
|
||||
) {
|
||||
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app
|
||||
const response = await request("GET /repos/{owner}/{repo}/installation", {
|
||||
@@ -168,6 +173,7 @@ async function getTokenFromRepository(
|
||||
type: "installation",
|
||||
installationId: response.data.id,
|
||||
repositoryNames: parsedRepositoryNames,
|
||||
permissions,
|
||||
});
|
||||
|
||||
const installationId = response.data.id;
|
||||
|
||||
@@ -17,7 +17,7 @@ const proxyUrl =
|
||||
const proxyFetch = (url, options) => {
|
||||
const urlHost = new URL(url).hostname;
|
||||
const noProxy = (process.env.no_proxy || process.env.NO_PROXY || "").split(
|
||||
","
|
||||
",",
|
||||
);
|
||||
|
||||
if (!noProxy.includes(urlHost)) {
|
||||
|
||||
Reference in New Issue
Block a user