返回 DeepSeek-Reasonix
auth.go
根目录 / internal / mcpdiag / auth.go
1 package mcpdiag
2
3 import (
4 "net/url"
5 "strings"
6 )
7
8 const (
9 AuthNone = "none"
10 AuthPossible = "possible"
11 AuthRequired = "required"
12 )
13
14 type AuthDiagnosis struct {
15 Status string
16 URL string
17 }
18
19 func DiagnoseAuth(transport, status, errText, url string, authConfigured bool) AuthDiagnosis {
20 if IsAuthFailure(errText) {
21 return AuthDiagnosis{Status: AuthRequired, URL: remoteAuthURL(transport, url)}
22 }
23 if authConfigured || !isRemoteTransport(transport) || !looksLikeHTTPURL(url) || strings.TrimSpace(errText) != "" {
24 return AuthDiagnosis{Status: AuthNone}
25 }
26 switch strings.ToLower(strings.TrimSpace(status)) {
27 case "connected", "failed":
28 return AuthDiagnosis{Status: AuthNone}
29 case "deferred", "initializing", "disabled":
30 return AuthDiagnosis{Status: AuthPossible, URL: strings.TrimSpace(url)}
31 default:
32 return AuthDiagnosis{Status: AuthNone}
33 }
34 }
35
36 func IsAuthFailure(errText string) bool {
37 lower := strings.ToLower(errText)
38 for _, needle := range []string{
39 "401",
40 "403",
41 "unauthorized",
42 "forbidden",
43 "invalid token",
44 "login required",
45 "authentication",
46 "not authenticated",
47 } {
48 if strings.Contains(lower, needle) {
49 return true
50 }
51 }
52 return false
53 }
54
55 func HasAuthConfig(headers, env map[string]string, url string) bool {
56 for k, v := range headers {
57 if strings.TrimSpace(k) == "" {
58 continue
59 }
60 if strings.TrimSpace(v) != "" && (isAuthish(k) || containsExplicitAuthMaterial(v)) {
61 return true
62 }
63 }
64 if containsAuthMaterial(url) {
65 return true
66 }
67 for k, v := range env {
68 if strings.TrimSpace(v) == "" {
69 continue
70 }
71 if isAuthish(k) || containsAuthMaterial(v) {
72 return true
73 }
74 }
75 return false
76 }
77
78 func ClearAuthConfig(headers, env map[string]string, rawURL string) (map[string]string, map[string]string, string, bool) {
79 cleanHeaders, changedHeaders := clearAuthMap(headers)
80 cleanEnv, changedEnv := clearAuthMap(env)
81 cleanURL, changedURL := clearAuthURL(rawURL)
82 return cleanHeaders, cleanEnv, cleanURL, changedHeaders || changedEnv || changedURL
83 }
84
85 func IsRemoteTransport(transport string) bool {
86 return isRemoteTransport(transport)
87 }
88
89 func remoteAuthURL(transport, url string) string {
90 if !isRemoteTransport(transport) || !looksLikeHTTPURL(url) {
91 return ""
92 }
93 return strings.TrimSpace(url)
94 }
95
96 func isRemoteTransport(transport string) bool {
97 switch strings.ToLower(strings.TrimSpace(transport)) {
98 case "http", "streamable-http", "sse":
99 return true
100 default:
101 return false
102 }
103 }
104
105 func looksLikeHTTPURL(url string) bool {
106 u := strings.ToLower(strings.TrimSpace(url))
107 return strings.HasPrefix(u, "https://") || strings.HasPrefix(u, "http://")
108 }
109
110 func containsAuthMaterial(s string) bool {
111 lower := strings.ToLower(s)
112 return strings.Contains(lower, "${") || containsExplicitAuthMaterial(lower)
113 }
114
115 func containsExplicitAuthMaterial(s string) bool {
116 lower := strings.ToLower(s)
117 return strings.Contains(lower, "access_token") ||
118 strings.Contains(lower, "id_token") ||
119 strings.Contains(lower, "refresh_token") ||
120 strings.Contains(lower, "api_key") ||
121 strings.Contains(lower, "api-key") ||
122 strings.Contains(lower, "apikey") ||
123 strings.Contains(lower, "bearer ")
124 }
125
126 func isAuthish(key string) bool {
127 lower := strings.ToLower(strings.TrimSpace(key))
128 return strings.Contains(lower, "auth") ||
129 strings.Contains(lower, "token") ||
130 strings.Contains(lower, "secret") ||
131 strings.Contains(lower, "credential") ||
132 strings.Contains(lower, "api_key") ||
133 strings.Contains(lower, "api-key") ||
134 strings.Contains(lower, "apikey") ||
135 strings.Contains(lower, "cookie")
136 }
137
138 func clearAuthMap(in map[string]string) (map[string]string, bool) {
139 if len(in) == 0 {
140 return nil, false
141 }
142 out := make(map[string]string, len(in))
143 changed := false
144 for k, v := range in {
145 if isAuthish(k) || containsExplicitAuthMaterial(v) {
146 changed = true
147 continue
148 }
149 out[k] = v
150 }
151 if len(out) == 0 {
152 out = nil
153 }
154 return out, changed
155 }
156
157 func clearAuthURL(raw string) (string, bool) {
158 trimmed := strings.TrimSpace(raw)
159 if !looksLikeHTTPURL(trimmed) {
160 return raw, false
161 }
162 u, err := url.Parse(trimmed)
163 if err != nil || u == nil {
164 return raw, false
165 }
166 q := u.Query()
167 changed := false
168 for key := range q {
169 if isAuthQueryKey(key) {
170 q.Del(key)
171 changed = true
172 }
173 }
174 if !changed {
175 return raw, false
176 }
177 u.RawQuery = q.Encode()
178 return u.String(), true
179 }
180
181 func isAuthQueryKey(key string) bool {
182 lower := strings.ToLower(strings.TrimSpace(key))
183 return lower == "key" || isAuthish(lower)
184 }
185
185 lines GO