From 8746053070f66e90995d88d07ed1d039d23e603f Mon Sep 17 00:00:00 2001 From: Steve Russo Date: Tue, 5 Dec 2023 12:16:54 -0500 Subject: [PATCH 1/3] docs(README): fix use of deprecated arguments (#86) Using those fields that have an underscore (instead of a hyphen) cause the following warnings if used: ``` Warning: Input 'app_id' has been deprecated with message: 'app_id' is deprecated and will be removed in a future version. Use 'app-id' instead. Warning: Input 'private_key' has been deprecated with message: 'private_key' is deprecated and will be removed in a future version. Use 'private-key' instead. ``` So this PR just drops the last use of `app_id` and `private_key` from the README in favor of `app-id` and `private-key`. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1f122ed..0e3bc2b 100644 --- a/README.md +++ b/README.md @@ -157,8 +157,8 @@ jobs: - uses: actions/create-github-app-token@v1 id: app-token with: - app_id: ${{ vars.APP_ID }} - private_key: ${{ secrets.PRIVATE_KEY }} + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.PRIVATE_KEY }} owner: ${{ matrix.owners-and-repos.owner }} repositories: ${{ join(matrix.owners-and-repos.repos) }} - uses: octokit/request-action@v2.x From 495056a51509f267cd7262080a7bb618ad7b5d08 Mon Sep 17 00:00:00 2001 From: Bo Anderson Date: Wed, 6 Dec 2023 20:25:27 +0000 Subject: [PATCH 2/3] fix: handle clock skew (#87) GitHub's macOS runners for the past while have had some bad clock drift which sometimes prevents this action from working with the error: ```console 'Issued at' claim ('iat') must be an Integer representing the time that the assertion was issued ``` `@octokit/auth-app` already has logic to handle this so we can defer to that code. --- dist/main.cjs | 23 ++++++++--------- lib/main.js | 24 ++++++++---------- package-lock.json | 32 +++++++++++++++++++++-- package.json | 1 + tests/main-repo-skew.js | 56 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 29 deletions(-) create mode 100644 tests/main-repo-skew.js diff --git a/dist/main.cjs b/dist/main.cjs index 266e78e..0d6d1a4 100644 --- a/dist/main.cjs +++ b/dist/main.cjs @@ -10390,12 +10390,9 @@ async function main(appId2, privateKey2, owner2, repositories2, core2, createApp privateKey: privateKey2, request: request2 }); - const appAuthentication = await auth({ - type: "app" - }); let authentication; if (parsedRepositoryNames) { - authentication = await pRetry(() => getTokenFromRepository(request2, auth, parsedOwner, appAuthentication, parsedRepositoryNames), { + authentication = await pRetry(() => getTokenFromRepository(request2, auth, parsedOwner, parsedRepositoryNames), { onFailedAttempt: (error) => { core2.info( `Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}` @@ -10404,7 +10401,7 @@ async function main(appId2, privateKey2, owner2, repositories2, core2, createApp retries: 3 }); } else { - authentication = await pRetry(() => getTokenFromOwner(request2, auth, appAuthentication, parsedOwner), { + authentication = await pRetry(() => getTokenFromOwner(request2, auth, parsedOwner), { onFailedAttempt: (error) => { core2.info( `Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}` @@ -10419,19 +10416,19 @@ async function main(appId2, privateKey2, owner2, repositories2, core2, createApp core2.saveState("token", authentication.token); } } -async function getTokenFromOwner(request2, auth, appAuthentication, parsedOwner) { +async function getTokenFromOwner(request2, auth, parsedOwner) { const response = await request2("GET /orgs/{org}/installation", { org: parsedOwner, - headers: { - authorization: `bearer ${appAuthentication.token}` + request: { + hook: auth.hook } }).catch((error) => { if (error.status !== 404) throw error; return request2("GET /users/{username}/installation", { username: parsedOwner, - headers: { - authorization: `bearer ${appAuthentication.token}` + request: { + hook: auth.hook } }); }); @@ -10441,12 +10438,12 @@ async function getTokenFromOwner(request2, auth, appAuthentication, parsedOwner) }); return authentication; } -async function getTokenFromRepository(request2, auth, parsedOwner, appAuthentication, parsedRepositoryNames) { +async function getTokenFromRepository(request2, auth, parsedOwner, parsedRepositoryNames) { const response = await request2("GET /repos/{owner}/{repo}/installation", { owner: parsedOwner, repo: parsedRepositoryNames.split(",")[0], - headers: { - authorization: `bearer ${appAuthentication.token}` + request: { + hook: auth.hook } }); const authentication = await auth({ diff --git a/lib/main.js b/lib/main.js index 9dfe730..233be3d 100644 --- a/lib/main.js +++ b/lib/main.js @@ -70,15 +70,11 @@ export async function main( request, }); - const appAuthentication = await auth({ - type: "app", - }); - let authentication; // If at least one repository is set, get installation ID from that repository if (parsedRepositoryNames) { - authentication = await pRetry(() => getTokenFromRepository(request, auth, parsedOwner,appAuthentication, parsedRepositoryNames), { + authentication = await pRetry(() => getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames), { onFailedAttempt: (error) => { core.info( `Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}` @@ -89,7 +85,7 @@ export async function main( } else { // Otherwise get the installation for the owner, which can either be an organization or a user account - authentication = await pRetry(() => getTokenFromOwner(request, auth, appAuthentication, parsedOwner), { + authentication = await pRetry(() => getTokenFromOwner(request, auth, parsedOwner), { onFailedAttempt: (error) => { core.info( `Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}` @@ -110,12 +106,12 @@ export async function main( } } -async function getTokenFromOwner(request, auth, appAuthentication, parsedOwner) { +async function getTokenFromOwner(request, auth, parsedOwner) { // https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-an-organization-installation-for-the-authenticated-app const response = await request("GET /orgs/{org}/installation", { org: parsedOwner, - headers: { - authorization: `bearer ${appAuthentication.token}`, + request: { + hook: auth.hook, }, }).catch((error) => { /* c8 ignore next */ @@ -124,8 +120,8 @@ async function getTokenFromOwner(request, auth, appAuthentication, parsedOwner) // https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-user-installation-for-the-authenticated-app return request("GET /users/{username}/installation", { username: parsedOwner, - headers: { - authorization: `bearer ${appAuthentication.token}`, + request: { + hook: auth.hook, }, }); }); @@ -138,13 +134,13 @@ async function getTokenFromOwner(request, auth, appAuthentication, parsedOwner) return authentication; } -async function getTokenFromRepository(request, auth, parsedOwner,appAuthentication, parsedRepositoryNames) { +async function getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames) { // 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", { owner: parsedOwner, repo: parsedRepositoryNames.split(",")[0], - headers: { - authorization: `bearer ${appAuthentication.token}`, + request: { + hook: auth.hook, }, }); diff --git a/package-lock.json b/package-lock.json index 2ef6e47..664e29d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "create-github-app-token", - "version": "1.6.0", + "version": "1.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-github-app-token", - "version": "1.6.0", + "version": "1.6.1", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", @@ -15,6 +15,7 @@ "p-retry": "^6.1.0" }, "devDependencies": { + "@sinonjs/fake-timers": "^11.2.2", "ava": "^5.3.1", "c8": "^8.0.1", "dotenv": "^16.3.1", @@ -811,6 +812,24 @@ "@octokit/openapi-types": "^19.0.0" } }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -4089,6 +4108,15 @@ "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", diff --git a/package.json b/package.json index e3b0b3f..b6459aa 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "p-retry": "^6.1.0" }, "devDependencies": { + "@sinonjs/fake-timers": "^11.2.2", "ava": "^5.3.1", "c8": "^8.0.1", "dotenv": "^16.3.1", diff --git a/tests/main-repo-skew.js b/tests/main-repo-skew.js new file mode 100644 index 0000000..a3554ad --- /dev/null +++ b/tests/main-repo-skew.js @@ -0,0 +1,56 @@ +import { test } from "./main.js"; + +import { install } from "@sinonjs/fake-timers"; + +// Verify `main` retry when the clock has drifted. +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 + const mockInstallationId = "123456"; + + install({ now: 0, toFake: ["Date"] }); + + mockPool + .intercept({ + path: `/repos/${owner}/${repo}/installation`, + method: "GET", + headers: { + accept: "application/vnd.github.v3+json", + "user-agent": "actions/create-github-app-token", + // Intentionally omitting the `authorization` header, since JWT creation is not idempotent. + }, + }) + .reply(({ headers }) => { + const [_, jwt] = (headers.authorization || "").split(" "); + const payload = JSON.parse(Buffer.from(jwt.split(".")[1], "base64").toString()); + + if (payload.iat < 0) { + return { + statusCode: 401, + data: { + message: "'Issued at' claim ('iat') must be an Integer representing the time that the assertion was issued." + }, + responseOptions: { + headers: { + "content-type": "application/json", + "date": new Date(Date.now() + 30000).toUTCString() + } + } + }; + } + + return { + statusCode: 200, + data: { + id: mockInstallationId + }, + responseOptions: { + headers: { + "content-type": "application/json" + } + } + }; + }).times(2); +}); From 2986852ad836768dfea7781f31828eb3e17990fa Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 6 Dec 2023 20:25:58 +0000 Subject: [PATCH 3/3] build(release): 1.6.2 [skip ci] ## [1.6.2](https://github.com/actions/create-github-app-token/compare/v1.6.1...v1.6.2) (2023-12-06) ### Bug Fixes * handle clock skew ([#87](https://github.com/actions/create-github-app-token/issues/87)) ([495056a](https://github.com/actions/create-github-app-token/commit/495056a51509f267cd7262080a7bb618ad7b5d08)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b6459aa..4b9706a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "create-github-app-token", "private": true, "type": "module", - "version": "1.6.1", + "version": "1.6.2", "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",