Compare commits

..

6 Commits

Author SHA1 Message Date
semantic-release-bot
4821f52fa7 build(release): 2.0.4 [skip ci]
## [2.0.4](https://github.com/actions/create-github-app-token/compare/v2.0.3...v2.0.4) (2025-05-02)

### Bug Fixes

* permission input handling ([#243](https://github.com/actions/create-github-app-token/issues/243)) ([2950cbc](2950cbc446))
2025-05-02 18:44:32 +00:00
Parker Brown
2950cbc446 fix: permission input handling (#243)
This pull request fixes the handling of permissions inputs.

- Updated `getPermissionsFromInputs` in
`lib/get-permissions-from-inputs.js` to use hyphens
(`INPUT_PERMISSION-`) instead of underscores (`INPUT_PERMISSION_`) in
input keys, added a check to skip empty values, and clarified behavior
when no permissions are set.
- Added a `shouldRetry` function to retry requests when server errors
(HTTP status 500 or higher) occur in the `main` function in
`lib/main.js` to prevent unnecessary retries.
- Updated test cases in `tests/main-token-permissions-set.test.js` to
match the new input key format with hyphens.
- Added a default empty string for unset inputs (e.g.,
`INPUT_PERMISSION-ADMINISTRATION`) in `tests/main.js` to simulate the
behavior of the Actions runner.
- Updated snapshots in `tests/snapshots/index.js.md` to reflect the
updated hyphenated input keys in permissions.

---------

Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com>
2025-05-02 11:44:01 -07:00
semantic-release-bot
30bf6253fa build(release): 2.0.3 [skip ci]
## [2.0.3](https://github.com/actions/create-github-app-token/compare/v2.0.2...v2.0.3) (2025-05-01)

### Bug Fixes

* **README:** use `v2` in examples ([#234](https://github.com/actions/create-github-app-token/issues/234)) ([9ba274d](9ba274d954)), closes [#232](https://github.com/actions/create-github-app-token/issues/232)
* use `core.getBooleanInput()` to retrieve boolean input values ([#223](https://github.com/actions/create-github-app-token/issues/223)) ([c3c17c7](c3c17c79cc))
2025-05-01 15:34:52 +00:00
Yuta Kasai
c3c17c79cc fix: use core.getBooleanInput() to retrieve boolean input values (#223)
This PR switches from evaluating values passed to `skip-token-revoke` as
true if they are truthy in JavaScript, to using `getBooleanInput`. This
change ensures that only proper YAML boolean values are recognized,
preventing unintended evaluations to true.
- The definition of `getBooleanInput` is here: definition of
`core#getBooealnInput` is here:
930c890727/packages/core/src/core.ts (L188-L208)

The documentation states, `"If truthy, the token will not be revoked
when the current job is complete"`, so this change could be considered a
breaking change. This means that if there are users who rely on `truthy`
and expect values like whitespace or `"false"` to be evaluated as true
(though this is likely rare), it would be a breaking change.
- `Boolean(" ")` and `Boolean("false")` are both evaluated as true.

Alternatively, it can simply be considered a fix. How to handle this is
up to the maintainer.

Resolves https://github.com/actions/create-github-app-token/issues/216
2025-04-25 11:59:34 -07:00
CarolMebiom
9ba274d954 fix(README): use v2 in examples (#234)
Fixes #232
2025-04-25 11:32:06 -07:00
nakatani-yo
a3c826a204 docs: fix typo in CONTRIBUTING.md (#233) 2025-04-10 11:39:20 -07:00
19 changed files with 52 additions and 32 deletions

View File

@@ -12,4 +12,4 @@ Run tests locally
npm test npm test
``` ```
Learn more about how the tests work in [test/README.md](test/README.md). Learn more about how the tests work in [tests/README.md](tests/README.md).

View File

@@ -28,7 +28,7 @@ jobs:
hello-world: hello-world:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/create-github-app-token@v2
id: app-token id: app-token
with: with:
app-id: ${{ vars.APP_ID }} app-id: ${{ vars.APP_ID }}
@@ -47,7 +47,7 @@ jobs:
auto-format: auto-format:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/create-github-app-token@v2
id: app-token id: app-token
with: with:
# required # required
@@ -73,7 +73,7 @@ jobs:
auto-format: auto-format:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/create-github-app-token@v2
id: app-token id: app-token
with: with:
# required # required
@@ -98,7 +98,7 @@ jobs:
auto-format: auto-format:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/create-github-app-token@v2
id: app-token id: app-token
with: with:
# required # required
@@ -135,7 +135,7 @@ jobs:
hello-world: hello-world:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/create-github-app-token@v2
id: app-token id: app-token
with: with:
app-id: ${{ vars.APP_ID }} app-id: ${{ vars.APP_ID }}
@@ -157,7 +157,7 @@ jobs:
hello-world: hello-world:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/create-github-app-token@v2
id: app-token id: app-token
with: with:
app-id: ${{ vars.APP_ID }} app-id: ${{ vars.APP_ID }}
@@ -182,7 +182,7 @@ jobs:
hello-world: hello-world:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/create-github-app-token@v2
id: app-token id: app-token
with: with:
app-id: ${{ vars.APP_ID }} app-id: ${{ vars.APP_ID }}
@@ -207,7 +207,7 @@ jobs:
hello-world: hello-world:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/create-github-app-token@v2
id: app-token id: app-token
with: with:
app-id: ${{ vars.APP_ID }} app-id: ${{ vars.APP_ID }}
@@ -249,7 +249,7 @@ jobs:
owners-and-repos: ${{ fromJson(needs.set-matrix.outputs.matrix) }} owners-and-repos: ${{ fromJson(needs.set-matrix.outputs.matrix) }}
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/create-github-app-token@v2
id: app-token id: app-token
with: with:
app-id: ${{ vars.APP_ID }} app-id: ${{ vars.APP_ID }}
@@ -279,7 +279,7 @@ jobs:
steps: steps:
- name: Create GitHub App token - name: Create GitHub App token
id: create_token id: create_token
uses: actions/create-github-app-token@v1 uses: actions/create-github-app-token@v2
with: with:
app-id: ${{ vars.GHES_APP_ID }} app-id: ${{ vars.GHES_APP_ID }}
private-key: ${{ secrets.GHES_APP_PRIVATE_KEY }} private-key: ${{ secrets.GHES_APP_PRIVATE_KEY }}
@@ -318,7 +318,7 @@ steps:
echo "private-key=$private_key" >> "$GITHUB_OUTPUT" echo "private-key=$private_key" >> "$GITHUB_OUTPUT"
- name: Generate GitHub App Token - name: Generate GitHub App Token
id: app-token id: app-token
uses: actions/create-github-app-token@v1 uses: actions/create-github-app-token@v2
with: with:
app-id: ${{ vars.APP_ID }} app-id: ${{ vars.APP_ID }}
private-key: ${{ steps.decode.outputs.private-key }} private-key: ${{ steps.decode.outputs.private-key }}
@@ -343,7 +343,7 @@ The reason we define one `permision-<permission name>` input per permission is t
### `skip-token-revoke` ### `skip-token-revoke`
**Optional:** If truthy, the token will not be revoked when the current job is complete. **Optional:** If true, the token will not be revoked when the current job is complete.
### `github-api-url` ### `github-api-url`
@@ -370,7 +370,7 @@ The action creates an installation access token using [the `POST /app/installati
1. The token is scoped to the current repository or `repositories` if set. 1. The token is scoped to the current repository or `repositories` if set.
2. The token inherits all the installation's permissions. 2. The token inherits all the installation's permissions.
3. The token is set as output `token` which can be used in subsequent steps. 3. The token is set as output `token` which can be used in subsequent steps.
4. Unless the `skip-token-revoke` input is set to a truthy value, the token is revoked in the `post` step of the action, which means it cannot be passed to another job. 4. Unless the `skip-token-revoke` input is set to true, the token is revoked in the `post` step of the action, which means it cannot be passed to another job.
5. The token is masked, it cannot be logged accidentally. 5. The token is masked, it cannot be logged accidentally.
> [!NOTE] > [!NOTE]

View File

@@ -18,8 +18,9 @@ inputs:
description: "Comma or newline-separated list of repositories to install the GitHub App on (defaults to current repository if owner is unset)" description: "Comma or newline-separated list of repositories to install the GitHub App on (defaults to current repository if owner is unset)"
required: false required: false
skip-token-revoke: skip-token-revoke:
description: "If truthy, the token will not be revoked when the current job is complete" description: "If true, the token will not be revoked when the current job is complete"
required: false required: false
default: "false"
# Make GitHub API configurable to support non-GitHub Cloud use cases # Make GitHub API configurable to support non-GitHub Cloud use cases
# see https://github.com/actions/create-github-app-token/issues/77 # see https://github.com/actions/create-github-app-token/issues/77
github-api-url: github-api-url:

8
dist/main.cjs vendored
View File

@@ -42394,8 +42394,9 @@ function createAppAuth(options) {
// lib/get-permissions-from-inputs.js // lib/get-permissions-from-inputs.js
function getPermissionsFromInputs(env) { function getPermissionsFromInputs(env) {
return Object.entries(env).reduce((permissions2, [key, value]) => { return Object.entries(env).reduce((permissions2, [key, value]) => {
if (!key.startsWith("INPUT_PERMISSION_")) return permissions2; if (!key.startsWith("INPUT_PERMISSION-")) return permissions2;
const permission = key.slice("INPUT_PERMISSION_".length).toLowerCase(); if (!value) return permissions2;
const permission = key.slice("INPUT_PERMISSION-".length).toLowerCase();
if (permissions2 === void 0) { if (permissions2 === void 0) {
return { [permission]: value }; return { [permission]: value };
} }
@@ -42568,6 +42569,7 @@ async function main(appId2, privateKey2, owner2, repositories2, permissions2, co
permissions2 permissions2
), ),
{ {
shouldRetry: (error) => error.status >= 500,
onFailedAttempt: (error) => { onFailedAttempt: (error) => {
core3.info( core3.info(
`Failed to create token for "${parsedRepositoryNames.join( `Failed to create token for "${parsedRepositoryNames.join(
@@ -42673,7 +42675,7 @@ var appId = import_core2.default.getInput("app-id");
var privateKey = import_core2.default.getInput("private-key"); var privateKey = import_core2.default.getInput("private-key");
var owner = import_core2.default.getInput("owner"); var owner = import_core2.default.getInput("owner");
var repositories = import_core2.default.getInput("repositories").split(/[\n,]+/).map((s) => s.trim()).filter((x) => x !== ""); var repositories = import_core2.default.getInput("repositories").split(/[\n,]+/).map((s) => s.trim()).filter((x) => x !== "");
var skipTokenRevoke = Boolean(import_core2.default.getInput("skip-token-revoke")); var skipTokenRevoke = import_core2.default.getBooleanInput("skip-token-revoke");
var permissions = getPermissionsFromInputs(process.env); var permissions = getPermissionsFromInputs(process.env);
var main_default = main( var main_default = main(
appId, appId,

2
dist/post.cjs vendored
View File

@@ -40308,7 +40308,7 @@ var import_core2 = __toESM(require_core(), 1);
// lib/post.js // lib/post.js
async function post(core3, request2) { async function post(core3, request2) {
const skipTokenRevoke = Boolean(core3.getInput("skip-token-revoke")); const skipTokenRevoke = core3.getBooleanInput("skip-token-revoke");
if (skipTokenRevoke) { if (skipTokenRevoke) {
core3.info("Token revocation was skipped"); core3.info("Token revocation was skipped");
return; return;

View File

@@ -7,9 +7,12 @@
*/ */
export function getPermissionsFromInputs(env) { export function getPermissionsFromInputs(env) {
return Object.entries(env).reduce((permissions, [key, value]) => { return Object.entries(env).reduce((permissions, [key, value]) => {
if (!key.startsWith("INPUT_PERMISSION_")) return permissions; if (!key.startsWith("INPUT_PERMISSION-")) return permissions;
if (!value) return permissions;
const permission = key.slice("INPUT_PERMISSION_".length).toLowerCase(); const permission = key.slice("INPUT_PERMISSION-".length).toLowerCase();
// Inherit app permissions if no permissions inputs are set
if (permissions === undefined) { if (permissions === undefined) {
return { [permission]: value }; return { [permission]: value };
} }

View File

@@ -89,6 +89,7 @@ export async function main(
permissions permissions
), ),
{ {
shouldRetry: (error) => error.status >= 500,
onFailedAttempt: (error) => { onFailedAttempt: (error) => {
core.info( core.info(
`Failed to create token for "${parsedRepositoryNames.join( `Failed to create token for "${parsedRepositoryNames.join(

View File

@@ -5,7 +5,7 @@
* @param {import("@octokit/request").request} request * @param {import("@octokit/request").request} request
*/ */
export async function post(core, request) { export async function post(core, request) {
const skipTokenRevoke = Boolean(core.getInput("skip-token-revoke")); const skipTokenRevoke = core.getBooleanInput("skip-token-revoke");
if (skipTokenRevoke) { if (skipTokenRevoke) {
core.info("Token revocation was skipped"); core.info("Token revocation was skipped");

View File

@@ -24,7 +24,7 @@ const repositories = core
.map((s) => s.trim()) .map((s) => s.trim())
.filter((x) => x !== ""); .filter((x) => x !== "");
const skipTokenRevoke = Boolean(core.getInput("skip-token-revoke")); const skipTokenRevoke = core.getBooleanInput("skip-token-revoke");
const permissions = getPermissionsFromInputs(process.env); const permissions = getPermissionsFromInputs(process.env);

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "create-github-app-token", "name": "create-github-app-token",
"version": "2.0.2", "version": "2.0.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "create-github-app-token", "name": "create-github-app-token",
"version": "2.0.2", "version": "2.0.4",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.11.1", "@actions/core": "^1.11.1",

View File

@@ -2,7 +2,7 @@
"name": "create-github-app-token", "name": "create-github-app-token",
"private": true, "private": true,
"type": "module", "type": "module",
"version": "2.0.2", "version": "2.0.4",
"description": "GitHub Action for creating a GitHub App Installation Access Token", "description": "GitHub Action for creating a GitHub App Installation Access Token",
"scripts": { "scripts": {
"build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --target=node20.0.0 --packages=bundle", "build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --target=node20.0.0 --packages=bundle",

View File

@@ -2,6 +2,6 @@ import { test } from "./main.js";
// Verify `main` successfully sets permissions // Verify `main` successfully sets permissions
await test(() => { await test(() => {
process.env.INPUT_PERMISSION_ISSUES = `write`; process.env["INPUT_PERMISSION-ISSUES"] = `write`;
process.env.INPUT_PERMISSION_PULL_REQUESTS = `read`; process.env["INPUT_PERMISSION-PULL-REQUESTS"] = `read`;
}); });

View File

@@ -8,6 +8,7 @@ export const DEFAULT_ENV = {
// inputs are set as environment variables with the prefix INPUT_ // inputs are set as environment variables with the prefix INPUT_
// https://docs.github.com/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs // https://docs.github.com/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
"INPUT_GITHUB-API-URL": "https://api.github.com", "INPUT_GITHUB-API-URL": "https://api.github.com",
"INPUT_SKIP-TOKEN-REVOKE": "false",
"INPUT_APP-ID": "123456", "INPUT_APP-ID": "123456",
// This key is invalidated. Its from https://github.com/octokit/auth-app.js/issues/465#issuecomment-1564998327. // This key is invalidated. Its from https://github.com/octokit/auth-app.js/issues/465#issuecomment-1564998327.
"INPUT_PRIVATE-KEY": `-----BEGIN RSA PRIVATE KEY----- "INPUT_PRIVATE-KEY": `-----BEGIN RSA PRIVATE KEY-----
@@ -37,6 +38,8 @@ so0tiQKBgGQXZaxaXhYUcxYHuCkQ3V4Vsj3ezlM92xXlP32SGFm3KgFhYy9kATxw
Cax1ytZzvlrKLQyQFVK1COs2rHt7W4cJ7op7C8zXfsigXCiejnS664oAuX8sQZID Cax1ytZzvlrKLQyQFVK1COs2rHt7W4cJ7op7C8zXfsigXCiejnS664oAuX8sQZID
x3WQZRiXlWejSMUAHuMwXrhGlltF3lw83+xAjnqsVp75kGS6OH61 x3WQZRiXlWejSMUAHuMwXrhGlltF3lw83+xAjnqsVp75kGS6OH61
-----END RSA PRIVATE KEY-----`, -----END RSA PRIVATE KEY-----`,
// The Actions runner sets all inputs to empty strings if not set.
"INPUT_PERMISSION-ADMINISTRATION": "",
}; };
export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) { export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
@@ -60,7 +63,7 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
const owner = env.INPUT_OWNER ?? env.GITHUB_REPOSITORY_OWNER; const owner = env.INPUT_OWNER ?? env.GITHUB_REPOSITORY_OWNER;
const currentRepoName = env.GITHUB_REPOSITORY.split("/")[1]; const currentRepoName = env.GITHUB_REPOSITORY.split("/")[1];
const repo = encodeURIComponent( const repo = encodeURIComponent(
(env.INPUT_REPOSITORIES ?? currentRepoName).split(",")[0], (env.INPUT_REPOSITORIES ?? currentRepoName).split(",")[0]
); );
mockPool mockPool
@@ -76,7 +79,7 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
.reply( .reply(
200, 200,
{ id: mockInstallationId, app_slug: mockAppSlug }, { id: mockInstallationId, app_slug: mockAppSlug },
{ headers: { "content-type": "application/json" } }, { headers: { "content-type": "application/json" } }
); );
// Mock installation access token request // Mock installation access token request
@@ -97,7 +100,7 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
.reply( .reply(
201, 201,
{ token: mockInstallationAccessToken, expires_at: mockExpiresAt }, { token: mockInstallationAccessToken, expires_at: mockExpiresAt },
{ headers: { "content-type": "application/json" } }, { headers: { "content-type": "application/json" } }
); );
// Run the callback // Run the callback

View File

@@ -7,6 +7,7 @@ process.env.STATE_token = "secret123";
// inputs are set as environment variables with the prefix INPUT_ // inputs are set as environment variables with the prefix INPUT_
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs // https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
process.env["INPUT_GITHUB-API-URL"] = "https://api.github.com"; process.env["INPUT_GITHUB-API-URL"] = "https://api.github.com";
process.env["INPUT_SKIP-TOKEN-REVOKE"] = "false";
// 1 hour in the future, not expired // 1 hour in the future, not expired
process.env.STATE_expiresAt = new Date( process.env.STATE_expiresAt = new Date(

View File

@@ -7,6 +7,10 @@ process.env.STATE_token = "secret123";
// 1 hour in the past, expired // 1 hour in the past, expired
process.env.STATE_expiresAt = new Date(Date.now() - 1000 * 60 * 60).toISOString(); process.env.STATE_expiresAt = new Date(Date.now() - 1000 * 60 * 60).toISOString();
// inputs are set as environment variables with the prefix INPUT_
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
process.env["INPUT_SKIP-TOKEN-REVOKE"] = "false";
const mockAgent = new MockAgent(); const mockAgent = new MockAgent();
setGlobalDispatcher(mockAgent); setGlobalDispatcher(mockAgent);

View File

@@ -7,6 +7,7 @@ process.env.STATE_token = "secret123";
// inputs are set as environment variables with the prefix INPUT_ // inputs are set as environment variables with the prefix INPUT_
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs // https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
process.env["INPUT_GITHUB-API-URL"] = "https://api.github.com"; process.env["INPUT_GITHUB-API-URL"] = "https://api.github.com";
process.env["INPUT_SKIP-TOKEN-REVOKE"] = "false";
// 1 hour in the future, not expired // 1 hour in the future, not expired
process.env.STATE_expiresAt = new Date(Date.now() + 1000 * 60 * 60).toISOString(); process.env.STATE_expiresAt = new Date(Date.now() + 1000 * 60 * 60).toISOString();

View File

@@ -2,4 +2,8 @@
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions
delete process.env.STATE_token; delete process.env.STATE_token;
// inputs are set as environment variables with the prefix INPUT_
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
process.env["INPUT_SKIP-TOKEN-REVOKE"] = "false";
await import("../post.js"); await import("../post.js");

View File

@@ -331,7 +331,7 @@ Generated by [AVA](https://avajs.dev).
--- REQUESTS ---␊ --- REQUESTS ---␊
GET /repos/actions/create-github-app-token/installation␊ GET /repos/actions/create-github-app-token/installation␊
POST /app/installations/123456/access_tokens␊ POST /app/installations/123456/access_tokens␊
{"repositories":["create-github-app-token"],"permissions":{"issues":"write","pull_requests":"read"}}` {"repositories":["create-github-app-token"],"permissions":{"issues":"write","pull-requests":"read"}}`
## post-revoke-token-fail-response.test.js ## post-revoke-token-fail-response.test.js

Binary file not shown.