easyai-ai-gateway/apps/api/internal/config/config_test.go
chengcheng d345c070ae feat: 实现 OIDC 服务端会话与请求刷新
使用 AES-256-GCM 保存认证中心令牌,并以随机 Cookie 哈希关联 PostgreSQL 会话。加入闲置与绝对期限、Refresh Token 轮换以及多实例刷新锁。
2026-07-13 19:09:10 +08:00

139 lines
5.0 KiB
Go

package config
import (
"bytes"
"encoding/base64"
"strings"
"testing"
)
func TestLoadOIDCJITProvisioningDefaultsToDisabled(t *testing.T) {
t.Setenv("OIDC_JIT_PROVISIONING_ENABLED", "")
t.Setenv("OIDC_GATEWAY_TENANT_KEY", "")
cfg := Load()
if cfg.OIDCJITProvisioningEnabled {
t.Fatal("OIDC JIT provisioning must be disabled by default")
}
if cfg.OIDCGatewayTenantKey != "" {
t.Fatalf("unexpected gateway tenant key: %q", cfg.OIDCGatewayTenantKey)
}
}
func TestValidateRequiresGatewayTenantKeyWhenOIDCJITIsEnabled(t *testing.T) {
cfg := Config{
OIDCEnabled: true,
OIDCJITProvisioningEnabled: true,
}
err := cfg.Validate()
if err == nil || !strings.Contains(err.Error(), "OIDC_GATEWAY_TENANT_KEY") {
t.Fatalf("Validate() error = %v, want missing OIDC_GATEWAY_TENANT_KEY", err)
}
cfg.OIDCGatewayTenantKey = "default"
if err := cfg.Validate(); err != nil {
t.Fatalf("Validate() with tenant key: %v", err)
}
}
func TestLoadOIDCBrowserSessionUsesSafeEnvironmentDefaults(t *testing.T) {
t.Setenv("APP_ENV", "development")
t.Setenv("OIDC_BROWSER_SESSION_ENABLED", "")
t.Setenv("OIDC_SESSION_COOKIE_SECURE", "")
cfg := Load()
if !cfg.OIDCBrowserSessionEnabled {
t.Fatal("OIDC browser session should be enabled by default")
}
if cfg.OIDCSessionCookieSecure {
t.Fatal("development cookie should allow localhost HTTP by default")
}
t.Setenv("APP_ENV", "production")
cfg = Load()
if !cfg.OIDCSessionCookieSecure {
t.Fatal("production OIDC session cookie must default to Secure")
}
t.Setenv("APP_ENV", "staging")
cfg = Load()
if !cfg.OIDCSessionCookieSecure {
t.Fatal("staging OIDC session cookie must default to Secure")
}
}
func TestValidateRejectsInsecureNonLocalOIDCBrowserSession(t *testing.T) {
cfg := Config{
AppEnv: "staging",
OIDCEnabled: true,
OIDCBrowserSessionEnabled: true,
OIDCSessionCookieSecure: false,
CORSAllowedOrigin: "https://gateway.example.com",
OIDCClientID: "gateway-public",
OIDCRedirectURI: "https://gateway.example.com/gateway-api/api/v1/auth/oidc/callback",
OIDCPostLogoutRedirectURI: "https://gateway.example.com/",
OIDCSessionEncryptionKey: base64.RawStdEncoding.EncodeToString(bytes.Repeat([]byte{1}, 32)),
OIDCSessionIdleTTLSeconds: 1800,
OIDCSessionAbsoluteTTLSeconds: 28800,
OIDCSessionRefreshBeforeSeconds: 60,
}
if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "OIDC_SESSION_COOKIE_SECURE") {
t.Fatalf("Validate() error = %v, want insecure non-local cookie rejection", err)
}
cfg.OIDCSessionCookieSecure = true
cfg.CORSAllowedOrigin = "*"
if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "CORS_ALLOWED_ORIGIN") {
t.Fatalf("Validate() error = %v, want wildcard credentialed CORS rejection", err)
}
}
func TestValidateRequiresCompletePublicClientSessionConfiguration(t *testing.T) {
cfg := Config{
AppEnv: "test", OIDCEnabled: true, OIDCBrowserSessionEnabled: true,
OIDCSessionCookieSecure: false, CORSAllowedOrigin: "http://localhost:5178",
}
if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "OIDC_CLIENT_ID") {
t.Fatalf("Validate() error = %v, want incomplete public client rejection", err)
}
cfg.OIDCClientID = "gateway-public"
cfg.OIDCRedirectURI = "http://localhost:8088/api/v1/auth/oidc/callback"
cfg.OIDCPostLogoutRedirectURI = "http://localhost:5178/"
cfg.OIDCSessionEncryptionKey = base64.RawStdEncoding.EncodeToString(bytes.Repeat([]byte{2}, 32))
cfg.OIDCSessionIdleTTLSeconds = 1800
cfg.OIDCSessionAbsoluteTTLSeconds = 28800
cfg.OIDCSessionRefreshBeforeSeconds = 60
if err := cfg.Validate(); err != nil {
t.Fatalf("Validate() valid public session config: %v", err)
}
key, err := cfg.OIDCSessionEncryptionKeyBytes()
if err != nil || len(key) != 32 {
t.Fatalf("decoded encryption key len=%d err=%v", len(key), err)
}
}
func TestValidateRejectsPlaintextOrWrongLengthSessionKey(t *testing.T) {
cfg := Config{
AppEnv: "test", OIDCEnabled: true, OIDCBrowserSessionEnabled: true,
OIDCClientID: "gateway-public", OIDCRedirectURI: "http://localhost:8088/callback",
OIDCPostLogoutRedirectURI: "http://localhost:5178/", OIDCSessionEncryptionKey: strings.Repeat("x", 32),
OIDCSessionIdleTTLSeconds: 1800, OIDCSessionAbsoluteTTLSeconds: 28800, OIDCSessionRefreshBeforeSeconds: 60,
CORSAllowedOrigin: "http://localhost:5178",
}
if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "OIDC_SESSION_ENCRYPTION_KEY") {
t.Fatalf("Validate() error = %v, want encoded AES-256 key rejection", err)
}
}
func TestValidateRejectsOfflineAccessForBrowserSession(t *testing.T) {
cfg := Config{
AppEnv: "test", OIDCEnabled: true, OIDCBrowserSessionEnabled: true,
OIDCRequiredScopes: []string{"gateway.access", "offline_access"},
}
if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "offline_access") {
t.Fatalf("Validate() error = %v, want offline_access rejection", err)
}
}