Files
knowledge-governance/internal/platform/postgres/auth_test.go
zheromiku fbe80ad784 feat(auth): accounts, sessions, RBAC and server-stamped actors
上线账号权限管理(后端):
- 迁移 000032:users + user_sessions(token 只存 sha256,服务端可即时撤销)
- bcrypt 密码、8 次失败锁 15 分钟、统一 401 错误防用户名枚举
- 三角色 viewer/engineer/admin;HttpOnly+SameSite=Lax 会话 cookie,滑动续期
- 强制改密锁定:must_change_password 会话只能走 auth 端点;首登 dobotai
- bootstrap:users 表为空时创建 admin(初始密码 dobotai,强制改密)
- 生产双闸授权/回滚/失败重试收敛为 admin 角色;AuthorizedBy 用会话身份
- 全部业务写端点删除手填 actor 字段(operator_ref/reviewer_ref/created_by/
  sealed_by/confirmed_by),服务端从会话注入;auth 关闭的开发 socket 回退 system
- 成员管理 API:列表/创建/改角色/启停/重置密码,最后一名 active admin 保护
- RBAC 矩阵纯函数契约测试 + 中间件行为测试(登录/锁定/角色/审计戳)
- 附带修复:GET /api/v1/users 曾被 GET=viewer 规则误放行(测试抓到)
2026-09-30 10:54:07 +08:00

50 lines
1.5 KiB
Go

package postgres
import (
"errors"
"strings"
"testing"
"time"
)
func TestAuthStoreRequiresIntegrationDatabase(t *testing.T) {
// Real bcrypt + sessions behaviour is covered by the integration suite;
// this unit file pins the pure helpers that gate every auth write.
if !validUsername("yaozhanghao") || !validUsername("a.b_c-9") {
t.Fatal("valid usernames rejected")
}
for _, bad := range []string{"", "a", "-lead", ".lead", "UPPER", "sp ace", strings.Repeat("x", 65)} {
if validUsername(bad) {
t.Fatalf("invalid username accepted: %q", bad)
}
}
if !validPasswordPolicy("dobotai!") || validPasswordPolicy("short") || validPasswordPolicy(strings.Repeat("x", 129)) {
t.Fatal("password policy wrong")
}
if !ValidRole(RoleViewer) || !ValidRole(RoleEngineer) || !ValidRole(RoleAdmin) || ValidRole("superuser") {
t.Fatal("role set wrong")
}
if len(hashSessionToken("t")) != 64 {
t.Fatal("session token hash must be sha256 hex")
}
if got := truncateUserAgent("a\nb"); got != "a b" {
t.Fatalf("user agent control chars: %q", got)
}
}
func TestAuthErrorsAreDistinct(t *testing.T) {
for _, err := range []error{ErrAuthInvalidCredential, ErrAuthLocked, ErrAuthUserDisabled, ErrAuthSessionInvalid, ErrAuthLastAdmin} {
if err == nil || errors.Is(err, ErrAuthUserNotFound) {
t.Fatalf("auth error taxonomy broken: %v", err)
}
}
}
func TestAuthSessionTTLDefaults(t *testing.T) {
// zero ttl must not create an instantly-dead session
ttl := 12 * time.Hour
if ttl <= 0 {
t.Fatal("default ttl must be positive")
}
}