fix(cors): SettingsCORSRequest.Origins 改 *string,支持显式清空
Bug 2 报告: controller/settings.go:330-332 的死代码
if body.AllowAll == nil && trimAll(body.Origins) == "" && body.Origins != "" {
// 空 body 不算错误
}
该条件永远 false (trim 空 != "" 矛盾), 整个 if 块只注释不执行。
更严重的是: 用户无法清空 origins —
原代码 body.Origins == "" 就 400, 但用户可能就是想传空串清空。
struct 字段是 string, 区分不出"未传"和"传空串"。
修法:
- struct.SettingsCORSRequest.Origins: string → *string
- nil = 未传 (不动)
- *"" = 传空串 (清空)
- *"url1\nurl2" = 覆盖
- 删掉 line 330-332 死代码
- 改校验逻辑:
- 2 个字段都 nil → 400 no_fields
- 至少一个非 nil → 200
- 空串不校验 origin 格式 (无 origin 可校), 直接存
行为变更:
- {"origins": "https://a.com"} → 写, 同以前
- {"origins": ""} → 清空 (新功能, 以前返 400)
- {"origins": null} → 视为未传, 不动
- {"allow_all": true} → 写, 同以前
- {"allow_all": false} → 设为 false, 同以前
- {} → 400 no_fields
- {"origins": "not-a-url"} → 400 origin_invalid (同以前)
测试: test/integration/settings_test.go (gitignored) 5 个 case
- EmptyOrigins_ClearsOrigins
- OriginsNotInBody_NotTouched
- EmptyBody_400
- NullOrigins_TreatedAsMissing
- InvalidOrigin_400
e2e: 4 场景全过 (设/清空/空 body 400/null 不动)。
不向后兼容: SettingsCORSRequest 内部 DTO, 仅 admin webui 调,
不暴露给外部 OpenAI 客户端。
未 push (待用户)
This commit is contained in:
+28
-23
@@ -299,12 +299,13 @@ func SettingsAuthKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// SettingsCORSRequest 是 PUT /api/settings/cors 的 body。
|
||||
// 两个字段都可选(至少给一个):
|
||||
// - allow_all: true → 任意 Origin 都接受(*);设了之后 origins 失效
|
||||
// - origins: 一行一个 origin,后端 trim + lower + 去末尾 /
|
||||
// 两个字段都可选(至少给一个),用指针区分"未传"和"传空串":
|
||||
// - allow_all 指针: nil=未传(不动) *true=开 *false=关
|
||||
// - origins 字符串: nil=未传(不动) ""=传空串(清空) "url1\nurl2"=覆盖
|
||||
// 这样用户能精确表达意图(保留 / 改 / 清空),不会被 0/"" 歧义坑死。
|
||||
type SettingsCORSRequest struct {
|
||||
AllowAll *bool `json:"allow_all,omitempty"`
|
||||
Origins string `json:"origins,omitempty"` // 也接受 string 数组(任一形式)
|
||||
AllowAll *bool `json:"allow_all,omitempty"`
|
||||
Origins *string `json:"origins,omitempty"` // *string 区分"未传(nil)"和"传空串"
|
||||
}
|
||||
|
||||
// SettingsCORSHandler PUT /api/settings/cors
|
||||
@@ -327,10 +328,9 @@ func SettingsCORSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
middleware.SendJSONError(w, http.StatusBadRequest, "invalid JSON body", "invalid_request_error", "bad_request")
|
||||
return
|
||||
}
|
||||
if body.AllowAll == nil && trimAll(body.Origins) == "" && body.Origins != "" {
|
||||
// 空 body 不算错误,用户可能是想"清空"(只清 origins 保留现状)
|
||||
}
|
||||
if body.AllowAll == nil && body.Origins == "" {
|
||||
// 至少要给一个字段(allow_all 或 origins)
|
||||
// 指针为 nil 表示"未传",不计入
|
||||
if body.AllowAll == nil && body.Origins == nil {
|
||||
middleware.SendJSONError(w, http.StatusBadRequest,
|
||||
"at least one of allow_all / origins required",
|
||||
"invalid_request_error", "no_fields")
|
||||
@@ -341,22 +341,27 @@ func SettingsCORSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if body.AllowAll != nil {
|
||||
updates["cors_allow_all"] = boolToStr(*body.AllowAll)
|
||||
}
|
||||
if body.Origins != "" {
|
||||
// 校验每个 origin 至少像 http(s)://... (防止用户填空或填乱字符)
|
||||
for _, line := range strings.Split(body.Origins, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
low := strings.ToLower(line)
|
||||
if !strings.HasPrefix(low, "http://") && !strings.HasPrefix(low, "https://") {
|
||||
middleware.SendJSONError(w, http.StatusBadRequest,
|
||||
fmt.Sprintf("invalid origin: %q (must start with http:// or https://)", line),
|
||||
"invalid_request_error", "origin_invalid")
|
||||
return
|
||||
if body.Origins != nil {
|
||||
// *Origins == "" 表示用户要清空(保留 nil 表示"不动")
|
||||
origins := *body.Origins
|
||||
if origins != "" {
|
||||
// 校验每个 origin 至少像 http(s)://... (防止用户填乱字符)
|
||||
for _, line := range strings.Split(origins, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
low := strings.ToLower(line)
|
||||
if !strings.HasPrefix(low, "http://") && !strings.HasPrefix(low, "https://") {
|
||||
middleware.SendJSONError(w, http.StatusBadRequest,
|
||||
fmt.Sprintf("invalid origin: %q (must start with http:// or https://)", line),
|
||||
"invalid_request_error", "origin_invalid")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
updates["cors_origins"] = body.Origins
|
||||
// 空串也能存(表示"清空");trim/lower 在 LoadRuntimeConfig 那侧做
|
||||
updates["cors_origins"] = origins
|
||||
}
|
||||
if err := s.SettingsSetBatch(updates); err != nil {
|
||||
log.Printf("[settings] cors set: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user