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) => {