Smail/backend/services/totp/service_test.go
smail f60d3b0880
Some checks are pending
Security Checks / security (push) Waiting to run
整理前后端与测试构建目录并完善配置和安全引导
统一 frontend、backend、test、test-log 和 out 目录,更新构建、部署及测试入口;纳入配置管理兼容与首次安全引导修复;排除本机配置、密钥和测试产物。
2026-09-05 19:19:52 +08:00

111 lines
3.2 KiB
Go

package totp
import (
"encoding/base32"
"errors"
"path/filepath"
"strings"
"testing"
"time"
"git.pcux.cn/xuans/Smail/models"
_ "modernc.org/sqlite"
"xorm.io/xorm"
)
func newTestService(t *testing.T) (*Service, *xorm.Engine) {
t.Helper()
engine, err := xorm.NewEngine("sqlite", filepath.Join(t.TempDir(), "totp.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = engine.Close() })
if err := engine.Sync2(&models.TOTPAuthenticator{}); err != nil {
t.Fatal(err)
}
root := t.TempDir()
service, err := NewService(engine, root)
if err != nil {
t.Fatal(err)
}
return service, engine
}
func TestEnrollmentVerifyReplayAndDisable(t *testing.T) {
service, _ := newTestService(t)
now := time.Date(2026, time.August, 27, 12, 0, 0, 0, time.UTC)
service.now = func() time.Time { return now }
enrollment, err := service.BeginEnrollment(1, "root")
if err != nil {
t.Fatal(err)
}
secret, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(enrollment.Secret)
if err != nil {
t.Fatal(err)
}
code := generateCode(secret, uint64(now.Unix()/PeriodSeconds))
if err := service.ConfirmEnrollment(1, enrollment.ID, code); err != nil {
t.Fatal(err)
}
if enabled, err := service.IsEnabled(1); err != nil || !enabled {
t.Fatalf("enabled=%v err=%v", enabled, err)
}
if err := service.Verify(1, code); !errors.Is(err, ErrReplay) {
t.Fatalf("expected replay error, got %v", err)
}
now = now.Add(PeriodSeconds * time.Second)
nextCode := generateCode(secret, uint64(now.Unix()/PeriodSeconds))
if err := service.Verify(1, nextCode); err != nil {
t.Fatal(err)
}
now = now.Add(PeriodSeconds * time.Second)
disableCode := generateCode(secret, uint64(now.Unix()/PeriodSeconds))
if err := service.Disable(1, disableCode); err != nil {
t.Fatal(err)
}
if enabled, err := service.IsEnabled(1); err != nil || enabled {
t.Fatalf("enabled=%v err=%v", enabled, err)
}
}
func TestEnrollmentInvalidCodeLimit(t *testing.T) {
service, _ := newTestService(t)
enrollment, err := service.BeginEnrollment(2, "alice")
if err != nil {
t.Fatal(err)
}
for attempt := 0; attempt < 5; attempt++ {
if err := service.ConfirmEnrollment(2, enrollment.ID, "000000"); !errors.Is(err, ErrInvalidCode) {
t.Fatalf("attempt %d: %v", attempt+1, err)
}
}
if err := service.ConfirmEnrollment(2, enrollment.ID, "000000"); !errors.Is(err, ErrEnrollmentExpired) {
t.Fatalf("expected expired enrollment, got %v", err)
}
}
func TestValidateAtAcceptsAdjacentWindow(t *testing.T) {
secret := []byte("01234567890123456789012345678901")
now := time.Unix(1_800_000_000, 0).UTC()
previousStep := now.Unix()/PeriodSeconds - 1
code := generateCode(secret, uint64(previousStep))
step, ok := validateAt(secret, code, now, 1)
if !ok || step != previousStep {
t.Fatalf("step=%d ok=%v", step, ok)
}
if _, ok := validateAt(secret, code, now.Add(PeriodSeconds*time.Second), 1); ok {
t.Fatal("expired adjacent code was accepted")
}
}
func TestProvisioningURIUsesSHA256(t *testing.T) {
uri := provisioningURI("root@example.com", "ABCDEF")
for _, value := range []string{"otpauth://totp/", "issuer=Smail", "algorithm=SHA256", "digits=6", "period=30", "secret=ABCDEF"} {
if !strings.Contains(uri, value) {
t.Fatalf("URI %q does not contain %q", uri, value)
}
}
}