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 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 16:49:49 +00:00
parent cc57cf887a
commit 0320042045
2 changed files with 18 additions and 3 deletions

View File

@@ -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')

View File

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