mirror of
https://github.com/jeffusion/gitea-ai-assistant.git
synced 2026-06-08 23:16:50 +00:00
[bugfix] 修复对于gitea webhook签名的检查失败问题
This commit is contained in:
@@ -9,4 +9,7 @@ OPENAI_MODEL=gpt-4o-mini
|
||||
|
||||
# 应用配置
|
||||
PORT=3000
|
||||
# 建议使用以下命令生成一个安全的随机字符串作为webhook密钥:
|
||||
# 在Linux/Mac终端: openssl rand -hex 32
|
||||
# 或者在Node.js中: require('crypto').randomBytes(32).toString('hex')
|
||||
WEBHOOK_SECRET=your_webhook_secret
|
||||
|
||||
15
README.md
15
README.md
@@ -71,17 +71,28 @@
|
||||
**Pull Request审查webhook**:
|
||||
- URL: `http://your-server:3000/webhook/gitea/pull_request`
|
||||
- 内容类型: `application/json`
|
||||
- 秘钥: 设置为与WEBHOOK_SECRET相同的值
|
||||
- 秘钥: 设置为与`WEBHOOK_SECRET`环境变量相同的值
|
||||
- 触发事件: 选择"Pull Request"
|
||||
|
||||
**提交状态审查webhook**:
|
||||
- URL: `http://your-server:3000/webhook/gitea/status`
|
||||
- 内容类型: `application/json`
|
||||
- 秘钥: 设置为与WEBHOOK_SECRET相同的值
|
||||
- 秘钥: 设置为与`WEBHOOK_SECRET`环境变量相同的值
|
||||
- 触发事件: 选择"Status"
|
||||
|
||||
> 注意: 老端点 `/webhook/gitea` 仍然支持Pull Request审查,但仅作向后兼容使用。
|
||||
|
||||
### Webhook签名验证
|
||||
|
||||
为确保请求安全,系统使用Gitea的Webhook签名验证机制:
|
||||
|
||||
1. 设置环境变量`WEBHOOK_SECRET`为一个安全的随机字符串
|
||||
2. 在Gitea的Webhook配置中,使用相同的字符串作为"秘钥"
|
||||
3. 每次请求时,系统会验证请求头中的`X-Gitea-Signature`
|
||||
4. 如果签名验证失败,请求会被拒绝处理
|
||||
|
||||
验证方法使用SHA-256哈希算法,在处理高负载的情况下这能防止恶意请求并保证请求来源的真实性。
|
||||
|
||||
## 功能说明
|
||||
|
||||
### PR代码审查
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
"name": "ai-review",
|
||||
"name": "gitea-ai-reviewer",
|
||||
"version": "1.0.0",
|
||||
"description": "AI-driven code review for Gitea",
|
||||
"packageManager": "yarn@4.6.0",
|
||||
"engines": {
|
||||
"bun": ">=1.2.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/zod-validator": "^0.4.3",
|
||||
"axios": "^1.8.3",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Context } from 'hono';
|
||||
import { giteaService, PullRequestFile, PullRequestDetails } from '../services/gitea';
|
||||
import { aiReviewService } from '../services/ai-review';
|
||||
// import config from '../config';
|
||||
// import * as crypto from 'crypto';
|
||||
import config from '../config';
|
||||
import * as crypto from 'crypto';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
// 判断是否为开发环境
|
||||
@@ -11,32 +11,41 @@ const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
|
||||
/**
|
||||
* 验证Webhook请求签名
|
||||
*/
|
||||
// function verifyWebhookSignature(body: string, signature: string): boolean {
|
||||
// // 开发环境下跳过签名验证
|
||||
// if (isDev && !signature) {
|
||||
// logger.warn('开发环境: 跳过Webhook签名验证');
|
||||
// return true;
|
||||
// }
|
||||
function verifyWebhookSignature(body: string, signature: string): boolean {
|
||||
// 开发环境下跳过签名验证
|
||||
if (isDev && !signature) {
|
||||
logger.warn('开发环境: 跳过Webhook签名验证');
|
||||
return true;
|
||||
}
|
||||
|
||||
// if (!config.app.webhookSecret) return false;
|
||||
if (!config.app.webhookSecret) {
|
||||
logger.warn('未配置Webhook密钥,跳过签名验证');
|
||||
return false;
|
||||
}
|
||||
|
||||
// const hmac = crypto.createHmac('sha256', config.app.webhookSecret);
|
||||
// hmac.update(body);
|
||||
// const calculatedSignature = `sha256=${hmac.digest('hex')}`;
|
||||
// Gitea使用SHA-256哈希算法
|
||||
const hmac = crypto.createHmac('sha256', config.app.webhookSecret);
|
||||
hmac.update(body);
|
||||
const calculatedSignature = hmac.digest('hex');
|
||||
|
||||
// // 如果签名不存在,直接返回false
|
||||
// if (!signature) return false;
|
||||
// 如果签名不存在,直接返回false
|
||||
if (!signature) {
|
||||
logger.warn('请求中无签名头');
|
||||
return false;
|
||||
}
|
||||
|
||||
// try {
|
||||
// return crypto.timingSafeEqual(
|
||||
// Buffer.from(calculatedSignature),
|
||||
// Buffer.from(signature)
|
||||
// );
|
||||
// } catch (error) {
|
||||
// logger.error('签名验证失败', error);
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// Gitea的签名没有前缀,直接比较
|
||||
try {
|
||||
// 使用timingSafeEqual进行常量时间比较,防止时序攻击
|
||||
return crypto.timingSafeEqual(
|
||||
Buffer.from(calculatedSignature),
|
||||
Buffer.from(signature)
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('签名验证失败', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Pull Request事件
|
||||
@@ -44,13 +53,13 @@ const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
|
||||
export async function handlePullRequestEvent(c: Context): Promise<Response> {
|
||||
try {
|
||||
// 验证Webhook签名
|
||||
// const signature = c.req.header('X-Gitea-Signature') || '';
|
||||
const signature = c.req.header('X-Gitea-Signature') || '';
|
||||
const rawBody = await c.req.text();
|
||||
|
||||
// if (!verifyWebhookSignature(rawBody, signature)) {
|
||||
// logger.error('Webhook签名验证失败');
|
||||
// return c.json({ error: 'Webhook签名验证失败' }, 401);
|
||||
// }
|
||||
if (!verifyWebhookSignature(rawBody, signature)) {
|
||||
logger.error('Webhook签名验证失败');
|
||||
return c.json({ error: 'Webhook签名验证失败' }, 401);
|
||||
}
|
||||
|
||||
// 解析请求体
|
||||
const body = JSON.parse(rawBody);
|
||||
@@ -99,7 +108,15 @@ export async function handlePullRequestEvent(c: Context): Promise<Response> {
|
||||
*/
|
||||
export async function handleCommitStatusEvent(c: Context): Promise<Response> {
|
||||
try {
|
||||
// 验证Webhook签名
|
||||
const signature = c.req.header('X-Gitea-Signature') || '';
|
||||
const rawBody = await c.req.text();
|
||||
|
||||
if (!verifyWebhookSignature(rawBody, signature)) {
|
||||
logger.error('Webhook签名验证失败');
|
||||
return c.json({ error: 'Webhook签名验证失败' }, 401);
|
||||
}
|
||||
|
||||
const body = JSON.parse(rawBody);
|
||||
|
||||
// 记录收到的数据,方便调试
|
||||
|
||||
19
src/index.ts
19
src/index.ts
@@ -7,7 +7,24 @@ const app = new Hono();
|
||||
|
||||
// 健康检查路由
|
||||
app.get('/', (c) => {
|
||||
return c.json({ status: 'ok', message: 'AI Code Review 服务运行中' });
|
||||
const webhookSecretConfigured = !!config.app.webhookSecret;
|
||||
|
||||
return c.json({
|
||||
status: 'ok',
|
||||
message: 'AI Code Review 服务运行中',
|
||||
version: '1.1.0',
|
||||
webhookSecurityEnabled: webhookSecretConfigured,
|
||||
configuration: {
|
||||
webhookEndpoints: {
|
||||
pullRequest: '/webhook/gitea/pull_request',
|
||||
commitStatus: '/webhook/gitea/status',
|
||||
legacy: '/webhook/gitea (仅支持Pull Request事件)'
|
||||
},
|
||||
signature: webhookSecretConfigured
|
||||
? '签名验证已启用 (使用X-Gitea-Signature头)'
|
||||
: '警告: 签名验证未配置,建议设置WEBHOOK_SECRET环境变量'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Gitea webhook路由 - 处理PR事件
|
||||
|
||||
Reference in New Issue
Block a user