package openvpn import ( "encoding/base64" "strings" "testing" "time" "github.com/pquerna/otp/totp" "vpn-platform/backend/internal/model" ) func TestValidateOpenVPNModes(t *testing.T) { cfg, err := New().Validate(map[string]interface{}{"auth_mode": "password", "subnet": "10.8.0.0 255.255.255.0"}) if err != nil { t.Fatal(err) } if cfg["proto"] != "udp" || cfg["port"] != 1194 { t.Fatalf("defaults not applied: %#v", cfg) } if _, err := New().Validate(map[string]interface{}{"auth_mode": "unknown"}); err == nil { t.Fatal("expected invalid auth mode error") } // 前端历史值 tcp 应归一为 tcp-server cfg2, err := New().Validate(map[string]interface{}{"proto": "tcp", "subnet": "10.8.0.0 255.255.255.0"}) if err != nil { t.Fatal(err) } if cfg2["proto"] != "tcp-server" { t.Fatalf("tcp not normalized: %#v", cfg2) } cfg3, err := New().Validate(map[string]interface{}{ "subnet": "10.8.0.0 255.255.255.0", "push_routes": "192.168.1.0/24", }) if err != nil { t.Fatal(err) } if cfg3["redirect_gateway"] != false { t.Fatalf("push_routes should default redirect_gateway off: %#v", cfg3) } // MFA 仅允许密码相关模式 if _, err := New().Validate(map[string]interface{}{ "auth_mode": "certificate", "mfa_mode": "required", "subnet": "10.8.0.0 255.255.255.0", }); err == nil { t.Fatal("expected MFA + certificate-only to fail") } cfg4, err := New().Validate(map[string]interface{}{ "auth_mode": "password", "mfa_mode": "required", "subnet": "10.8.0.0 255.255.255.0", "proto": "tcp", }) if err != nil { t.Fatal(err) } if cfg4["proto"] != "tcp-server" || cfg4["mfa_mode"] != "required" { t.Fatalf("mfa/tcp normalize: %#v", cfg4) } } func TestSplitPasswordAndOTP(t *testing.T) { pw, otp := splitPasswordAndOTP("secretpass123456", "") if pw != "secretpass" || otp != "123456" { t.Fatalf("suffix: %q %q", pw, otp) } scrv := "SCRV1:" + base64.StdEncoding.EncodeToString([]byte("mypass")) + ":" + base64.StdEncoding.EncodeToString([]byte("654321")) pw, otp = splitPasswordAndOTP(scrv, "") if pw != "mypass" || otp != "654321" { t.Fatalf("scrv1: %q %q", pw, otp) } pw, otp = splitPasswordAndOTP("onlypass", "111222") if pw != "onlypass" || otp != "111222" { t.Fatalf("challenge line: %q %q", pw, otp) } } func TestClientProto(t *testing.T) { if clientProto("tcp-server") != "tcp-client" || clientProto("udp") != "udp" || clientProto("tcp") != "tcp-client" { t.Fatal("clientProto mapping failed") } } func TestServerConfigIncludesProductionTunings(t *testing.T) { cfg, err := New().Validate(map[string]interface{}{ "auth_mode": "password", "proto": "tcp", "subnet": "10.8.0.0 255.255.240.0", "max_clients": 1000, "mfa_mode": "off", }) if err != nil { t.Fatal(err) } if cfg["proto"] != "tcp-server" { t.Fatalf("proto: %#v", cfg["proto"]) } tunn := &model.Tunnel{ID: 1, Protocol: "openvpn"} raw := New().serverConfig(tunn, cfg) for _, want := range []string{ "proto tcp-server", "tun-mtu 1500", "mssfix 1450", "keepalive 5 30", "reneg-sec 0", "verify-client-cert none", "username-as-common-name", "max-clients 1000", "duplicate-cn", "via-file", } { if !strings.Contains(raw, want) { t.Fatalf("server.conf missing %q\n%s", want, raw) } } if strings.Contains(raw, "via-env") { t.Fatal("should use via-file not via-env") } } func TestValidateTOTPSkew(t *testing.T) { // 使用固定 secret 生成当前码并校验 secret := "JBSWY3DPEHPK3PXP" // "Hello!" base32, common test secret code, err := totp.GenerateCode(secret, time.Now()) if err != nil { t.Fatal(err) } ok, counter := validateTOTPSkew(code, secret, 1) if !ok || counter == 0 { t.Fatalf("expected valid totp, ok=%v counter=%d", ok, counter) } if ok2, _ := validateTOTPSkew("000000", secret, 1); ok2 { t.Fatal("expected invalid code") } } func TestCertificateCNIsSafeAndStable(t *testing.T) { cn := certificateCN("张三 / alice@example.com") if !certificateName.MatchString(cn) || !strings.HasPrefix(cn, "u-") { t.Fatalf("unsafe CN: %q", cn) } }