ac38d5e663
部署前生产级审计(可靠性/数据层/安全三路)后,清掉 7 处代码级硬伤: A1 后台定时器 goroutine 无 panic recover → 单个 DB panic 崩整个 gateway。加 safeGo/ safeCall,包住订阅/掉单补偿/微信推送/探针 goroutine,单轮 tick 再兜一层。 A2 提示词控制面(建/激活/停用,热广播全服务)只 RequireAuth → 任意登录用户改全局提示词。 三写端点+列表挂 RequireAdmin。 A3 HITL 审批端点无角色门 → viewer 可放行烧钱执行。加 RequireTenantRole(member)。 A4 审计/护栏列表 limit 无校验,limit=-1 让 gorm 取消 LIMIT 全表扫。加 clampLimit/ clampOffset,AdminTasks/AdminSpaces 补上界。 A5 限流 Redis 一挂就完全放行(fail-open)。加进程内固定窗口兜底(fail-safe) + 登录/注册 按 IP 专用严限流(10/min)。 A6 公开 by-id 端点(stream/exec/report导出/kb导入流)无鉴权无租户过滤。加 AuthFromHeaderOrQuery(从 ?token= 取 JWT) + task/report 按 owner 归属校验;桌面端 5 处 EventSource/下载 URL 经 tokenQuery 附 JWT。 A7 文件上传无大小上限(整文件进内存 OOM 面) → 50MB 闸(KB_MAX_UPLOAD_BYTES)+ LimitReader; http.Server 加 ReadHeaderTimeout/ReadTimeout/MaxHeaderBytes(不设 WriteTimeout 保 SSE)。 带单测:clampLimit/safeCall/procLimiter/AuthFromHeaderOrQuery/TaskOwner。 build+vet+全量 test 绿;desktop tsc 绿。B(迁移工具/实时探针/出网韧性/登录锁定/leader选举) 与 C(TLS/PG HA/K8s/备份自动化/可观测)分期后做,参照 production_readiness.md。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
1.4 KiB
Go
57 lines
1.4 KiB
Go
package middleware
|
||
|
||
import (
|
||
"net/http/httptest"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"github.com/sundynix/sundynix-gateway/internal/auth"
|
||
)
|
||
|
||
// A5:进程内兜底限流——固定窗口内超限即拒,过窗后恢复。Redis 挂时靠它 fail-safe。
|
||
func TestProcLimiter(t *testing.T) {
|
||
l := newProcLimiter(time.Minute)
|
||
now := time.Now()
|
||
for i := 0; i < 3; i++ {
|
||
if !l.allow("k", 3, now) {
|
||
t.Fatalf("前 3 次应放行(第 %d 次被拒)", i+1)
|
||
}
|
||
}
|
||
if l.allow("k", 3, now) {
|
||
t.Fatal("超限第 4 次应拒")
|
||
}
|
||
if !l.allow("k", 3, now.Add(2*time.Minute)) {
|
||
t.Fatal("新窗口应恢复放行")
|
||
}
|
||
}
|
||
|
||
// A6:AuthFromHeaderOrQuery——?token= 有效则注入 uid、无 token 则 401。
|
||
func TestAuthFromHeaderOrQuery(t *testing.T) {
|
||
gin.SetMode(gin.TestMode)
|
||
tok, err := auth.Issue("u1")
|
||
if err != nil {
|
||
t.Fatalf("签发失败: %v", err)
|
||
}
|
||
|
||
w := httptest.NewRecorder()
|
||
c, _ := gin.CreateTestContext(w)
|
||
c.Request = httptest.NewRequest("GET", "/x?token="+tok, nil)
|
||
AuthFromHeaderOrQuery()(c)
|
||
if c.IsAborted() {
|
||
t.Fatal("有效 query token 不该被拦")
|
||
}
|
||
if v, _ := c.Get(CtxUserID); v != "u1" {
|
||
t.Fatalf("应注入 uid=u1,得 %v", v)
|
||
}
|
||
|
||
w2 := httptest.NewRecorder()
|
||
c2, _ := gin.CreateTestContext(w2)
|
||
c2.Request = httptest.NewRequest("GET", "/x", nil)
|
||
AuthFromHeaderOrQuery()(c2)
|
||
if !c2.IsAborted() || w2.Code != 401 {
|
||
t.Fatalf("无 token 应 401,得 aborted=%v code=%d", c2.IsAborted(), w2.Code)
|
||
}
|
||
}
|