import fs from 'fs'; import crypto from 'crypto'; var ALLOWLIST_PATH = '/etc/nginx/gitea-repo-allowlist.json'; var SECRET_PATH = '/etc/nginx/gitea-webhook-secret'; function getSecret() { try { return fs.readFileSync(SECRET_PATH).toString().trim(); } catch (e) { return null; } } function loadAllowlist() { try { return JSON.parse(fs.readFileSync(ALLOWLIST_PATH).toString()); } catch (e) { return null; } } function isRepoAllowed(repoFullName, allowlist) { if (!allowlist || !repoFullName) return false; if ((allowlist.repos || []).indexOf(repoFullName) !== -1) return true; var owners = allowlist.trusted_owners || []; for (var i = 0; i < owners.length; i++) { if (repoFullName.startsWith(owners[i] + '/')) return true; } return false; } function constantTimeEqual(a, b) { if (a.length !== b.length) return false; var result = 0; for (var i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i); return result === 0; } async function verifyAndProxy(r) { var secret = getSecret(); if (!secret) { r.error('gitea-hmac: failed to read secret'); r.return(500, 'Config error'); return; } var giteaSig = r.headersIn['X-Gitea-Signature']; if (!giteaSig) { r.error('gitea-hmac: missing X-Gitea-Signature'); r.return(403, 'Missing signature'); return; } var body = r.requestText || ''; var hmac = crypto.createHmac('sha256', secret); hmac.update(body); if (!constantTimeEqual(hmac.digest('hex'), giteaSig)) { r.error('gitea-hmac: signature mismatch'); r.return(403, 'Invalid signature'); return; } var allowlist = loadAllowlist(); if (!allowlist) { r.error('gitea-hmac: cannot read allowlist'); r.return(403, 'Authorization unavailable'); return; } var repoFullName = ''; try { repoFullName = (JSON.parse(body).repository || {}).full_name || ''; } catch (e) { r.error('gitea-hmac: invalid JSON'); r.return(403, 'Invalid body'); return; } if (!repoFullName) { r.error('gitea-hmac: no repo name'); r.return(403, 'Missing repo'); return; } if (!isRepoAllowed(repoFullName, allowlist)) { r.error('gitea-hmac: BLOCKED ' + repoFullName); r.return(403, 'Not authorized'); return; } var res = await r.subrequest('/hooks/gitea-upstream', { method: r.method, body: body }); var ct = res.headersOut['Content-Type']; if (ct) r.headersOut['Content-Type'] = ct; r.return(res.status, res.responseBody); } export default { verifyAndProxy };