xfy f2352ab9cc docs(config,stream,logging,handler,proxy,cache,server,ssl,middleware): 为核心模块添加详细 GoDoc 文档注释
- config: 为 Config 和所有子配置结构添加完整文档,包含使用示例和注意事项
- stream: 为负载均衡器和服务器添加详细的参数、返回值和功能说明
- logging: 为日志格式化和输出函数添加文档,说明支持的变量替换
- handler: 为路由器、静态文件和 sendfile 处理器添加文档
- proxy: 为健康检查器和代理功能添加完整文档
- cache/server/ssl/middleware: 补充相关模块的文档注释
- config.example.yaml: 添加可信代理配置、加密套件示例,更新压缩级别说明

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-07 15:36:09 +08:00

410 lines
8.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 security 提供基本认证功能的测试。
//
// 该文件测试基本认证模块的各项功能,包括:
// - 基本认证创建和配置
// - 用户认证验证
// - 密码哈希bcrypt/argon2id
// - 用户添加和删除
// - 凭据提取
//
// 作者xfy
package security
import (
"testing"
"github.com/valyala/fasthttp"
"golang.org/x/crypto/bcrypt"
"rua.plus/lolly/internal/config"
)
func TestNewBasicAuth(t *testing.T) {
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)
tests := []struct {
name string
cfg *config.AuthConfig
wantErr bool
}{
{
name: "nil config",
cfg: nil,
wantErr: true,
},
{
name: "invalid type",
cfg: &config.AuthConfig{
Type: "digest",
},
wantErr: true,
},
{
name: "no users",
cfg: &config.AuthConfig{
Type: "basic",
},
wantErr: true,
},
{
name: "empty username",
cfg: &config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "", Password: string(hashedPassword)},
},
},
wantErr: true,
},
{
name: "empty password",
cfg: &config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: ""},
},
},
wantErr: true,
},
{
name: "valid config",
cfg: &config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
},
},
{
name: "valid with bcrypt",
cfg: &config.AuthConfig{
Type: "basic",
Algorithm: "bcrypt",
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
},
},
{
name: "valid with argon2id format",
cfg: &config.AuthConfig{
Type: "basic",
Algorithm: "argon2id",
Users: []config.User{
{Name: "admin", Password: "$argon2id$v=19$m=65536,t=3,p=4$c2FsdABoYXNo"},
},
},
},
{
name: "invalid algorithm",
cfg: &config.AuthConfig{
Type: "basic",
Algorithm: "md5",
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
auth, err := NewBasicAuth(tt.cfg)
if (err != nil) != tt.wantErr {
t.Errorf("NewBasicAuth() error = %v, wantErr %v", err, tt.wantErr)
}
if !tt.wantErr && auth == nil {
t.Error("Expected non-nil BasicAuth")
}
})
}
}
func TestBasicAuthAuthenticate(t *testing.T) {
password := "testpassword"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
tests := []struct {
name string
username string
password string
expected bool
}{
{
name: "valid credentials",
username: "admin",
password: password,
expected: true,
},
{
name: "wrong password",
username: "admin",
password: "wrongpassword",
expected: false,
},
{
name: "unknown user",
username: "unknown",
password: password,
expected: false,
},
{
name: "empty username",
username: "",
password: password,
expected: false,
},
{
name: "empty password",
username: "admin",
password: "",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := auth.Authenticate(tt.username, tt.password)
if result != tt.expected {
t.Errorf("Authenticate(%s, ***) = %v, expected %v", tt.username, result, tt.expected)
}
})
}
}
func TestBasicAuthProcess(t *testing.T) {
password := "testpassword"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
RequireTLS: false, // Disable TLS for testing
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
Realm: "Test Realm",
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
nextHandler := func(ctx *fasthttp.RequestCtx) {
_, _ = ctx.WriteString("OK")
}
handler := auth.Process(nextHandler)
if handler == nil {
t.Error("Process() returned nil handler")
}
}
func TestBasicAuthAddUser(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: "$2b$12$existinghash"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
// Test adding user
err = auth.AddUser("newuser", "$2b$12$newhash")
if err != nil {
t.Errorf("AddUser() error: %v", err)
}
if !auth.HasUser("newuser") {
t.Error("Expected newuser to exist")
}
// Test empty username
err = auth.AddUser("", "$2b$12$hash")
if err == nil {
t.Error("Expected error for empty username")
}
// Test invalid hash format
err = auth.AddUser("user2", "invalidhash")
if err == nil {
t.Error("Expected error for invalid hash")
}
}
func TestBasicAuthRemoveUser(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: "$2b$12$hash"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
// Remove existing user
auth.RemoveUser("admin")
if auth.HasUser("admin") {
t.Error("Expected admin to be removed")
}
// Remove non-existent user (should not error)
auth.RemoveUser("nonexistent")
}
func TestBasicAuthUserCount(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "user1", Password: "$2b$12$hash1"},
{Name: "user2", Password: "$2b$12$hash2"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
if count := auth.UserCount(); count != 2 {
t.Errorf("Expected UserCount 2, got %d", count)
}
_ = auth.AddUser("user3", "$2b$12$hash3")
if count := auth.UserCount(); count != 3 {
t.Errorf("Expected UserCount 3, got %d", count)
}
auth.RemoveUser("user1")
if count := auth.UserCount(); count != 2 {
t.Errorf("Expected UserCount 2, got %d", count)
}
}
func TestHashPasswordBcrypt(t *testing.T) {
password := "testpassword"
hash, err := HashPasswordBcrypt(password, bcrypt.DefaultCost)
if err != nil {
t.Fatalf("HashPasswordBcrypt() error: %v", err)
}
if hash == "" {
t.Error("Expected non-empty hash")
}
// Verify the hash works
err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
if err != nil {
t.Errorf("Hash verification failed: %v", err)
}
}
func TestValidatePasswordHash(t *testing.T) {
tests := []struct {
name string
hash string
algorithm HashAlgorithm
wantErr bool
}{
{
name: "valid bcrypt",
hash: "$2b$12$hash",
algorithm: HashBcrypt,
},
{
name: "invalid bcrypt format",
hash: "nothere",
algorithm: HashBcrypt,
wantErr: true,
},
{
name: "valid argon2id",
hash: "$argon2id$v=19$m=65536,t=3,p=4$salt$hash",
algorithm: HashArgon2id,
},
{
name: "invalid argon2id format",
hash: "$bcrypt$hash",
algorithm: HashArgon2id,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validatePasswordHash(tt.hash, tt.algorithm)
if (err != nil) != tt.wantErr {
t.Errorf("validatePasswordHash() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestExtractCredentials(t *testing.T) {
password := "testpassword"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
RequireTLS: false,
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
// Create a mock request context
ctx := &fasthttp.RequestCtx{}
// Test without Authorization header
_, _, ok := auth.extractCredentials(ctx)
if ok {
t.Error("Expected no credentials without header")
}
// Test with valid Basic auth header
ctx.Request.Header.Set("Authorization", "Basic YWRtaW46dGVzdHBhc3N3b3Jk")
username, pwd, ok := auth.extractCredentials(ctx)
if !ok {
t.Error("Expected credentials to be extracted")
}
if username != "admin" {
t.Errorf("Expected username 'admin', got %s", username)
}
if pwd != "testpassword" {
t.Errorf("Expected password 'testpassword', got %s", pwd)
}
}
func TestName(t *testing.T) {
password := "test"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
if auth.Name() != "basic_auth" {
t.Errorf("Expected name 'basic_auth', got %s", auth.Name())
}
}