diff --git a/.env.example b/.env.example index 6e70840..1fec674 100644 --- a/.env.example +++ b/.env.example @@ -12,8 +12,8 @@ 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 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 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/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, diff --git a/package-lock.json b/package-lock.json index f198ea1..ee63b99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,12 +44,12 @@ "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", "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", @@ -2188,6 +2188,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 +2224,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 +5747,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 +5917,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 +6931,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 +9765,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", @@ -11114,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", @@ -11666,6 +12010,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", @@ -12259,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", @@ -12313,6 +12654,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..37c6e72 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,12 +54,12 @@ "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", "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() 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) +}