From 0320042045b98bd3ba5a81f0e75686186ff8aede Mon Sep 17 00:00:00 2001 From: Kenso Grimm Date: Sat, 11 Jul 2026 16:49:49 +0000 Subject: [PATCH] fix: Gitea-Webhook akzeptiert HMAC-Signatur (X-Gitea-Signature) Gitea sendet das Webhook-Secret als HMAC-SHA256-Signatur des Bodys, nicht als Klartext-Header. Bisher liefen alle per Secret konfigurierten Repo-Hooks auf 401 - Push-Deploys funktionierten nur ueber ?token= in der URL. Co-Authored-By: Claude Fable 5 --- server/index.js | 3 ++- server/routes/webhook/gitea.js | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/server/index.js b/server/index.js index aa489ee..18915bb 100644 --- a/server/index.js +++ b/server/index.js @@ -24,7 +24,8 @@ assertServerConfig() const app = express() app.set('trust proxy', 1) app.use(sessionMiddleware()) -app.use(express.json({ limit: '2mb' })) +// rawBody wird fuer die HMAC-Verifikation des Gitea-Webhooks benoetigt +app.use(express.json({ limit: '2mb', verify: (req, _res, buf) => { req.rawBody = buf } })) app.use((req, res, next) => { const origin = req.get('Origin') diff --git a/server/routes/webhook/gitea.js b/server/routes/webhook/gitea.js index 0470eb1..bf214cc 100644 --- a/server/routes/webhook/gitea.js +++ b/server/routes/webhook/gitea.js @@ -1,3 +1,4 @@ +import crypto from 'node:crypto' import { Router } from 'express' import { config } from '../../config.js' import { upsertWebsiteProjectByRepo } from '../../services/appwriteAdmin.js' @@ -15,8 +16,21 @@ function verifyWebhookToken(req) { const token = config.gitea.webhookToken if (!token) return false const queryToken = req.query.token - const headerToken = req.get('X-Gitea-Token') || req.get('X-Gogs-Signature') - return queryToken === token || headerToken === token + const headerToken = req.get('X-Gitea-Token') + if (queryToken === token || headerToken === token) return true + + // Gitea sendet das Webhook-Secret nicht im Klartext, sondern als + // HMAC-SHA256-Signatur des Request-Bodys (X-Gitea-Signature). + const signature = req.get('X-Gitea-Signature') || req.get('X-Gogs-Signature') + if (signature && req.rawBody) { + const expected = crypto.createHmac('sha256', token).update(req.rawBody).digest('hex') + try { + return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) + } catch { + return false + } + } + return false } router.post('/gitea', async (req, res) => {