✨ feat(vscode): 多人协作二期:状态分层/写校验/签名/团队视图
- 本机状态(会话id/worktree/profile/prDiffFile)迁 workspaceState, 共享 state JSON 只留团队字段;读侧本地记录叠加,旧数据兜底 - mergeStateJsonComment 写后读回校验,并发覆盖时在最新状态上重放一次 - webhook 支持 Gitea secret,校验 X-Gitea-Signature(HMAC-SHA256), 不匹配 401;设置面板新增 Webhook Secret 字段 - 看板新增 我的/全部 范围切换(团队视图),按工作区持久化
This commit is contained in:
1 parent
c370214d12
commit
faf227412e
18 files changed
+665
-202
No files matched your search
@@ -24,6 +24,8 @@
|
||||
|
||||
import type { IncomingMessage, Server, ServerResponse } from 'node:http'
|
||||
import type { Event } from 'vscode'
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { createHmac, timingSafeEqual } from 'node:crypto'
|
||||
import { createServer } from 'node:http'
|
||||
import { EventEmitter } from 'vscode'
|
||||
import { logger } from '../logging/logger'
|
||||
@@ -111,10 +113,33 @@ export interface PushWebhookEvent {
|
||||
export class WebhookServer {
|
||||
private server?: Server
|
||||
private port?: number
|
||||
private secret = ''
|
||||
private readonly emitter = new EventEmitter<WebhookEvent>()
|
||||
|
||||
readonly onEvent: Event<WebhookEvent> = this.emitter.event
|
||||
|
||||
/**
|
||||
* 设置(或清空)Gitea webhook secret。非空时对每个请求校验
|
||||
* `X-Gitea-Signature`(HMAC-SHA256 over 原始 body 字节),不匹配一律 401
|
||||
* ——端口经 frp 暴露公网,没有签名任何人都能伪造事件触发 codex 执行。
|
||||
*/
|
||||
setSecret(secret: string): void {
|
||||
this.secret = secret
|
||||
}
|
||||
|
||||
/** 时序安全的签名比对;头缺失/长度不符/非法 hex 都按不通过处理。 */
|
||||
private verifySignature(rawBody: Buffer, signatureHeader: string): boolean {
|
||||
const expected = createHmac('sha256', this.secret).update(rawBody).digest()
|
||||
let received: Buffer
|
||||
try {
|
||||
received = Buffer.from(signatureHeader, 'hex')
|
||||
}
|
||||
catch {
|
||||
return false
|
||||
}
|
||||
return received.length === expected.length && timingSafeEqual(received, expected)
|
||||
}
|
||||
|
||||
/** The port currently bound, or undefined if the server is not listening. */
|
||||
get currentPort(): number | undefined {
|
||||
return this.port
|
||||
@@ -220,7 +245,21 @@ export class WebhookServer {
|
||||
req.on('data', (c: Buffer) => chunks.push(c))
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const body = Buffer.concat(chunks).toString('utf-8')
|
||||
const rawBody = Buffer.concat(chunks)
|
||||
if (this.secret) {
|
||||
const signatureHeader = (req.headers['x-gitea-signature'] || '').toString()
|
||||
if (!signatureHeader || !this.verifySignature(rawBody, signatureHeader)) {
|
||||
logger.add({
|
||||
level: 'warn',
|
||||
source: 'webhook',
|
||||
message: `签名校验失败,拒绝请求 (signature=${signatureHeader ? '不匹配' : '<缺失>'})`,
|
||||
})
|
||||
res.writeHead(401, { 'Content-Type': 'application/json' })
|
||||
res.end(JSON.stringify({ ok: false, error: 'invalid_signature' }))
|
||||
return
|
||||
}
|
||||
}
|
||||
const body = rawBody.toString('utf-8')
|
||||
const eventHeader = (req.headers['x-gitea-event'] || '').toString() || '<missing>'
|
||||
logger.add({
|
||||
level: 'info',
|
||||
|
||||
Reference in new issue
Block a user