Files
GovAI/server/pkg/auth/twofa_test.go
T
freedakgmail c949204662 feat(govai): 0617 优化首批 — 安全/私有化/深度研究/服务层/可观测性
借鉴 odysseus 的能力设计,全程净室实现、零 AGPL 代码、不引入 AGPL 依赖。

T1 提示注入防护: pkg/promptguard 包裹外部/知识库内容为不可信数据,buildMessages 移出 system 指令区。 T2 安全 CI: .github/workflows(ci+security: govulncheck/gitleaks/actionlint/hadolint/trivy)+dependabot+.hadolint.yaml;go.mod 加 toolchain go1.25.11 修复 20 个 stdlib CVE。 T3 管理员 2FA: 迁移 000016 + RFC6238 TOTP/备份码(pkg/auth, 零依赖) + 登录流程集成(后端)。 T4 本地模型: LLM/embedding 支持本地 vLLM/Ollama(OpenAI 兼容, 鉴权头条件发送, NoAuth) + docs/local-deploy.md。 T6 深度研究: 迁移 000017 + Python research-worker(净室多步流水线, 检索避开 SearXNG) + Go research 服务/handler/路由。 T7 service 层: 新增 internal/service/{research,twofa}, 2FA 业务逻辑从胖 handler 下沉, 接口注入可单测。 T10 缓存/可观测性: internal/cache(Redis+内存, 优雅降级) 接入 store 热点列表; Prometheus 指标+/metrics; docs/openapi.yaml。 验证: go build/vet/test ./... 全绿(8 包); research-worker 12 单测过; 真实 PG 应用迁移并烟测。
2026-06-17 17:52:47 +08:00

91 lines
2.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package auth
import (
"strings"
"testing"
)
// RFC 6238 测试向量:种子 ASCII "12345678901234567890"base32 如下),
// SHA1、time=59s 对应 8 位 TOTP 为 94287082,截断到 6 位即 287082。
func TestTOTP_RFC6238Vector(t *testing.T) {
const secret = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ" // base32("12345678901234567890")
code, err := TOTPCodeAt(secret, 59)
if err != nil {
t.Fatalf("TOTPCodeAt 出错: %v", err)
}
if code != "287082" {
t.Fatalf("RFC6238 向量不匹配:want 287082, got %s", code)
}
}
func TestValidateTOTP_CurrentAndSkew(t *testing.T) {
secret, err := GenerateTOTPSecret()
if err != nil {
t.Fatalf("生成密钥失败: %v", err)
}
var now int64 = 1_700_000_000
cur, _ := TOTPCodeAt(secret, now)
if !ValidateTOTP(secret, cur, now) {
t.Fatal("当前时间窗的口令应通过校验")
}
// 上一个时间窗的口令应在 ±1 窗容忍范围内通过
prev, _ := TOTPCodeAt(secret, now-30)
if !ValidateTOTP(secret, prev, now) {
t.Fatal("上一个时间窗的口令应在容忍范围内通过")
}
// 超出 ±1 窗(-90s)应失败
old, _ := TOTPCodeAt(secret, now-90)
if ValidateTOTP(secret, old, now) {
t.Fatal("超出容忍范围的口令应被拒绝")
}
// 明显错误的口令应失败
if ValidateTOTP(secret, "000000", now) && cur != "000000" {
t.Fatal("错误口令应被拒绝")
}
// 长度不符应直接拒绝
if ValidateTOTP(secret, "12345", now) {
t.Fatal("位数不足的口令应被拒绝")
}
}
func TestProvisioningURI(t *testing.T) {
uri := TOTPProvisioningURI("ABC234", "admin@govai.gov.cn", "政智通 GovAI")
for _, want := range []string{"otpauth://totp/", "secret=ABC234", "issuer=", "digits=6", "period=30"} {
if !strings.Contains(uri, want) {
t.Fatalf("otpauth URI 缺少 %q: %s", want, uri)
}
}
}
func TestBackupCodes_GenerateVerifyConsumeSemantics(t *testing.T) {
plain, hashes, err := GenerateBackupCodes(8)
if err != nil {
t.Fatalf("生成备份码失败: %v", err)
}
if len(plain) != 8 || len(hashes) != 8 {
t.Fatalf("应生成 8 个备份码,实际 plain=%d hashes=%d", len(plain), len(hashes))
}
// 每个明文应能匹配其对应哈希
for i := range plain {
if !CheckBackupCode(plain[i], hashes[i]) {
t.Fatalf("备份码 #%d 无法匹配自身哈希", i)
}
}
// 归一化:大小写/连字符/空格不应影响校验
if !CheckBackupCode(strings.ToUpper(plain[0]), hashes[0]) {
t.Fatal("大写形式的备份码应仍匹配")
}
if !CheckBackupCode(strings.ReplaceAll(plain[0], "-", ""), hashes[0]) {
t.Fatal("去掉连字符的备份码应仍匹配")
}
// 不匹配的码应失败
if CheckBackupCode("wrong-code1", hashes[0]) {
t.Fatal("错误备份码不应匹配")
}
// 备份码之间不应交叉匹配
if CheckBackupCode(plain[0], hashes[1]) {
t.Fatal("不同备份码不应交叉匹配")
}
}