From fac362228e7417cea1a2481e6762145e942cc03f Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 20 May 2024 17:01:05 -0400 Subject: [PATCH 1/7] feat: swap out smee with homegrow proxy client --- README.md | 7 +- package-lock.json | 390 ++++++++++++++++++++++++++++++++++++++ package.json | 4 +- scripts/webhook-relay.mjs | 94 +++++++++ 4 files changed, 488 insertions(+), 7 deletions(-) create mode 100644 scripts/webhook-relay.mjs diff --git a/README.md b/README.md index 4776eb6..976954c 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,6 @@ Configuration is contained in a `.env` file which you'll need to customize for y - `PUBLIC_ORG` and `PRIVATE_ORG` environment variables if you want to keep your private mirrors in a different GitHub organization from the public forks - `ALLOWED_HANDLES` variable to a comma-separated list of GitHub user handles which ought to be allowed to access the app to create mirrors. If unset, all users who are members of the organization will be allowed to use the app. -- use https://smee.io or your own infrastructure to make the docker service available to receive webhooks ```sh docker build -t internal-contribution-forks . @@ -147,11 +146,7 @@ You should be up and running on [http://localhost:3000](http://localhost:3000)! Webhooks are an important part of this application, they listen for events that happen to your organization and trigger the app to do things like create branch protections or sync code between forks. -You can use [smee.io](https://smee.io) to test webhooks locally. [ngrok](https://ngrok.com/) is another option. - -For smee: Go to [smee.io](https://smee.io/new), this will create a new URL for you to use. e.g. `https://smee.io/AbCd1234EfGh5678`. - -Copy the URL and paste it into the `WEBHOOK_PROXY_URL` environment variable in `.env`. +We have our own webhook proxy that you can use to test webhooks locally. You will need to set `PUBLIC_ORG` (and `PRIVATE_ORG` if you want to test with a different organization) in your `.env` file to your GitHub organization name. ### GitHub Requirements diff --git a/package-lock.json b/package-lock.json index f198ea1..9857ab3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "dotenv-load": "3.0.0", "eslint": "8.57.0", "eslint-config-next": "14.2.1", + "github-app-webhook-relay-polling": "^1.1.0", "husky": "9.0.11", "jest": "29.7.0", "lint-staged": "15.2.2", @@ -2188,6 +2189,12 @@ "node": ">= 18" } }, + "node_modules/@octokit/tsconfig": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@octokit/tsconfig/-/tsconfig-1.0.2.tgz", + "integrity": "sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==", + "dev": true + }, "node_modules/@octokit/types": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", @@ -2218,6 +2225,12 @@ "node": ">= 18" } }, + "node_modules/@octokit/webhooks-types": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-6.11.0.tgz", + "integrity": "sha512-AanzbulOHljrku1NGfafxdpTCfw2ENaWzH01N2vqQM+cUFbk868Cgh0xylz0JIM9BoKbfI++bdD6EYX0Q/UTEw==", + "dev": true + }, "node_modules/@octokit/webhooks/node_modules/@octokit/webhooks-types": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.3.2.tgz", @@ -5735,6 +5748,26 @@ "node": ">= 0.6" } }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5885,6 +5918,312 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-app-webhook-relay-polling": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/github-app-webhook-relay-polling/-/github-app-webhook-relay-polling-1.1.0.tgz", + "integrity": "sha512-ty2rKVIw5Jic0B8yUsI4jOjLrzxJY6iH/UKnceIh43sI+b0/KY1LuDfe7VgDXdNuxhoam+hn0SgIWmk3ppq89Q==", + "dev": true, + "dependencies": { + "@octokit/app": "^13.1.0", + "@octokit/webhooks-types": "^6.7.0" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/app": { + "version": "13.1.8", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-13.1.8.tgz", + "integrity": "sha512-bCncePMguVyFpdBbnceFKfmPOuUD94T189GuQ0l00ZcQ+mX4hyPqnaWJlsXE2HSdA71eV7p8GPDZ+ErplTkzow==", + "dev": true, + "dependencies": { + "@octokit/auth-app": "^4.0.13", + "@octokit/auth-unauthenticated": "^3.0.0", + "@octokit/core": "^4.0.0", + "@octokit/oauth-app": "^4.0.7", + "@octokit/plugin-paginate-rest": "^6.0.0", + "@octokit/types": "^9.0.0", + "@octokit/webhooks": "^10.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-app": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-4.0.13.tgz", + "integrity": "sha512-NBQkmR/Zsc+8fWcVIFrwDgNXS7f4XDrkd9LHdi9DPQw1NdGHLviLzRO2ZBwTtepnwHXW5VTrVU9eFGijMUqllg==", + "dev": true, + "dependencies": { + "@octokit/auth-oauth-app": "^5.0.0", + "@octokit/auth-oauth-user": "^2.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "deprecation": "^2.3.1", + "lru-cache": "^9.0.0", + "universal-github-app-jwt": "^1.1.1", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-oauth-app": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-5.0.6.tgz", + "integrity": "sha512-SxyfIBfeFcWd9Z/m1xa4LENTQ3l1y6Nrg31k2Dcb1jS5ov7pmwMJZ6OGX8q3K9slRgVpeAjNA1ipOAMHkieqyw==", + "dev": true, + "dependencies": { + "@octokit/auth-oauth-device": "^4.0.0", + "@octokit/auth-oauth-user": "^2.0.0", + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "@types/btoa-lite": "^1.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-oauth-device": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-4.0.5.tgz", + "integrity": "sha512-XyhoWRTzf2ZX0aZ52a6Ew5S5VBAfwwx1QnC2Np6Et3MWQpZjlREIcbcvVZtkNuXp6Z9EeiSLSDUqm3C+aMEHzQ==", + "dev": true, + "dependencies": { + "@octokit/oauth-methods": "^2.0.0", + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-oauth-user": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-2.1.2.tgz", + "integrity": "sha512-kkRqNmFe7s5GQcojE3nSlF+AzYPpPv7kvP/xYEnE57584pixaFBH8Vovt+w5Y3E4zWUEOxjdLItmBTFAWECPAg==", + "dev": true, + "dependencies": { + "@octokit/auth-oauth-device": "^4.0.0", + "@octokit/oauth-methods": "^2.0.0", + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-token": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.4.tgz", + "integrity": "sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-unauthenticated": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-3.0.5.tgz", + "integrity": "sha512-yH2GPFcjrTvDWPwJWWCh0tPPtTL5SMgivgKPA+6v/XmYN6hGQkAto8JtZibSKOpf8ipmeYhLNWQ2UgW0GYILCw==", + "dev": true, + "dependencies": { + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/core": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.4.tgz", + "integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==", + "dev": true, + "dependencies": { + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/endpoint": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.6.tgz", + "integrity": "sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==", + "dev": true, + "dependencies": { + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/graphql": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.6.tgz", + "integrity": "sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==", + "dev": true, + "dependencies": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/oauth-app": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-4.2.4.tgz", + "integrity": "sha512-iuOVFrmm5ZKNavRtYu5bZTtmlKLc5uVgpqTfMEqYYf2OkieV6VdxKZAb5qLVdEPL8LU2lMWcGpavPBV835cgoA==", + "dev": true, + "dependencies": { + "@octokit/auth-oauth-app": "^5.0.0", + "@octokit/auth-oauth-user": "^2.0.0", + "@octokit/auth-unauthenticated": "^3.0.0", + "@octokit/core": "^4.0.0", + "@octokit/oauth-authorization-url": "^5.0.0", + "@octokit/oauth-methods": "^2.0.0", + "@types/aws-lambda": "^8.10.83", + "fromentries": "^1.3.1", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/oauth-authorization-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-5.0.0.tgz", + "integrity": "sha512-y1WhN+ERDZTh0qZ4SR+zotgsQUE1ysKnvBt1hvDRB2WRzYtVKQjn97HEPzoehh66Fj9LwNdlZh+p6TJatT0zzg==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/oauth-methods": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-2.0.6.tgz", + "integrity": "sha512-l9Uml2iGN2aTWLZcm8hV+neBiFXAQ9+3sKiQe/sgumHlL6HDg0AQ8/l16xX/5jJvfxueqTW5CWbzd0MjnlfHZw==", + "dev": true, + "dependencies": { + "@octokit/oauth-authorization-url": "^5.0.0", + "@octokit/request": "^6.2.3", + "@octokit/request-error": "^3.0.3", + "@octokit/types": "^9.0.0", + "btoa-lite": "^1.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/openapi-types": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz", + "integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==", + "dev": true + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/plugin-paginate-rest": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz", + "integrity": "sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==", + "dev": true, + "dependencies": { + "@octokit/tsconfig": "^1.0.2", + "@octokit/types": "^9.2.3" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=4" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/request": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.8.tgz", + "integrity": "sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/request-error": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz", + "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==", + "dev": true, + "dependencies": { + "@octokit/types": "^9.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/types": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.3.2.tgz", + "integrity": "sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==", + "dev": true, + "dependencies": { + "@octokit/openapi-types": "^18.0.0" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/webhooks": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-10.9.2.tgz", + "integrity": "sha512-hFVF/szz4l/Y/GQdKxNmQjUke0XJXK986p+ucIlubTGVPVtVtup5G1jarQfvCMBs9Fvlf9dvH8K83E4lefmofQ==", + "dev": true, + "dependencies": { + "@octokit/request-error": "^3.0.0", + "@octokit/webhooks-methods": "^3.0.0", + "@octokit/webhooks-types": "6.11.0", + "aggregate-error": "^3.1.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/webhooks-methods": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-3.0.3.tgz", + "integrity": "sha512-2vM+DCNTJ5vL62O5LagMru6XnYhV4fJslK+5YUkTa6rWlW2S+Tqs1lF9Wr9OGqHfVwpBj3TeztWfVON/eUoW1Q==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-app-webhook-relay-polling/node_modules/lru-cache": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", + "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -6593,6 +6932,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -9418,6 +9766,26 @@ "node": ">= 18" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -11666,6 +12034,12 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -12313,6 +12687,22 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index f9dc2eb..38c8b05 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "license": "MIT", "homepage": "https://github.com/github-community-projects/internal-contribution-forks", "scripts": { - "dev": "concurrently \"dotenv-load node scripts/probot-forwarder.js\" \"next dev\"", + "dev": "concurrently -c \"auto\" -n webhooks,app \"dotenv-load node scripts/webhook-relay.mjs\" \"next dev\"", + "webhook": "dotenv-load node scripts/webhook-relay.mjs", "build": "SKIP_ENV_VALIDATIONS='true' next build", "start": "next start", "lint": "SKIP_ENV_VALIDATIONS='true' next lint && prettier --check .", @@ -53,6 +54,7 @@ "dotenv-load": "3.0.0", "eslint": "8.57.0", "eslint-config-next": "14.2.1", + "github-app-webhook-relay-polling": "^1.1.0", "husky": "9.0.11", "jest": "29.7.0", "lint-staged": "15.2.2", diff --git a/scripts/webhook-relay.mjs b/scripts/webhook-relay.mjs new file mode 100644 index 0000000..3eed802 --- /dev/null +++ b/scripts/webhook-relay.mjs @@ -0,0 +1,94 @@ +import { sign } from '@octokit/webhooks-methods' +import WebhookRelay from 'github-app-webhook-relay-polling' +import crypto from 'node:crypto' +import { App } from 'octokit' + +if (!process.env.PUBLIC_ORG) { + console.error( + 'Missing PUBLIC_ORG environment variable. This is required for the webhook relay to work locally.', + ) + process.exit(1) +} + +const url = `${process.env.NEXTAUTH_URL}/api/webhooks` + +const privateKeyPkcs8 = crypto + .createPrivateKey(process.env.PRIVATE_KEY.replace(/\\n/g, '\n')) + .export({ + type: 'pkcs8', + format: 'pem', + }) + +const setupForwarder = (organizationOwner) => { + const app = new App({ + appId: process.env.APP_ID, + privateKey: privateKeyPkcs8, + webhooks: { + // value does not matter, but has to be set. + secret: 'secret', + }, + }) + + const relay = new WebhookRelay({ + owner: organizationOwner, + events: ['*'], + app, + }) + + relay.on('start', () => { + console.log('Webhook forwarder ready') + console.log(`Using '${organizationOwner}' as the organization`) + }) + + relay.on('webhook', async (event) => { + console.log( + `[${organizationOwner}] Forwarding received webhook: ${event.name}`, + ) + + const parsedEvent = JSON.stringify(event.payload) + + const eventNameWithAction = event.payload.action + ? `${event.name}.${event.payload.action}` + : event.name + + console.log( + `[${organizationOwner}] Forwarding ${eventNameWithAction} event to ${url} ... `, + ) + + const headers = {} + + headers['x-hub-signature-256'] = await sign( + process.env.WEBHOOK_SECRET, + parsedEvent, + ) + headers['x-github-event'] = eventNameWithAction + headers['x-github-delivery'] = event.id + headers['content-type'] = 'application/json' + + const response = await fetch(url, { + method: 'POST', + headers: headers, + body: parsedEvent, + }) + + console.log( + `[${organizationOwner}] ${eventNameWithAction} event response status= ${response.status}`, + ) + }) + + relay.on('error', (error) => { + console.log(`[${organizationOwner}] error: ${error}`) + }) + + relay.start() +} + +setupForwarder(process.env.PUBLIC_ORG) + +if ( + process.env.PRIVATE_ORG && + process.env.PUBLIC_ORG !== process.env.PRIVATE_ORG +) { + console.log('Setting up private organization webhook relay') + setupForwarder(process.env.PRIVATE_ORG) +} From 34d6e072918bd8cd759035d48483fc9bd1e7fa17 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 20 May 2024 17:03:32 -0400 Subject: [PATCH 2/7] fix: update example env to remove unsused webhook_url --- .env.example | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 6e70840..abd289b 100644 --- a/.env.example +++ b/.env.example @@ -12,8 +12,7 @@ NEXTAUTH_URL=http://localhost:3000 # A comma-separated list of GitHub usernames that are allowed to access the app ALLOWED_HANDLES= -# Go to https://smee.io/new set this to the URL that you are redirected to. -WEBHOOK_PROXY_URL= +# This is used to sign payloads from gihtub WEBHOOK_SECRET=bad-secret # Use `trace` to get verbose logging or `info` to show less From 9fbd97b5c9214873a236404f2c7ce9ca0064dbde Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 20 May 2024 17:04:18 -0400 Subject: [PATCH 3/7] fix: update env checker to remove unused webhook_proxy_url --- env.mjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/env.mjs b/env.mjs index a0522c1..f488b5d 100644 --- a/env.mjs +++ b/env.mjs @@ -17,7 +17,6 @@ export const env = createEnv({ PRIVATE_KEY: z.string(), // Optional environment variables - WEBHOOK_PROXY_URL: z.string().url().optional().or(z.literal('')), LOG_LEVEL: z.string().optional().default('debug'), NODE_ENV: z.string().optional().default('development'), PUBLIC_ORG: z.string().optional(), @@ -53,7 +52,6 @@ export const env = createEnv({ NEXTAUTH_URL: process.env.NEXTAUTH_URL, WEBHOOK_SECRET: process.env.WEBHOOK_SECRET, PRIVATE_KEY: process.env.PRIVATE_KEY, - WEBHOOK_PROXY_URL: process.env.WEBHOOK_PROXY_URL, LOG_LEVEL: process.env.LOG_LEVEL, NODE_ENV: process.env.NODE_ENV, PUBLIC_ORG: process.env.PUBLIC_ORG, From 6564394db48dab46829bd9a8c03293150d524419 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 20 May 2024 17:06:02 -0400 Subject: [PATCH 4/7] docs: update docs for env example secret --- .env.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index abd289b..1fec674 100644 --- a/.env.example +++ b/.env.example @@ -12,7 +12,8 @@ NEXTAUTH_URL=http://localhost:3000 # A comma-separated list of GitHub usernames that are allowed to access the app ALLOWED_HANDLES= -# This is used to sign payloads from gihtub +# This is used to sign payloads from github, see this doc for more info +# https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries WEBHOOK_SECRET=bad-secret # Use `trace` to get verbose logging or `info` to show less From b546ceaa1e6fe675f2c32c59d552de38c9ab0c32 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Tue, 21 May 2024 10:14:36 -0400 Subject: [PATCH 5/7] feat: remove unused smee client --- package-lock.json | 33 --------------------------------- package.json | 1 - scripts/probot-forwarder.js | 9 --------- 3 files changed, 43 deletions(-) delete mode 100644 scripts/probot-forwarder.js diff --git a/package-lock.json b/package-lock.json index 9857ab3..96e8d4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,7 +50,6 @@ "lint-staged": "15.2.2", "nock": "14.0.0-beta.6", "prettier": "3.2.5", - "smee-client": "2.0.1", "ts-jest": "29.1.2", "ts-node": "10.9.2", "typescript": "5.4.5", @@ -11482,29 +11481,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/smee-client": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/smee-client/-/smee-client-2.0.1.tgz", - "integrity": "sha512-s2+eG9vNMWQQvu8Jz+SfAiihpYsmaMtcyPnHtBuZEhaAAQOQV63xSSL9StWv2p08xKgvSC8pEZ28rXoy41FhLg==", - "dev": true, - "dependencies": { - "commander": "^12.0.0", - "eventsource": "^2.0.2", - "validator": "^13.11.0" - }, - "bin": { - "smee": "bin/smee.js" - } - }, - "node_modules/smee-client/node_modules/commander": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", - "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", - "dev": true, - "engines": { - "node": ">=18" - } - }, "node_modules/sonic-boom": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", @@ -12633,15 +12609,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/validator": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", - "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 38c8b05..e545e2d 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "lint-staged": "15.2.2", "nock": "14.0.0-beta.6", "prettier": "3.2.5", - "smee-client": "2.0.1", "ts-jest": "29.1.2", "ts-node": "10.9.2", "typescript": "5.4.5", diff --git a/scripts/probot-forwarder.js b/scripts/probot-forwarder.js deleted file mode 100644 index 44a962d..0000000 --- a/scripts/probot-forwarder.js +++ /dev/null @@ -1,9 +0,0 @@ -const SmeeClient = require('smee-client') - -const smee = new SmeeClient({ - source: process.env.WEBHOOK_PROXY_URL, - target: 'http://localhost:3000/api/webhooks', - logger: console, -}) - -smee.start() From 3df9a6a6ca80317f8b13c7f5c5b0e71d976abf18 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Tue, 21 May 2024 15:21:27 -0400 Subject: [PATCH 6/7] fix: lock deps in place --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e545e2d..37c6e72 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "dotenv-load": "3.0.0", "eslint": "8.57.0", "eslint-config-next": "14.2.1", - "github-app-webhook-relay-polling": "^1.1.0", + "github-app-webhook-relay-polling": "1.1.0", "husky": "9.0.11", "jest": "29.7.0", "lint-staged": "15.2.2", From 5cf1a258c31c636dda8f09d2dfe8b641446c26c9 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Tue, 21 May 2024 15:27:04 -0400 Subject: [PATCH 7/7] chore: update deps to use pinned version --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 96e8d4e..ee63b99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "dotenv-load": "3.0.0", "eslint": "8.57.0", "eslint-config-next": "14.2.1", - "github-app-webhook-relay-polling": "^1.1.0", + "github-app-webhook-relay-polling": "1.1.0", "husky": "9.0.11", "jest": "29.7.0", "lint-staged": "15.2.2",