security: 会话可撤销与认证面加固
- users 加 token_version(schema v4):改密/登出递增,该用户全部已发 access/refresh 立即作废;refresh 改轮换式换发新 cookie - 邀请码改精确匹配 + 注册成功后消费(一码一用),修复子串匹配让任意后缀放行、可无限薅体验金 - 生产校验拦下 .env.example 占位密钥;OT_ENV 枚举校验(拼错不再静默跳过断言);生产强制 COOKIE_SECURE;非 development 拒绝默认密码播种管理员 - WebAuthn 会话启用过期校验 + 4096 容量上限(未认证 login/begin 可无限撑爆内存),begin/complete 挂登录级限速 - /api/v1 挂 1MB 请求体上限;login 先限 IP 桶再解析 body;全局安全响应头 + CSP - 连接池配置(PG 30/SQLite 8);SQLite DSN 自动追加 WAL + busy_timeout(驱动只认 _pragma,旧注释的 _journal_mode 写法不生效)
This commit is contained in:
1 parent
d6db0f8162
commit
e3ff0d3329
14 files changed
+392
-37
No files matched your search
@@ -0,0 +1,88 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/openteam/server/internal/store"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 邀请码必须精确匹配(子串匹配会让任意后缀放行)且用后消费(一码一用)。
|
||||
func TestInviteCodesLoadAndConsume(t *testing.T) {
|
||||
db := openInviteTestDB(t)
|
||||
|
||||
// 历史形态:JSON 字符串 + 逗号分隔
|
||||
if err := db.Create(&store.SystemConfig{Key: "invite_codes", Value: `"ALPHA-2026, BETA-7;"`}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codes, err := loadInviteCodes(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(codes) != 2 || codes[0] != "ALPHA-2026" || codes[1] != "BETA-7" {
|
||||
t.Fatalf("应解析出 2 个邀请码,实际 %v", codes)
|
||||
}
|
||||
|
||||
// 消费:精确码被移除,其余保留;写回仍是字符串形态(管理后台按逗号分隔编辑)
|
||||
if err := consumeInviteCode(db, "ALPHA-2026"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codes, _ = loadInviteCodes(db)
|
||||
if len(codes) != 1 || codes[0] != "BETA-7" {
|
||||
t.Fatalf("消费后应剩 1 个邀请码,实际 %v", codes)
|
||||
}
|
||||
var raw string
|
||||
db.Model(&store.SystemConfig{}).Where("key = ?", "invite_codes").Pluck("value", &raw)
|
||||
if raw != `"BETA-7"` {
|
||||
t.Fatalf("写回应为 JSON 字符串形态,实际 %s", raw)
|
||||
}
|
||||
|
||||
// 消费不存在的码:无变化、不报错
|
||||
if err := consumeInviteCode(db, "NOT-THERE"); err != nil {
|
||||
t.Fatalf("消费不存在的码不应报错: %v", err)
|
||||
}
|
||||
|
||||
// 新形态:JSON 数组
|
||||
db.Where("key = ?", "invite_codes").Delete(&store.SystemConfig{})
|
||||
db.Create(&store.SystemConfig{Key: "invite_codes", Value: `["CODE-A","CODE-B"]`})
|
||||
codes, err = loadInviteCodes(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(codes) != 2 || codes[0] != "CODE-A" {
|
||||
t.Fatalf("数组形态应可解析,实际 %v", codes)
|
||||
}
|
||||
|
||||
// 非法 JSON:报错而不是静默全放行
|
||||
db.Where("key = ?", "invite_codes").Delete(&store.SystemConfig{})
|
||||
db.Create(&store.SystemConfig{Key: "invite_codes", Value: `{invalid`})
|
||||
if _, err := loadInviteCodes(db); err == nil {
|
||||
t.Fatal("非法配置应报错")
|
||||
}
|
||||
}
|
||||
|
||||
// 消费时同码重复录入只删一个。
|
||||
func TestConsumeInviteCodeDuplicateEntry(t *testing.T) {
|
||||
db := openInviteTestDB(t)
|
||||
db.Create(&store.SystemConfig{Key: "invite_codes", Value: `"CODE-A,CODE-A,CODE-B"`})
|
||||
if err := consumeInviteCode(db, "CODE-A"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codes, _ := loadInviteCodes(db)
|
||||
if len(codes) != 2 || codes[0] != "CODE-A" || codes[1] != "CODE-B" {
|
||||
t.Fatalf("同码重复只应删一个,实际 %v", codes)
|
||||
}
|
||||
}
|
||||
|
||||
func openInviteTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db, err := store.Open("sqlite", ":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.Close()
|
||||
})
|
||||
return db
|
||||
}
|
||||
Reference in new issue
Block a user