Compare commits

...

4 Commits

Author SHA1 Message Date
semantic-release-bot 86576b355d build(release): 1.7.0 [skip ci]
# [1.7.0](https://github.com/actions/create-github-app-token/compare/v1.6.4...v1.7.0) (2024-01-26)

### Features

* `github-api-url` ([#88](https://github.com/actions/create-github-app-token/issues/88)) ([837e275](https://github.com/actions/create-github-app-token/commit/837e2752e017897b136a438ea12a06c044b8414e)), closes [#77](https://github.com/actions/create-github-app-token/issues/77)
2024-01-26 18:51:44 +00:00
Gregor Martynus 837e2752e0 feat: github-api-url (#88)
closes #77

---------

Co-authored-by: Parker Brown <17183625+parkerbxyz@users.noreply.github.com>
2024-01-26 10:51:16 -08:00
semantic-release-bot c4fa18d55c build(release): 1.6.4 [skip ci]
## [1.6.4](https://github.com/actions/create-github-app-token/compare/v1.6.3...v1.6.4) (2024-01-19)

### Bug Fixes

* **revocation:** avoid revoking expired tokens and fail gracefully ([#95](https://github.com/actions/create-github-app-token/issues/95)) ([0c01407](https://github.com/actions/create-github-app-token/commit/0c014070f93045fed9b48f568f28b2f1cca37088)), closes [#72](https://github.com/actions/create-github-app-token/issues/72)
2024-01-19 15:45:52 +00:00
Josh Gross 0c014070f9 fix(revocation): avoid revoking expired tokens and fail gracefully (#95)
Fixes #72

If an Actions job is long enough, more than an hour can pass between
creating and revoking the App token in the post-job clean up step. Since
the token itself is used to authenticate with the revoke API, an expired
token will fail to be revoked.

This PR saves the token expiration in the actions state and uses that in
the post step to determine if the token can be revoked. I've also added
error handling to the revoke token API call, as it's unlikely that users
would want their job to fail if the token can't be revoked.
2024-01-19 07:45:12 -08:00
19 changed files with 262 additions and 76 deletions
+18 -5
View File
@@ -15,7 +15,11 @@ In order to use this action, you need to:
### Create a token for the current repository
```yaml
on: [issues]
name: Run tests on staging
on:
push:
branches:
- main
jobs:
hello-world:
@@ -26,11 +30,10 @@ jobs:
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- uses: peter-evans/create-or-update-comment@v3
github-api-url: "https://github.acme-inc.com/api/v3"
- uses: ./actions/staging-tests
with:
token: ${{ steps.app-token.outputs.token }}
issue-number: ${{ github.event.issue.number }}
body: "Hello, World!"
```
### Use app token with `actions/checkout`
@@ -146,7 +149,7 @@ jobs:
run: echo 'matrix=[{"owner":"owner1"},{"owner":"owner2","repos":["repo1"]}]' >>"$GITHUB_OUTPUT"
use-matrix:
name: '@${{ matrix.owners-and-repos.owner }} installation'
name: "@${{ matrix.owners-and-repos.owner }} installation"
needs: [set-matrix]
runs-on: ubuntu-latest
strategy:
@@ -172,6 +175,12 @@ jobs:
MULTILINE_JSON_STRING: ${{ steps.get-installation-repositories.outputs.data }}
```
### Run the workflow in a github.com repository against an organization in GitHub Enterprise Server
```yaml
on: [push]
```
## Inputs
### `app-id`
@@ -197,6 +206,10 @@ jobs:
**Optional:** If truthy, the token will not be revoked when the current job is complete.
### `github-api-url`
**Optional:** The URL of the GitHub REST API. Defaults to the URL of the GitHub Rest API where the workflow is run from.
## Outputs
### `token`
+5
View File
@@ -32,6 +32,11 @@ inputs:
description: "If truthy, the token will not be revoked when the current job is complete"
required: false
deprecationMessage: "'skip_token_revoke' is deprecated and will be removed in a future version. Use 'skip-token-revoke' instead."
# Make GitHub API configurable to support non-GitHub Cloud use cases
# see https://github.com/actions/create-github-app-token/issues/77
github-api-url:
description: The URL of the GitHub REST API.
default: ${{ github.api_url }}
outputs:
token:
description: "GitHub installation access token"
+7 -8
View File
@@ -3023,7 +3023,7 @@ var require_dist_node6 = __commonJS({
module2.exports = __toCommonJS2(dist_src_exports);
function oauthAuthorizationUrl(options) {
const clientType = options.clientType || "oauth-app";
const baseUrl = options.baseUrl || "https://github.com";
const baseUrl2 = options.baseUrl || "https://github.com";
const result = {
clientType,
allowSignup: options.allowSignup === false ? false : true,
@@ -3037,7 +3037,7 @@ var require_dist_node6 = __commonJS({
const scopes = "scopes" in options ? options.scopes : [];
result.scopes = typeof scopes === "string" ? scopes.split(/[,\s]+/).filter(Boolean) : scopes;
}
result.url = urlBuilderAuthorize(`${baseUrl}/login/oauth/authorize`, result);
result.url = urlBuilderAuthorize(`${baseUrl2}/login/oauth/authorize`, result);
return result;
}
function urlBuilderAuthorize(base, options) {
@@ -3149,10 +3149,10 @@ var require_dist_node7 = __commonJS({
request: request2 = import_request3.request,
...options
}) {
const baseUrl = requestToOAuthBaseUrl(request2);
const baseUrl2 = requestToOAuthBaseUrl(request2);
return (0, import_oauth_authorization_url.oauthAuthorizationUrl)({
...options,
baseUrl
baseUrl: baseUrl2
});
}
var import_request22 = require_dist_node5();
@@ -10420,6 +10420,7 @@ async function main(appId2, privateKey2, owner2, repositories2, core2, createApp
core2.setOutput("token", authentication.token);
if (!skipTokenRevoke2) {
core2.saveState("token", authentication.token);
core2.setOutput("expiresAt", authentication.expiresAt);
}
}
async function getTokenFromOwner(request2, auth, parsedOwner) {
@@ -10463,7 +10464,6 @@ async function getTokenFromRepository(request2, auth, parsedOwner, parsedReposit
// lib/request.js
var import_request = __toESM(require_dist_node5(), 1);
var request_default = import_request.request.defaults({
baseUrl: process.env["GITHUB_API_URL"],
headers: {
"user-agent": "actions/create-github-app-token"
}
@@ -10489,6 +10489,7 @@ var repositories = import_core.default.getInput("repositories");
var skipTokenRevoke = Boolean(
import_core.default.getInput("skip-token-revoke") || import_core.default.getInput("skip_token_revoke")
);
var baseUrl = import_core.default.getInput("github-api-url").replace(/\/$/, "");
main(
appId,
privateKey,
@@ -10496,9 +10497,7 @@ main(
repositories,
import_core.default,
import_auth_app.createAppAuth,
request_default.defaults({
baseUrl: process.env["GITHUB_API_URL"]
}),
request_default.defaults({ baseUrl }),
skipTokenRevoke
).catch((error) => {
console.error(error);
+24 -13
View File
@@ -3003,30 +3003,41 @@ async function post(core2, request2) {
core2.info("Token is not set");
return;
}
await request2("DELETE /installation/token", {
headers: {
authorization: `token ${token}`
}
});
core2.info("Token revoked");
const expiresAt = core2.getState("expiresAt");
if (expiresAt && tokenExpiresIn(expiresAt) < 0) {
core2.info("Token expired, skipping token revocation");
return;
}
try {
await request2("DELETE /installation/token", {
headers: {
authorization: `token ${token}`
}
});
core2.info("Token revoked");
} catch (error) {
core2.warning(
`Token revocation failed: ${error.message}`
);
}
}
function tokenExpiresIn(expiresAt) {
const now = /* @__PURE__ */ new Date();
const expiresAtDate = new Date(expiresAt);
return Math.round((expiresAtDate.getTime() - now.getTime()) / 1e3);
}
// lib/request.js
var import_request = __toESM(require_dist_node5(), 1);
var request_default = import_request.request.defaults({
baseUrl: process.env["GITHUB_API_URL"],
headers: {
"user-agent": "actions/create-github-app-token"
}
});
// post.js
post(
import_core.default,
request_default.defaults({
baseUrl: process.env["GITHUB_API_URL"]
})
).catch((error) => {
var baseUrl = import_core.default.getInput("github-api-url").replace(/\/$/, "");
post(import_core.default, request_default.defaults({ baseUrl })).catch((error) => {
console.error(error);
import_core.default.setFailed(error.message);
});
+1
View File
@@ -103,6 +103,7 @@ export async function main(
// Make token accessible to post function (so we can invalidate it)
if (!skipTokenRevoke) {
core.saveState("token", authentication.token);
core.setOutput("expiresAt", authentication.expiresAt);
}
}
+26 -6
View File
@@ -21,11 +21,31 @@ export async function post(core, request) {
return;
}
await request("DELETE /installation/token", {
headers: {
authorization: `token ${token}`,
},
});
const expiresAt = core.getState("expiresAt");
if (expiresAt && tokenExpiresIn(expiresAt) < 0) {
core.info("Token expired, skipping token revocation");
return;
}
core.info("Token revoked");
try {
await request("DELETE /installation/token", {
headers: {
authorization: `token ${token}`,
},
});
core.info("Token revoked");
} catch (error) {
core.warning(
`Token revocation failed: ${error.message}`)
}
}
/**
* @param {string} expiresAt
*/
function tokenExpiresIn(expiresAt) {
const now = new Date();
const expiresAtDate = new Date(expiresAt);
return Math.round((expiresAtDate.getTime() - now.getTime()) / 1000);
}
-1
View File
@@ -1,7 +1,6 @@
import { request } from "@octokit/request";
export default request.defaults({
baseUrl: process.env["GITHUB_API_URL"],
headers: {
"user-agent": "actions/create-github-app-token",
},
+3 -3
View File
@@ -31,6 +31,8 @@ const skipTokenRevoke = Boolean(
core.getInput("skip-token-revoke") || core.getInput("skip_token_revoke")
);
const baseUrl = core.getInput("github-api-url").replace(/\/$/, "");
main(
appId,
privateKey,
@@ -38,9 +40,7 @@ main(
repositories,
core,
createAppAuth,
request.defaults({
baseUrl: process.env["GITHUB_API_URL"],
}),
request.defaults({ baseUrl }),
skipTokenRevoke
).catch((error) => {
/* c8 ignore next 3 */
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "create-github-app-token",
"private": true,
"type": "module",
"version": "1.6.3",
"version": "1.7.0",
"description": "GitHub Action for creating a GitHub App Installation Access Token",
"scripts": {
"build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --target=node20.0.0",
+3 -6
View File
@@ -5,12 +5,9 @@ import core from "@actions/core";
import { post } from "./lib/post.js";
import request from "./lib/request.js";
post(
core,
request.defaults({
baseUrl: process.env["GITHUB_API_URL"],
})
).catch((error) => {
const baseUrl = core.getInput("github-api-url").replace(/\/$/, "");
post(core, request.defaults({ baseUrl })).catch((error) => {
/* c8 ignore next 3 */
console.error(error);
core.setFailed(error.message);
+13
View File
@@ -0,0 +1,13 @@
import { test, DEFAULT_ENV } from "./main.js";
// Verify that main works with a custom GitHub API URL passed as `github-api-url` input
await test(
() => {
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
process.env.INPUT_REPOSITORIES = process.env.GITHUB_REPOSITORY;
},
{
...DEFAULT_ENV,
"INPUT_GITHUB-API-URL": "https://github.acme-inc.com/api/v3",
}
);
@@ -2,10 +2,10 @@ import { test } from "./main.js";
// Verify `main` retry when the GitHub API returns a 500 error.
await test((mockPool) => {
process.env.INPUT_OWNER = 'actions'
process.env.INPUT_REPOSITORIES = 'failed-repo';
const owner = process.env.INPUT_OWNER
const repo = process.env.INPUT_REPOSITORIES
process.env.INPUT_OWNER = "actions";
process.env.INPUT_REPOSITORIES = "failed-repo";
const owner = process.env.INPUT_OWNER;
const repo = process.env.INPUT_REPOSITORIES;
const mockInstallationId = "123456";
mockPool
@@ -18,9 +18,9 @@ await test((mockPool) => {
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(500, 'GitHub API not available')
mockPool
.reply(500, "GitHub API not available");
mockPool
.intercept({
path: `/repos/${owner}/${repo}/installation`,
method: "GET",
@@ -35,5 +35,4 @@ await test((mockPool) => {
{ id: mockInstallationId },
{ headers: { "content-type": "application/json" } }
);
});
@@ -17,8 +17,8 @@ await test((mockPool) => {
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(500, 'GitHub API not available')
mockPool
.reply(500, "GitHub API not available");
mockPool
.intercept({
path: `/orgs/${process.env.INPUT_OWNER}/installation`,
method: "GET",
+23 -15
View File
@@ -2,14 +2,15 @@
// @ts-check
import { MockAgent, setGlobalDispatcher } from "undici";
export async function test(cb = (_mockPool) => {}) {
// Set required environment variables and inputs
process.env.GITHUB_REPOSITORY_OWNER = "actions";
process.env.GITHUB_REPOSITORY = "actions/create-github-app-token";
export const DEFAULT_ENV = {
GITHUB_REPOSITORY_OWNER: "actions",
GITHUB_REPOSITORY: "actions/create-github-app-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_APP-ID"] = "123456";
process.env["INPUT_PRIVATE-KEY"] = `-----BEGIN RSA PRIVATE KEY-----
"INPUT_GITHUB-API-URL": "https://api.github.com",
"INPUT_APP-ID": "123456",
// This key is invalidated. Its from https://github.com/octokit/auth-app.js/issues/465#issuecomment-1564998327.
"INPUT_PRIVATE-KEY": `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA280nfuUM9w00Ib9E2rvZJ6Qu3Ua3IqR34ZlK53vn/Iobn2EL
Z9puc5Q/nFBU15NKwHyQNb+OG2hTCkjd1Xi9XPzEOH1r42YQmTGq8YCkUSkk6KZA
5dnhLwN9pFquT9fQgrf4r1D5GJj3rqvj8JDr1sBmunArqY5u4gziSrIohcjLIZV0
@@ -35,27 +36,33 @@ r4J2gqb0xTDfq7gLMNrIXc2QQM4gKbnJp60JQM3p9NmH8huavBZGvSvNzTwXyGG3
so0tiQKBgGQXZaxaXhYUcxYHuCkQ3V4Vsj3ezlM92xXlP32SGFm3KgFhYy9kATxw
Cax1ytZzvlrKLQyQFVK1COs2rHt7W4cJ7op7C8zXfsigXCiejnS664oAuX8sQZID
x3WQZRiXlWejSMUAHuMwXrhGlltF3lw83+xAjnqsVp75kGS6OH61
-----END RSA PRIVATE KEY-----`; // This key is invalidated. Its from https://github.com/octokit/auth-app.js/issues/465#issuecomment-1564998327.
-----END RSA PRIVATE KEY-----`,
};
export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
for (const [key, value] of Object.entries(env)) {
process.env[key] = value;
}
// Set up mocking
const baseUrl = new URL(env["INPUT_GITHUB-API-URL"]);
const basePath = baseUrl.pathname === '/' ? '' : baseUrl.pathname;
const mockAgent = new MockAgent();
mockAgent.disableNetConnect();
setGlobalDispatcher(mockAgent);
const mockPool = mockAgent.get("https://api.github.com");
const mockPool = mockAgent.get(baseUrl.origin);
// Calling `auth({ type: "app" })` to obtain a JWT doesnt make network requests, so no need to intercept.
// Mock installation id request
const mockInstallationId = "123456";
const owner = process.env.INPUT_OWNER ?? process.env.GITHUB_REPOSITORY_OWNER;
const owner = env.INPUT_OWNER ?? env.GITHUB_REPOSITORY_OWNER;
const repo = encodeURIComponent(
(process.env.INPUT_REPOSITORIES ?? process.env.GITHUB_REPOSITORY).split(
","
)[0]
(env.INPUT_REPOSITORIES ?? env.GITHUB_REPOSITORY).split(",")[0]
);
mockPool
.intercept({
path: `/repos/${owner}/${repo}/installation`,
path: `${basePath}/repos/${owner}/${repo}/installation`,
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
@@ -72,9 +79,10 @@ x3WQZRiXlWejSMUAHuMwXrhGlltF3lw83+xAjnqsVp75kGS6OH61
// Mock installation access token request
const mockInstallationAccessToken =
"ghs_16C7e42F292c6912E7710c838347Ae178B4a"; // This token is invalidated. Its from https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-an-installation-access-token-for-an-app.
const mockExpiresAt = "2016-07-11T22:14:10Z";
mockPool
.intercept({
path: `/app/installations/${mockInstallationId}/access_tokens`,
path: `${basePath}/app/installations/${mockInstallationId}/access_tokens`,
method: "POST",
headers: {
accept: "application/vnd.github.v3+json",
@@ -84,7 +92,7 @@ x3WQZRiXlWejSMUAHuMwXrhGlltF3lw83+xAjnqsVp75kGS6OH61
})
.reply(
201,
{ token: mockInstallationAccessToken },
{ token: mockInstallationAccessToken, expires_at: mockExpiresAt },
{ headers: { "content-type": "application/json" } }
);
@@ -0,0 +1,34 @@
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_GITHUB-API-URL"] = "https://api.github.com";
// 1 hour in the future, not expired
process.env.STATE_expiresAt = new Date(
Date.now() + 1000 * 60 * 60
).toISOString();
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(401);
await import("../post.js");
+28
View File
@@ -0,0 +1,28 @@
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";
// 1 hour in the past, expired
process.env.STATE_expiresAt = new Date(Date.now() - 1000 * 60 * 60).toISOString();
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");
+7
View File
@@ -4,6 +4,13 @@ import { MockAgent, setGlobalDispatcher } from "undici";
// 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_GITHUB-API-URL"] = "https://api.github.com";
// 1 hour in the future, not expired
process.env.STATE_expiresAt = new Date(Date.now() + 1000 * 60 * 60).toISOString();
const mockAgent = new MockAgent();
setGlobalDispatcher(mockAgent);
+60 -8
View File
@@ -16,6 +16,22 @@ Generated by [AVA](https://avajs.dev).
private_key — 'private_key' is deprecated and will be removed in a future version. Use 'private-key' instead.␊
skip_token_revoke — 'skip_token_revoke' is deprecated and will be removed in a future version. Use 'skip-token-revoke' instead.`
## main-custom-github-api-url.test.js
> stderr
''
> stdout
`owner and repositories set, creating token for repositories "actions/create-github-app-token" owned by "actions"␊
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=expiresAt::2016-07-11T22:14:10Z`
## main-missing-app-id.test.js
> stderr
@@ -69,7 +85,9 @@ Generated by [AVA](https://avajs.dev).
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=expiresAt::2016-07-11T22:14:10Z`
## main-token-get-owner-set-repo-set-to-many.test.js
@@ -83,7 +101,9 @@ Generated by [AVA](https://avajs.dev).
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=expiresAt::2016-07-11T22:14:10Z`
## main-token-get-owner-set-repo-set-to-one.test.js
@@ -97,7 +117,9 @@ Generated by [AVA](https://avajs.dev).
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=expiresAt::2016-07-11T22:14:10Z`
## main-token-get-owner-set-to-org-repo-unset.test.js
@@ -111,7 +133,9 @@ Generated by [AVA](https://avajs.dev).
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=expiresAt::2016-07-11T22:14:10Z`
## main-token-get-owner-set-to-user-fail-response.test.js
@@ -126,7 +150,9 @@ Generated by [AVA](https://avajs.dev).
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=expiresAt::2016-07-11T22:14:10Z`
## main-token-get-owner-set-to-user-repo-unset.test.js
@@ -140,7 +166,9 @@ Generated by [AVA](https://avajs.dev).
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=expiresAt::2016-07-11T22:14:10Z`
## main-token-get-owner-unset-repo-set.test.js
@@ -154,7 +182,9 @@ Generated by [AVA](https://avajs.dev).
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=expiresAt::2016-07-11T22:14:10Z`
## main-token-get-owner-unset-repo-unset.test.js
@@ -168,7 +198,29 @@ Generated by [AVA](https://avajs.dev).
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=expiresAt::2016-07-11T22:14:10Z`
## post-revoke-token-fail-response.test.js
> stderr
''
> stdout
'::warning::Token revocation failed: '
## post-token-expired.test.js
> stderr
''
> stdout
'Token expired, skipping token revocation'
## post-token-set.test.js
Binary file not shown.