File size: 3,514 Bytes
644c352
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package service

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"opus-api/internal/converter"
	"opus-api/internal/model"
	"opus-api/internal/types"
	"time"

	"gorm.io/gorm"
)

// CookieValidator Cookie 验证器
type CookieValidator struct {
	service *CookieService
}

// NewCookieValidator 创建验证器
func NewCookieValidator(service *CookieService) *CookieValidator {
	return &CookieValidator{service: service}
}

// ValidateCookie 验证单个 Cookie
func (v *CookieValidator) ValidateCookie(cookie *model.MorphCookie) bool {
	result := v.testCookie(cookie)

	db := v.service.GetDB()
	if result {
		db.Model(cookie).Updates(map[string]interface{}{
			"is_valid":       true,
			"last_validated": time.Now(),
			"error_count":    0,
		})
	} else {
		db.Model(cookie).Updates(map[string]interface{}{
			"is_valid":       false,
			"last_validated": time.Now(),
			"error_count":    gorm.Expr("error_count + ?", 1),
		})
	}

	return result
}

// ValidateAllCookies 验证用户的所有 Cookie
func (v *CookieValidator) ValidateAllCookies(userID uint) map[uint]bool {
	cookies, err := v.service.ListCookies(userID)
	if err != nil {
		return nil
	}

	results := make(map[uint]bool)
	for _, cookie := range cookies {
		results[cookie.ID] = v.ValidateCookie(&cookie)
	}

	return results
}

// testCookie 测试 Cookie 是否有效(与 /v1/messages 逻辑完全一致)
func (v *CookieValidator) testCookie(cookie *model.MorphCookie) bool {
	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	// 创建 Claude 格式的测试请求
	claudeReq := types.ClaudeRequest{
		Model:     types.DefaultModel,
		MaxTokens: 1024,
		Messages: []types.ClaudeMessage{
			{
				Role:    "user",
				Content: "Hello!",
			},
		},
	}

	// 使用与 /v1/messages 相同的转换逻辑,将 Claude 格式转换为 Morph 格式
	morphReq := converter.ClaudeToMorph(claudeReq)

	reqBody, _ := json.Marshal(morphReq)
	req, err := http.NewRequest("POST", types.MorphAPIURL, bytes.NewReader(reqBody))
	if err != nil {
		return false
	}

	// 使用与 /v1/messages 相同的请求头
	for key, value := range types.MorphHeaders {
		req.Header.Set(key, value)
	}
	// 覆盖 Cookie
	req.Header.Set("cookie", cookie.APIKey)

	resp, err := client.Do(req)
	if err != nil {
		return false
	}
	defer resp.Body.Close()

	// 检查响应状态码
	// 200 OK 表示成功,401/403 表示认证失败
	if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
		return false
	}

	// 尝试读取响应体
	body, _ := io.ReadAll(resp.Body)

	// 检查是否是有效的 API 响应(SSE 流格式)
	bodyStr := string(body)
	
	// Morph API 返回 SSE 流,有效的响应会包含 "data:" 前缀
	if !containsPrefix(bodyStr, "data:") {
		return false
	}

	return true
}

// containsPrefix 检查字符串是否包含指定前缀(忽略空白字符)
func containsPrefix(s, prefix string) bool {
	// 去除前导空白字符
	start := 0
	for start < len(s) && (s[start] == ' ' || s[start] == '\t' || s[start] == '\n' || s[start] == '\r') {
		start++
	}
	
	if start >= len(s) {
		return false
	}
	
	return len(s[start:]) >= len(prefix) && s[start:start+len(prefix)] == prefix
}

// validateCookieQuiet 静默验证 Cookie(不更新数据库)
func (v *CookieValidator) validateCookieQuiet(cookie *model.MorphCookie) bool {
	return v.testCookie(cookie)
}