Skip to content

Commit fd812fe

Browse files
authored
Merge pull request #118 from xunxun1982/xxdev
feat: 站点管理签到支持多认证方式
2 parents 2ed8d48 + 1df8f81 commit fd812fe

File tree

12 files changed

+1149
-71
lines changed

12 files changed

+1149
-71
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package sitemanagement
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
)
7+
8+
// AuthConfig represents parsed authentication configuration with multiple auth methods.
9+
// Supports both legacy single-auth format and new multi-auth format.
10+
type AuthConfig struct {
11+
// AuthTypes contains the list of authentication types to try (e.g., ["access_token", "cookie"])
12+
AuthTypes []string
13+
// AuthValues maps auth type to its value (e.g., {"access_token": "xxx", "cookie": "yyy"})
14+
AuthValues map[string]string
15+
}
16+
17+
// parseAuthConfig parses auth_type and auth_value fields into AuthConfig.
18+
// Supports both legacy single-auth format and new multi-auth JSON format.
19+
//
20+
// Legacy format (backward compatible):
21+
// - auth_type: "access_token" or "cookie" or "none"
22+
// - auth_value: single encrypted value
23+
//
24+
// New format (multi-auth):
25+
// - auth_type: "access_token,cookie" (comma-separated)
26+
// - auth_value: JSON string {"access_token":"xxx","cookie":"yyy"}
27+
//
28+
// Returns AuthConfig with parsed types and values.
29+
// If auth_type is "none" or empty, returns empty config.
30+
func parseAuthConfig(authType, decryptedAuthValue string) AuthConfig {
31+
config := AuthConfig{
32+
AuthTypes: []string{},
33+
AuthValues: make(map[string]string),
34+
}
35+
36+
// Parse auth types (comma-separated)
37+
authType = strings.TrimSpace(authType)
38+
if authType == "" || authType == AuthTypeNone {
39+
return config
40+
}
41+
42+
for _, t := range strings.Split(authType, ",") {
43+
t = strings.TrimSpace(t)
44+
if t != "" && t != AuthTypeNone {
45+
config.AuthTypes = append(config.AuthTypes, t)
46+
}
47+
}
48+
49+
if len(config.AuthTypes) == 0 {
50+
return config
51+
}
52+
53+
// Parse auth values
54+
decryptedAuthValue = strings.TrimSpace(decryptedAuthValue)
55+
if decryptedAuthValue == "" {
56+
return config
57+
}
58+
59+
// Try to parse as JSON (new multi-auth format)
60+
var jsonValues map[string]string
61+
if err := json.Unmarshal([]byte(decryptedAuthValue), &jsonValues); err == nil {
62+
// Successfully parsed as JSON - only keep values for configured auth types
63+
for _, t := range config.AuthTypes {
64+
if v, ok := jsonValues[t]; ok {
65+
config.AuthValues[t] = v
66+
}
67+
}
68+
return config
69+
}
70+
71+
// Fallback to legacy single-auth format
72+
// Assign the raw value to the first auth type as a legacy fallback.
73+
// If multiple types were configured but the value isn't JSON, this is
74+
// a best-effort recovery - only the first type gets a credential.
75+
config.AuthValues[config.AuthTypes[0]] = decryptedAuthValue
76+
77+
return config
78+
}
79+
80+
// HasAuthType checks if the config contains the specified auth type.
81+
func (c *AuthConfig) HasAuthType(authType string) bool {
82+
for _, t := range c.AuthTypes {
83+
if t == authType {
84+
return true
85+
}
86+
}
87+
return false
88+
}
89+
90+
// GetAuthValue returns the auth value for the specified type.
91+
// Returns empty string if the type is not found.
92+
func (c *AuthConfig) GetAuthValue(authType string) string {
93+
return c.AuthValues[authType]
94+
}
95+
96+
// IsEmpty returns true if the config has no auth types.
97+
func (c *AuthConfig) IsEmpty() bool {
98+
return len(c.AuthTypes) == 0
99+
}
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
package sitemanagement
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestParseAuthConfig_LegacySingleAuth(t *testing.T) {
11+
t.Parallel()
12+
13+
tests := []struct {
14+
name string
15+
authType string
16+
decryptedValue string
17+
expectedTypes []string
18+
expectedValues map[string]string
19+
expectedIsEmpty bool
20+
expectedHasAccess bool
21+
expectedHasCookie bool
22+
}{
23+
{
24+
name: "legacy access_token",
25+
authType: "access_token",
26+
decryptedValue: "sk-1234567890",
27+
expectedTypes: []string{"access_token"},
28+
expectedValues: map[string]string{"access_token": "sk-1234567890"},
29+
expectedIsEmpty: false,
30+
expectedHasAccess: true,
31+
expectedHasCookie: false,
32+
},
33+
{
34+
name: "legacy cookie",
35+
authType: "cookie",
36+
decryptedValue: "session=abc123; token=xyz",
37+
expectedTypes: []string{"cookie"},
38+
expectedValues: map[string]string{"cookie": "session=abc123; token=xyz"},
39+
expectedIsEmpty: false,
40+
expectedHasAccess: false,
41+
expectedHasCookie: true,
42+
},
43+
{
44+
name: "none auth type",
45+
authType: "none",
46+
decryptedValue: "",
47+
expectedTypes: []string{},
48+
expectedValues: map[string]string{},
49+
expectedIsEmpty: true,
50+
expectedHasAccess: false,
51+
expectedHasCookie: false,
52+
},
53+
{
54+
name: "empty auth type",
55+
authType: "",
56+
decryptedValue: "some-value",
57+
expectedTypes: []string{},
58+
expectedValues: map[string]string{},
59+
expectedIsEmpty: true,
60+
expectedHasAccess: false,
61+
expectedHasCookie: false,
62+
},
63+
{
64+
name: "empty value",
65+
authType: "access_token",
66+
decryptedValue: "",
67+
expectedTypes: []string{"access_token"},
68+
expectedValues: map[string]string{},
69+
expectedIsEmpty: false,
70+
expectedHasAccess: true,
71+
expectedHasCookie: false,
72+
},
73+
}
74+
75+
for _, tt := range tests {
76+
t.Run(tt.name, func(t *testing.T) {
77+
t.Parallel()
78+
79+
config := parseAuthConfig(tt.authType, tt.decryptedValue)
80+
81+
assert.Equal(t, tt.expectedTypes, config.AuthTypes)
82+
assert.Equal(t, tt.expectedValues, config.AuthValues)
83+
assert.Equal(t, tt.expectedIsEmpty, config.IsEmpty())
84+
assert.Equal(t, tt.expectedHasAccess, config.HasAuthType("access_token"))
85+
assert.Equal(t, tt.expectedHasCookie, config.HasAuthType("cookie"))
86+
87+
// Test GetAuthValue
88+
for authType, expectedValue := range tt.expectedValues {
89+
assert.Equal(t, expectedValue, config.GetAuthValue(authType))
90+
}
91+
})
92+
}
93+
}
94+
95+
func TestParseAuthConfig_MultiAuth(t *testing.T) {
96+
t.Parallel()
97+
98+
tests := []struct {
99+
name string
100+
authType string
101+
decryptedValue string
102+
expectedTypes []string
103+
expectedValues map[string]string
104+
expectedIsEmpty bool
105+
expectedHasAccess bool
106+
expectedHasCookie bool
107+
}{
108+
{
109+
name: "multi-auth with both access_token and cookie",
110+
authType: "access_token,cookie",
111+
decryptedValue: func() string {
112+
data := map[string]string{
113+
"access_token": "sk-1234567890",
114+
"cookie": "session=abc123; cf_clearance=xyz",
115+
}
116+
b, _ := json.Marshal(data)
117+
return string(b)
118+
}(),
119+
expectedTypes: []string{"access_token", "cookie"},
120+
expectedValues: map[string]string{
121+
"access_token": "sk-1234567890",
122+
"cookie": "session=abc123; cf_clearance=xyz",
123+
},
124+
expectedIsEmpty: false,
125+
expectedHasAccess: true,
126+
expectedHasCookie: true,
127+
},
128+
{
129+
name: "multi-auth with spaces in auth_type",
130+
authType: " access_token , cookie ",
131+
decryptedValue: func() string {
132+
data := map[string]string{
133+
"access_token": "sk-test",
134+
"cookie": "session=test",
135+
}
136+
b, _ := json.Marshal(data)
137+
return string(b)
138+
}(),
139+
expectedTypes: []string{"access_token", "cookie"},
140+
expectedValues: map[string]string{
141+
"access_token": "sk-test",
142+
"cookie": "session=test",
143+
},
144+
expectedIsEmpty: false,
145+
expectedHasAccess: true,
146+
expectedHasCookie: true,
147+
},
148+
{
149+
name: "multi-auth with only one value in JSON",
150+
authType: "access_token,cookie",
151+
decryptedValue: func() string {
152+
data := map[string]string{
153+
"access_token": "sk-only-token",
154+
}
155+
b, _ := json.Marshal(data)
156+
return string(b)
157+
}(),
158+
expectedTypes: []string{"access_token", "cookie"},
159+
expectedValues: map[string]string{
160+
"access_token": "sk-only-token",
161+
},
162+
expectedIsEmpty: false,
163+
expectedHasAccess: true,
164+
expectedHasCookie: true,
165+
},
166+
}
167+
168+
for _, tt := range tests {
169+
t.Run(tt.name, func(t *testing.T) {
170+
t.Parallel()
171+
172+
config := parseAuthConfig(tt.authType, tt.decryptedValue)
173+
174+
assert.Equal(t, tt.expectedTypes, config.AuthTypes)
175+
assert.Equal(t, tt.expectedValues, config.AuthValues)
176+
assert.Equal(t, tt.expectedIsEmpty, config.IsEmpty())
177+
assert.Equal(t, tt.expectedHasAccess, config.HasAuthType("access_token"))
178+
assert.Equal(t, tt.expectedHasCookie, config.HasAuthType("cookie"))
179+
180+
// Test GetAuthValue
181+
for authType, expectedValue := range tt.expectedValues {
182+
assert.Equal(t, expectedValue, config.GetAuthValue(authType))
183+
}
184+
})
185+
}
186+
}
187+
188+
func TestParseAuthConfig_EdgeCases(t *testing.T) {
189+
t.Parallel()
190+
191+
tests := []struct {
192+
name string
193+
authType string
194+
decryptedValue string
195+
expectedTypes []string
196+
expectedValues map[string]string
197+
}{
198+
{
199+
name: "auth_type with none mixed in",
200+
authType: "access_token,none,cookie",
201+
decryptedValue: `{"access_token":"sk-test","cookie":"session=test"}`,
202+
expectedTypes: []string{"access_token", "cookie"},
203+
expectedValues: map[string]string{
204+
"access_token": "sk-test",
205+
"cookie": "session=test",
206+
},
207+
},
208+
{
209+
name: "invalid JSON fallback to first auth type",
210+
authType: "access_token,cookie",
211+
decryptedValue: "not-a-json-value",
212+
expectedTypes: []string{"access_token", "cookie"},
213+
expectedValues: map[string]string{
214+
"access_token": "not-a-json-value",
215+
},
216+
},
217+
{
218+
name: "empty JSON object",
219+
authType: "access_token,cookie",
220+
decryptedValue: "{}",
221+
expectedTypes: []string{"access_token", "cookie"},
222+
expectedValues: map[string]string{},
223+
},
224+
{
225+
name: "multi-auth with empty value",
226+
authType: "access_token,cookie",
227+
decryptedValue: "",
228+
expectedTypes: []string{"access_token", "cookie"},
229+
expectedValues: map[string]string{},
230+
},
231+
{
232+
name: "only whitespace in auth_type",
233+
authType: " , , ",
234+
decryptedValue: "some-value",
235+
expectedTypes: []string{},
236+
expectedValues: map[string]string{},
237+
},
238+
{
239+
name: "auth_type with trailing comma",
240+
authType: "access_token,cookie,",
241+
decryptedValue: `{"access_token":"sk-test","cookie":"session=test"}`,
242+
expectedTypes: []string{"access_token", "cookie"},
243+
expectedValues: map[string]string{
244+
"access_token": "sk-test",
245+
"cookie": "session=test",
246+
},
247+
},
248+
}
249+
250+
for _, tt := range tests {
251+
t.Run(tt.name, func(t *testing.T) {
252+
t.Parallel()
253+
254+
config := parseAuthConfig(tt.authType, tt.decryptedValue)
255+
256+
assert.Equal(t, tt.expectedTypes, config.AuthTypes)
257+
assert.Equal(t, tt.expectedValues, config.AuthValues)
258+
})
259+
}
260+
}

0 commit comments

Comments
 (0)