门禁兼容修复
This commit is contained in:
1 parent
f60b416949
commit
801c0af692
18 files changed
+257
-87
No files matched your search
+16
-11
@@ -41,6 +41,9 @@ func New(ctx context.Context, cfg config.Config) (*Application, error) {
|
||||
if !cfg.SuperAgent.Enabled {
|
||||
return nil, fmt.Errorf("initialize chat: SuperAgent must be enabled")
|
||||
}
|
||||
if cfg.Chat.AllowLegacyShortToken {
|
||||
log.Printf("security warning: legacy short chat auth token compatibility is enabled")
|
||||
}
|
||||
superAgentClient, err := superagent.NewHTTPClient(superagent.Config{
|
||||
Enabled: cfg.SuperAgent.Enabled,
|
||||
BaseURL: cfg.SuperAgent.BaseURL,
|
||||
@@ -67,11 +70,12 @@ func New(ctx context.Context, cfg config.Config) (*Application, error) {
|
||||
return nil, fmt.Errorf("initialize chat service: %w", err)
|
||||
}
|
||||
chatHandler, err := handler.NewChatHandler(chatService, handler.ChatOptions{
|
||||
AuthToken: cfg.Chat.AuthToken,
|
||||
AllowedOrigins: cfg.Chat.AllowedOrigins,
|
||||
MaxBodyBytes: cfg.Chat.MaxBodyBytes,
|
||||
RunTimeout: cfg.Chat.RunTimeout,
|
||||
Logger: log.Default(),
|
||||
AuthToken: cfg.Chat.AuthToken,
|
||||
AllowLegacyShortToken: cfg.Chat.AllowLegacyShortToken,
|
||||
AllowedOrigins: cfg.Chat.AllowedOrigins,
|
||||
MaxBodyBytes: cfg.Chat.MaxBodyBytes,
|
||||
RunTimeout: cfg.Chat.RunTimeout,
|
||||
Logger: log.Default(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initialize chat handler: %w", err)
|
||||
@@ -79,12 +83,13 @@ func New(ctx context.Context, cfg config.Config) (*Application, error) {
|
||||
routerOptions.Chat = chatHandler
|
||||
if cfg.Chat.CompatAppID != "" {
|
||||
dashScopeChatHandler, err := handler.NewDashScopeChatHandler(chatService, handler.DashScopeChatOptions{
|
||||
AppID: cfg.Chat.CompatAppID,
|
||||
AuthToken: cfg.Chat.AuthToken,
|
||||
AllowedOrigins: cfg.Chat.AllowedOrigins,
|
||||
MaxBodyBytes: cfg.Chat.MaxBodyBytes,
|
||||
RunTimeout: cfg.Chat.RunTimeout,
|
||||
Logger: log.Default(),
|
||||
AppID: cfg.Chat.CompatAppID,
|
||||
AuthToken: cfg.Chat.AuthToken,
|
||||
AllowLegacyShortToken: cfg.Chat.AllowLegacyShortToken,
|
||||
AllowedOrigins: cfg.Chat.AllowedOrigins,
|
||||
MaxBodyBytes: cfg.Chat.MaxBodyBytes,
|
||||
RunTimeout: cfg.Chat.RunTimeout,
|
||||
Logger: log.Default(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initialize DashScope-compatible chat handler: %w", err)
|
||||
|
||||
+24
-10
@@ -1,8 +1,10 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -43,6 +45,11 @@ func TestNewKeepsExternalIntegrationsDisabledByDefault(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewWiresChatAPIToSuperAgent(t *testing.T) {
|
||||
var applicationLogs bytes.Buffer
|
||||
originalLogOutput := log.Writer()
|
||||
log.SetOutput(&applicationLogs)
|
||||
t.Cleanup(func() { log.SetOutput(originalLogOutput) })
|
||||
|
||||
provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Authorization") != "Bearer provider-open-api-key" {
|
||||
t.Errorf("provider Authorization = %q", r.Header.Get("Authorization"))
|
||||
@@ -77,14 +84,15 @@ func TestNewWiresChatAPIToSuperAgent(t *testing.T) {
|
||||
MaxMessageBytes: 4096,
|
||||
},
|
||||
Chat: config.ChatConfig{
|
||||
Enabled: true,
|
||||
AuthToken: "0123456789abcdef0123456789abcdef",
|
||||
SubjectID: "app-chat-test-subject",
|
||||
CompatAppID: "fire-safety-app",
|
||||
MaxBodyBytes: 4096,
|
||||
RunTimeout: time.Minute,
|
||||
SessionTTL: time.Minute,
|
||||
MaxSessions: 10,
|
||||
Enabled: true,
|
||||
AuthToken: "delivered-key",
|
||||
AllowLegacyShortToken: true,
|
||||
SubjectID: "app-chat-test-subject",
|
||||
CompatAppID: "fire-safety-app",
|
||||
MaxBodyBytes: 4096,
|
||||
RunTimeout: time.Minute,
|
||||
SessionTTL: time.Minute,
|
||||
MaxSessions: 10,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -93,7 +101,7 @@ func TestNewWiresChatAPIToSuperAgent(t *testing.T) {
|
||||
defer application.close()
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/chat", strings.NewReader(`{"message":"你好"}`))
|
||||
request.Header.Set("Authorization", "Bearer 0123456789abcdef0123456789abcdef")
|
||||
request.Header.Set("Authorization", "Bearer delivered-key")
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("Accept", "text/event-stream")
|
||||
response := httptest.NewRecorder()
|
||||
@@ -105,7 +113,7 @@ func TestNewWiresChatAPIToSuperAgent(t *testing.T) {
|
||||
}
|
||||
|
||||
compatRequest := httptest.NewRequest(http.MethodPost, "/api/v1/apps/fire-safety-app/completion", strings.NewReader(`{"input":{"prompt":"你好"},"parameters":{}}`))
|
||||
compatRequest.Header.Set("xtoken", "0123456789abcdef0123456789abcdef")
|
||||
compatRequest.Header.Set("xtoken", "delivered-key")
|
||||
compatRequest.Header.Set("Content-Type", "application/json")
|
||||
compatResponse := httptest.NewRecorder()
|
||||
application.server.Handler.ServeHTTP(compatResponse, compatRequest)
|
||||
@@ -113,4 +121,10 @@ func TestNewWiresChatAPIToSuperAgent(t *testing.T) {
|
||||
!strings.Contains(compatResponse.Body.String(), `"finish_reason":"stop"`) || !strings.Contains(compatResponse.Body.String(), "测试回答") {
|
||||
t.Fatalf("compat chat status=%d body=%s", compatResponse.Code, compatResponse.Body.String())
|
||||
}
|
||||
if !strings.Contains(applicationLogs.String(), "legacy short chat auth token compatibility is enabled") {
|
||||
t.Fatalf("application logs did not contain legacy compatibility warning: %s", applicationLogs.String())
|
||||
}
|
||||
if strings.Contains(applicationLogs.String(), "delivered-key") {
|
||||
t.Fatal("application logs leaked the chat auth token")
|
||||
}
|
||||
}
|
||||
+46
-29
@@ -25,15 +25,16 @@ const (
|
||||
SuperAgentProbeSubjectIDEnv = "FIRE_SAFETY_SUPERAGENT_PROBE_SUBJECT_ID"
|
||||
SuperAgentProbeTimeoutEnv = "FIRE_SAFETY_SUPERAGENT_PROBE_TIMEOUT"
|
||||
|
||||
ChatEnabledEnv = "FIRE_SAFETY_CHAT_ENABLED"
|
||||
ChatAuthTokenEnv = "FIRE_SAFETY_CHAT_AUTH_TOKEN"
|
||||
ChatSubjectIDEnv = "FIRE_SAFETY_CHAT_SUBJECT_ID"
|
||||
ChatAllowedOriginsEnv = "FIRE_SAFETY_CHAT_ALLOWED_ORIGINS"
|
||||
ChatMaxBodyBytesEnv = "FIRE_SAFETY_CHAT_MAX_BODY_BYTES"
|
||||
ChatRunTimeoutEnv = "FIRE_SAFETY_CHAT_RUN_TIMEOUT"
|
||||
ChatSessionTTLEnv = "FIRE_SAFETY_CHAT_SESSION_TTL"
|
||||
ChatMaxSessionsEnv = "FIRE_SAFETY_CHAT_MAX_SESSIONS"
|
||||
ChatCompatAppIDEnv = "FIRE_SAFETY_CHAT_COMPAT_APP_ID"
|
||||
ChatEnabledEnv = "FIRE_SAFETY_CHAT_ENABLED"
|
||||
ChatAuthTokenEnv = "FIRE_SAFETY_CHAT_AUTH_TOKEN"
|
||||
ChatAllowLegacyShortTokenEnv = "FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN"
|
||||
ChatSubjectIDEnv = "FIRE_SAFETY_CHAT_SUBJECT_ID"
|
||||
ChatAllowedOriginsEnv = "FIRE_SAFETY_CHAT_ALLOWED_ORIGINS"
|
||||
ChatMaxBodyBytesEnv = "FIRE_SAFETY_CHAT_MAX_BODY_BYTES"
|
||||
ChatRunTimeoutEnv = "FIRE_SAFETY_CHAT_RUN_TIMEOUT"
|
||||
ChatSessionTTLEnv = "FIRE_SAFETY_CHAT_SESSION_TTL"
|
||||
ChatMaxSessionsEnv = "FIRE_SAFETY_CHAT_MAX_SESSIONS"
|
||||
ChatCompatAppIDEnv = "FIRE_SAFETY_CHAT_COMPAT_APP_ID"
|
||||
|
||||
MCPEnabledEnv = "FIRE_SAFETY_MCP_ENABLED"
|
||||
MCPAuthTokenEnv = "FIRE_SAFETY_MCP_AUTH_TOKEN"
|
||||
@@ -117,15 +118,16 @@ type SuperAgentConfig struct {
|
||||
// conversation settings. The static credential is a test-stage boundary, not
|
||||
// final end-user authentication.
|
||||
type ChatConfig struct {
|
||||
Enabled bool
|
||||
AuthToken string
|
||||
SubjectID string
|
||||
CompatAppID string
|
||||
AllowedOrigins []string
|
||||
MaxBodyBytes int64
|
||||
RunTimeout time.Duration
|
||||
SessionTTL time.Duration
|
||||
MaxSessions int
|
||||
Enabled bool
|
||||
AuthToken string
|
||||
AllowLegacyShortToken bool
|
||||
SubjectID string
|
||||
CompatAppID string
|
||||
AllowedOrigins []string
|
||||
MaxBodyBytes int64
|
||||
RunTimeout time.Duration
|
||||
SessionTTL time.Duration
|
||||
MaxSessions int
|
||||
}
|
||||
|
||||
// MCPConfig contains inbound SuperAgent MCP settings.
|
||||
@@ -229,6 +231,10 @@ func loadChatConfig() (ChatConfig, error) {
|
||||
if err != nil {
|
||||
return ChatConfig{}, err
|
||||
}
|
||||
allowLegacyShortToken, err := parseBool(ChatAllowLegacyShortTokenEnv, false)
|
||||
if err != nil {
|
||||
return ChatConfig{}, err
|
||||
}
|
||||
maxBodyBytes, err := parseInt64(ChatMaxBodyBytesEnv, defaultChatMaxBodyBytes)
|
||||
if err != nil {
|
||||
return ChatConfig{}, err
|
||||
@@ -251,15 +257,16 @@ func loadChatConfig() (ChatConfig, error) {
|
||||
}
|
||||
|
||||
cfg := ChatConfig{
|
||||
Enabled: enabled,
|
||||
AuthToken: strings.TrimSpace(os.Getenv(ChatAuthTokenEnv)),
|
||||
SubjectID: valueOrDefault(ChatSubjectIDEnv, defaultChatSubjectID),
|
||||
CompatAppID: strings.TrimSpace(os.Getenv(ChatCompatAppIDEnv)),
|
||||
AllowedOrigins: allowedOrigins,
|
||||
MaxBodyBytes: maxBodyBytes,
|
||||
RunTimeout: runTimeout,
|
||||
SessionTTL: sessionTTL,
|
||||
MaxSessions: int(maxSessions),
|
||||
Enabled: enabled,
|
||||
AuthToken: os.Getenv(ChatAuthTokenEnv),
|
||||
AllowLegacyShortToken: allowLegacyShortToken,
|
||||
SubjectID: valueOrDefault(ChatSubjectIDEnv, defaultChatSubjectID),
|
||||
CompatAppID: strings.TrimSpace(os.Getenv(ChatCompatAppIDEnv)),
|
||||
AllowedOrigins: allowedOrigins,
|
||||
MaxBodyBytes: maxBodyBytes,
|
||||
RunTimeout: runTimeout,
|
||||
SessionTTL: sessionTTL,
|
||||
MaxSessions: int(maxSessions),
|
||||
}
|
||||
if cfg.MaxBodyBytes <= 0 || cfg.MaxBodyBytes > maximumChatBodyBytes {
|
||||
return ChatConfig{}, fmt.Errorf("%s must be between 1 and %d", ChatMaxBodyBytesEnv, maximumChatBodyBytes)
|
||||
@@ -279,12 +286,22 @@ func loadChatConfig() (ChatConfig, error) {
|
||||
if cfg.CompatAppID != "" && !validChatCompatAppID(cfg.CompatAppID) {
|
||||
return ChatConfig{}, fmt.Errorf("%s must contain 1 to 128 ASCII letters, digits, underscores, or hyphens", ChatCompatAppIDEnv)
|
||||
}
|
||||
if cfg.Enabled && (len(cfg.AuthToken) < 32 || !validSecretHeaderValue(cfg.AuthToken)) {
|
||||
return ChatConfig{}, fmt.Errorf("%s must contain at least 32 printable ASCII characters", ChatAuthTokenEnv)
|
||||
if cfg.Enabled && !validChatAuthToken(cfg.AuthToken, cfg.AllowLegacyShortToken) {
|
||||
if cfg.AllowLegacyShortToken {
|
||||
return ChatConfig{}, fmt.Errorf("%s must contain 1 to 4096 printable ASCII characters when %s is true", ChatAuthTokenEnv, ChatAllowLegacyShortTokenEnv)
|
||||
}
|
||||
return ChatConfig{}, fmt.Errorf("%s must contain 32 to 4096 printable ASCII characters", ChatAuthTokenEnv)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func validChatAuthToken(value string, allowLegacyShortToken bool) bool {
|
||||
if !validSecretHeaderValue(value) {
|
||||
return false
|
||||
}
|
||||
return allowLegacyShortToken || len(value) >= 32
|
||||
}
|
||||
|
||||
func validChatCompatAppID(value string) bool {
|
||||
if value == "" || len(value) > 128 {
|
||||
return false
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -40,6 +41,9 @@ func TestLoadDefaults(t *testing.T) {
|
||||
if cfg.Chat.Enabled {
|
||||
t.Fatal("Chat.Enabled = true, want false")
|
||||
}
|
||||
if cfg.Chat.AllowLegacyShortToken {
|
||||
t.Fatal("Chat.AllowLegacyShortToken = true, want false")
|
||||
}
|
||||
if cfg.Chat.SubjectID != "fire-safety-ymd-chat-test-subject" || cfg.Chat.MaxBodyBytes != 128*1024 ||
|
||||
cfg.Chat.CompatAppID != "" || cfg.Chat.RunTimeout != 10*time.Minute || cfg.Chat.SessionTTL != 30*time.Minute || cfg.Chat.MaxSessions != 1000 {
|
||||
t.Fatalf("unexpected Chat defaults: %#v", cfg.Chat)
|
||||
@@ -209,6 +213,7 @@ func TestLoadRejectsInvalidValues(t *testing.T) {
|
||||
{name: "probe timeout syntax", key: SuperAgentProbeTimeoutEnv, value: "forever"},
|
||||
{name: "probe timeout non-positive", key: SuperAgentProbeTimeoutEnv, value: "0s"},
|
||||
{name: "Chat boolean", key: ChatEnabledEnv, value: "sometimes"},
|
||||
{name: "Chat legacy short token boolean", key: ChatAllowLegacyShortTokenEnv, value: "sometimes"},
|
||||
{name: "Chat body syntax", key: ChatMaxBodyBytesEnv, value: "large"},
|
||||
{name: "Chat body non-positive", key: ChatMaxBodyBytesEnv, value: "0"},
|
||||
{name: "Chat body too large", key: ChatMaxBodyBytesEnv, value: "1048577"},
|
||||
@@ -300,6 +305,57 @@ func TestLoadRequiresEnabledChatSettings(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadChatLegacyShortTokenPolicy(t *testing.T) {
|
||||
configureChat := func(t *testing.T, token string, allowLegacy bool) {
|
||||
t.Helper()
|
||||
t.Setenv(SuperAgentEnabledEnv, "true")
|
||||
t.Setenv(SuperAgentBaseURLEnv, "https://superagent.example.test")
|
||||
t.Setenv(SuperAgentOpenAPIKeyEnv, "different-superagent-open-api-key")
|
||||
t.Setenv(ChatEnabledEnv, "true")
|
||||
t.Setenv(ChatAuthTokenEnv, token)
|
||||
t.Setenv(ChatAllowLegacyShortTokenEnv, strconv.FormatBool(allowLegacy))
|
||||
}
|
||||
|
||||
t.Run("default rejects short token", func(t *testing.T) {
|
||||
clearEnvironment(t)
|
||||
configureChat(t, "delivered-key", false)
|
||||
if _, err := Load(); err == nil || !strings.Contains(err.Error(), ChatAuthTokenEnv) {
|
||||
t.Fatalf("Load() error = %v, want short token rejection", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("explicit compatibility allows short token", func(t *testing.T) {
|
||||
clearEnvironment(t)
|
||||
configureChat(t, "delivered-key", true)
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error = %v", err)
|
||||
}
|
||||
if !cfg.Chat.AllowLegacyShortToken || cfg.Chat.AuthToken != "delivered-key" {
|
||||
t.Fatalf("unexpected Chat config: %#v", cfg.Chat)
|
||||
}
|
||||
})
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
token string
|
||||
}{
|
||||
{name: "empty", token: ""},
|
||||
{name: "space", token: "delivered key"},
|
||||
{name: "control character", token: "delivered\tkey"},
|
||||
{name: "Unicode", token: "delivered密钥"},
|
||||
{name: "oversized", token: strings.Repeat("a", 4097)},
|
||||
} {
|
||||
t.Run("compatibility rejects "+tt.name, func(t *testing.T) {
|
||||
clearEnvironment(t)
|
||||
configureChat(t, tt.token, true)
|
||||
if _, err := Load(); err == nil || !strings.Contains(err.Error(), ChatAuthTokenEnv) {
|
||||
t.Fatalf("Load() error = %v, want unsafe token rejection", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRequiresEnabledMCPSettings(t *testing.T) {
|
||||
validToken := "0123456789abcdef0123456789abcdef"
|
||||
validDSN := "postgresql://fire:secret@127.0.0.1:5432/fire_safety?sslmode=disable"
|
||||
@@ -318,6 +374,7 @@ func TestLoadRequiresEnabledMCPSettings(t *testing.T) {
|
||||
{
|
||||
name: "short token",
|
||||
configure: func(t *testing.T) {
|
||||
t.Setenv(ChatAllowLegacyShortTokenEnv, "true")
|
||||
t.Setenv(MCPAuthTokenEnv, "short-token")
|
||||
t.Setenv(MCPAllowedTownsEnv, "莒格庄镇")
|
||||
},
|
||||
@@ -465,6 +522,7 @@ func clearEnvironment(t *testing.T) {
|
||||
SuperAgentProbeTimeoutEnv,
|
||||
ChatEnabledEnv,
|
||||
ChatAuthTokenEnv,
|
||||
ChatAllowLegacyShortTokenEnv,
|
||||
ChatSubjectIDEnv,
|
||||
ChatAllowedOriginsEnv,
|
||||
ChatMaxBodyBytesEnv,
|
||||
|
||||
@@ -32,11 +32,12 @@ type ChatUseCase interface {
|
||||
|
||||
// ChatOptions contains the independently authenticated user-facing transport settings.
|
||||
type ChatOptions struct {
|
||||
AuthToken string
|
||||
AllowedOrigins []string
|
||||
MaxBodyBytes int64
|
||||
RunTimeout time.Duration
|
||||
Logger *log.Logger
|
||||
AuthToken string
|
||||
AllowLegacyShortToken bool
|
||||
AllowedOrigins []string
|
||||
MaxBodyBytes int64
|
||||
RunTimeout time.Duration
|
||||
Logger *log.Logger
|
||||
}
|
||||
|
||||
// ChatHandler exposes a bounded SSE chat API without exposing provider credentials or sessions.
|
||||
@@ -54,8 +55,8 @@ func NewChatHandler(chat ChatUseCase, options ChatOptions) (*ChatHandler, error)
|
||||
if chat == nil {
|
||||
return nil, errors.New("chat service is required")
|
||||
}
|
||||
if !validChatToken(options.AuthToken) {
|
||||
return nil, errors.New("chat auth token must contain at least 32 printable ASCII characters")
|
||||
if !validChatToken(options.AuthToken, options.AllowLegacyShortToken) {
|
||||
return nil, errors.New("chat auth token is outside the configured printable ASCII length policy")
|
||||
}
|
||||
if options.MaxBodyBytes <= 0 || options.MaxBodyBytes > maximumChatBodyBytes {
|
||||
return nil, errors.New("chat max body bytes is outside the supported range")
|
||||
@@ -389,8 +390,8 @@ func publicChatError(err error) (int, string, string) {
|
||||
}
|
||||
}
|
||||
|
||||
func validChatToken(value string) bool {
|
||||
if len(value) < 32 || len(value) > 4096 {
|
||||
func validChatToken(value string, allowLegacyShortToken bool) bool {
|
||||
if value == "" || len(value) > 4096 || (!allowLegacyShortToken && len(value) < 32) {
|
||||
return false
|
||||
}
|
||||
for _, character := range value {
|
||||
|
||||
@@ -19,6 +19,10 @@ const testChatToken = "abcdef0123456789abcdef0123456789"
|
||||
func TestNewChatHandlerRejectsUnsafeOptions(t *testing.T) {
|
||||
tests := []ChatOptions{
|
||||
{AuthToken: "short", MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AuthToken: "", AllowLegacyShortToken: true, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AuthToken: "delivered\tkey", AllowLegacyShortToken: true, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AuthToken: "delivered密钥", AllowLegacyShortToken: true, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AuthToken: strings.Repeat("a", 4097), AllowLegacyShortToken: true, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AuthToken: testChatToken, MaxBodyBytes: maximumChatBodyBytes + 1, RunTimeout: time.Second},
|
||||
{AuthToken: testChatToken, MaxBodyBytes: 1, RunTimeout: maximumChatRunTime + time.Second},
|
||||
{AuthToken: testChatToken, AllowedOrigins: []string{"*"}, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
@@ -31,6 +35,18 @@ func TestNewChatHandlerRejectsUnsafeOptions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewChatHandlerAllowsExplicitLegacyShortToken(t *testing.T) {
|
||||
_, err := NewChatHandler(&fakeChatUseCase{}, ChatOptions{
|
||||
AuthToken: "delivered-key",
|
||||
AllowLegacyShortToken: true,
|
||||
MaxBodyBytes: 1,
|
||||
RunTimeout: time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewChatHandler() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatTransportGuardsRunBeforeService(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -20,12 +20,13 @@ import (
|
||||
// transport. Its token authenticates callers of this service and is never a
|
||||
// provider credential.
|
||||
type DashScopeChatOptions struct {
|
||||
AppID string
|
||||
AuthToken string
|
||||
AllowedOrigins []string
|
||||
MaxBodyBytes int64
|
||||
RunTimeout time.Duration
|
||||
Logger *log.Logger
|
||||
AppID string
|
||||
AuthToken string
|
||||
AllowLegacyShortToken bool
|
||||
AllowedOrigins []string
|
||||
MaxBodyBytes int64
|
||||
RunTimeout time.Duration
|
||||
Logger *log.Logger
|
||||
}
|
||||
|
||||
// DashScopeChatHandler adapts the provider-neutral ChatUseCase to the narrow
|
||||
@@ -48,8 +49,8 @@ func NewDashScopeChatHandler(chat ChatUseCase, options DashScopeChatOptions) (*D
|
||||
if !validDashScopeAppID(options.AppID) {
|
||||
return nil, errors.New("DashScope-compatible app ID is invalid")
|
||||
}
|
||||
if !validChatToken(options.AuthToken) {
|
||||
return nil, errors.New("DashScope-compatible auth token must contain at least 32 printable ASCII characters")
|
||||
if !validChatToken(options.AuthToken, options.AllowLegacyShortToken) {
|
||||
return nil, errors.New("DashScope-compatible auth token is outside the configured printable ASCII length policy")
|
||||
}
|
||||
if options.MaxBodyBytes <= 0 || options.MaxBodyBytes > maximumChatBodyBytes {
|
||||
return nil, errors.New("DashScope-compatible max body bytes is outside the supported range")
|
||||
|
||||
@@ -22,6 +22,10 @@ func TestNewDashScopeChatHandlerRejectsUnsafeOptions(t *testing.T) {
|
||||
{AppID: "app/other", AuthToken: testChatToken, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AppID: "app.other", AuthToken: testChatToken, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AppID: testDashScopeAppID, AuthToken: "short", MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AppID: testDashScopeAppID, AuthToken: "", AllowLegacyShortToken: true, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AppID: testDashScopeAppID, AuthToken: "delivered\tkey", AllowLegacyShortToken: true, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AppID: testDashScopeAppID, AuthToken: "delivered密钥", AllowLegacyShortToken: true, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AppID: testDashScopeAppID, AuthToken: strings.Repeat("a", 4097), AllowLegacyShortToken: true, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
{AppID: testDashScopeAppID, AuthToken: testChatToken, MaxBodyBytes: maximumChatBodyBytes + 1, RunTimeout: time.Second},
|
||||
{AppID: testDashScopeAppID, AuthToken: testChatToken, MaxBodyBytes: 1, RunTimeout: maximumChatRunTime + time.Second},
|
||||
{AppID: testDashScopeAppID, AuthToken: testChatToken, AllowedOrigins: []string{"*"}, MaxBodyBytes: 1, RunTimeout: time.Second},
|
||||
@@ -38,6 +42,19 @@ func TestNewDashScopeChatHandlerRejectsUnsafeOptions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDashScopeChatHandlerAllowsExplicitLegacyShortToken(t *testing.T) {
|
||||
_, err := NewDashScopeChatHandler(&fakeChatUseCase{}, DashScopeChatOptions{
|
||||
AppID: testDashScopeAppID,
|
||||
AuthToken: "delivered-key",
|
||||
AllowLegacyShortToken: true,
|
||||
MaxBodyBytes: 1,
|
||||
RunTimeout: time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewDashScopeChatHandler() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDashScopeChatStreamsCompatibleResultAfterStrictSuccess(t *testing.T) {
|
||||
turn := &fakeChatTurn{
|
||||
conversationID: "conv_0123456789abcdef01234567",
|
||||
|
||||
Reference in new issue
Block a user