Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a skip_token_revoke input for configuring token revocation #54

Merged
merged 5 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ jobs:
> [!NOTE]
> If `owner` is set and `repositories` is empty, access will be scoped to all repositories in the provided repository owner's installation. If `owner` and `repositories` are empty, access will be scoped to only the current repository.

### `skip_token_revoke`

**Optional:** If truthy, the token will not be revoked when the current job is complete.

## Outputs

### `token`
Expand All @@ -158,7 +162,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.
2. The token inherits all the installation's permissions.
3. The token is set as output `token` which can be used in subsequent steps.
4. 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 a truthy value, 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.

> [!NOTE]
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ inputs:
repositories:
description: "Repositories to install the GitHub App on (defaults to current repository if owner is unset)"
required: false
skip_token_revoke:
description: "If truthy, the token will not be revoked when the current job is complete"
required: false
outputs:
token:
description: "GitHub installation access token"
Expand Down
10 changes: 7 additions & 3 deletions dist/main.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10006,7 +10006,7 @@ var import_core = __toESM(require_core(), 1);
var import_auth_app = __toESM(require_dist_node12(), 1);

// lib/main.js
async function main(appId2, privateKey2, owner2, repositories2, core2, createAppAuth2, request2) {
async function main(appId2, privateKey2, owner2, repositories2, core2, createAppAuth2, request2, skipTokenRevoke2) {
let parsedOwner = "";
let parsedRepositoryNames = "";
if (!owner2 && !repositories2) {
Expand Down Expand Up @@ -10082,7 +10082,9 @@ async function main(appId2, privateKey2, owner2, repositories2, core2, createApp
}
core2.setSecret(authentication.token);
core2.setOutput("token", authentication.token);
core2.saveState("token", authentication.token);
if (!skipTokenRevoke2) {
core2.saveState("token", authentication.token);
}
}

// lib/request.js
Expand All @@ -10105,6 +10107,7 @@ var appId = import_core.default.getInput("app_id");
var privateKey = import_core.default.getInput("private_key");
var owner = import_core.default.getInput("owner");
var repositories = import_core.default.getInput("repositories");
var skipTokenRevoke = Boolean(import_core.default.getInput("skip_token_revoke"));
main(
appId,
privateKey,
Expand All @@ -10114,7 +10117,8 @@ main(
import_auth_app.createAppAuth,
request_default.defaults({
baseUrl: process.env["GITHUB_API_URL"]
})
}),
skipTokenRevoke
).catch((error) => {
console.error(error);
import_core.default.setFailed(error.message);
Expand Down
5 changes: 5 additions & 0 deletions dist/post.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2973,6 +2973,11 @@ var import_core = __toESM(require_core(), 1);

// lib/post.js
async function post(core2, request2) {
const skipTokenRevoke = Boolean(core2.getInput("skip_token_revoke"));
if (skipTokenRevoke) {
core2.info("Token revocation was skipped");
return;
}
const token = core2.getState("token");
if (!token) {
core2.info("Token is not set");
Expand Down
8 changes: 6 additions & 2 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @param {import("@actions/core")} core
* @param {import("@octokit/auth-app").createAppAuth} createAppAuth
* @param {import("@octokit/request").request} request
* @param {boolean} skipTokenRevoke
*/
export async function main(
appId,
Expand All @@ -16,7 +17,8 @@ export async function main(
repositories,
core,
createAppAuth,
request
request,
skipTokenRevoke
) {
let parsedOwner = "";
let parsedRepositoryNames = "";
Expand Down Expand Up @@ -122,5 +124,7 @@ export async function main(
core.setOutput("token", authentication.token);

// Make token accessible to post function (so we can invalidate it)
core.saveState("token", authentication.token);
if (!skipTokenRevoke) {
core.saveState("token", authentication.token);
}
}
7 changes: 7 additions & 0 deletions lib/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
* @param {import("@octokit/request").request} request
*/
export async function post(core, request) {
const skipTokenRevoke = Boolean(core.getInput("skip_token_revoke"));

if (skipTokenRevoke) {
core.info("Token revocation was skipped");
return;
}

const token = core.getState("token");

if (!token) {
Expand Down
5 changes: 4 additions & 1 deletion main.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const privateKey = core.getInput("private_key");
const owner = core.getInput("owner");
const repositories = core.getInput("repositories");

const skipTokenRevoke = Boolean(core.getInput("skip_token_revoke"));

main(
appId,
privateKey,
Expand All @@ -28,7 +30,8 @@ main(
createAppAuth,
request.defaults({
baseUrl: process.env["GITHUB_API_URL"],
})
}),
skipTokenRevoke
).catch((error) => {
console.error(error);
core.setFailed(error.message);
Expand Down
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Add one test file per scenario. You can run them in isolation with:
node tests/post-token-set.test.js
```

All tests are run together in [tests/index.js](index.js), which can be execauted with ava
All tests are run together in [tests/index.js](index.js), which can be executed with ava

```
npx ava tests/index.js
Expand Down
29 changes: 29 additions & 0 deletions tests/post-token-skipped.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { MockAgent, setGlobalDispatcher } from "undici";

// state variables are set as environment variables with the prefix STATE_
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions
process.env.STATE_token = "secret123";

// 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 = "true";

const mockAgent = new MockAgent();

setGlobalDispatcher(mockAgent);

// Provide the base url to the request
const mockPool = mockAgent.get("https://api.github.com");

// intercept the request
mockPool
.intercept({
path: "/installation/token",
method: "DELETE",
headers: {
authorization: "token secret123",
},
})
.reply(204);

await import("../post.js");
10 changes: 10 additions & 0 deletions tests/snapshots/index.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ Generated by [AVA](https://avajs.dev).

'Token revoked'

## post-token-skipped.test.js

> stderr

''

> stdout

'Token revocation was skipped'

## post-token-unset.test.js

> stderr
Expand Down
Binary file modified tests/snapshots/index.js.snap
Binary file not shown.