Compare commits

...

3 Commits

Author SHA1 Message Date
semantic-release-bot 2986852ad8 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))
2023-12-06 20:25:58 +00:00
Bo Anderson 495056a515 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.
2023-12-06 12:25:27 -08:00
Steve Russo 8746053070 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`.
2023-12-05 09:16:54 -08:00
6 changed files with 110 additions and 32 deletions
+2 -2
View File
@@ -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
+10 -13
View File
@@ -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({
+10 -14
View File
@@ -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,
},
});
+30 -2
View File
@@ -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",
+2 -1
View File
@@ -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",
@@ -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",
+56
View File
@@ -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);
});