fix: allow public config fallback for cached login

This commit is contained in:
sagitchu
2026-05-14 18:45:12 +08:00
parent 2eee506716
commit fd3ecc38ef
5 changed files with 73 additions and 9 deletions
@@ -42,16 +42,29 @@ func TestPublicConfigGetRejectsSensitiveKeys(t *testing.T) {
assertHandlerCodeMsg(t, resp, 403, "禁止访问敏感配置") assertHandlerCodeMsg(t, resp, 403, "禁止访问敏感配置")
} }
func TestConfigGetNowRequiresAuth(t *testing.T) { func TestConfigGetAllowsPublicCloudflareSiteKeyWithoutAuthForCachedLoginPage(t *testing.T) {
router, _ := setupConfigAccessTestRouter(t) router, r := setupConfigAccessTestRouter(t)
seedConfigValue(t, r, "cloudflare_site_key", "site-key")
req := httptest.NewRequest(http.MethodPost, "/api/v1/config/get", bytes.NewBufferString(`{"name":"app_name"}`)) req := httptest.NewRequest(http.MethodPost, "/api/v1/config/get", bytes.NewBufferString(`{"name":"cloudflare_site_key"}`))
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
router.ServeHTTP(resp, req) router.ServeHTTP(resp, req)
assertHandlerCodeMsg(t, resp, 401, "未登录或token已过期") assertHandlerConfigValue(t, resp, "cloudflare_site_key", "site-key")
}
func TestConfigGetRejectsSensitiveKeysWithoutAuth(t *testing.T) {
router, _ := setupConfigAccessTestRouter(t)
req := httptest.NewRequest(http.MethodPost, "/api/v1/config/get", bytes.NewBufferString(`{"name":"jwt_secret"}`))
req.Header.Set("Content-Type", "application/json")
resp := httptest.NewRecorder()
router.ServeHTTP(resp, req)
assertHandlerCodeMsg(t, resp, 403, "禁止访问敏感配置")
} }
func TestConfigUpdateRejectsSensitiveKeys(t *testing.T) { func TestConfigUpdateRejectsSensitiveKeys(t *testing.T) {
@@ -139,3 +152,23 @@ func assertHandlerCodeMsg(t *testing.T, rec *httptest.ResponseRecorder, expected
t.Fatalf("expected (%d,%q), got (%d,%q)", expectedCode, expectedMsg, out.Code, out.Msg) t.Fatalf("expected (%d,%q), got (%d,%q)", expectedCode, expectedMsg, out.Code, out.Msg)
} }
} }
func assertHandlerConfigValue(t *testing.T, rec *httptest.ResponseRecorder, expectedName, expectedValue string) {
t.Helper()
var out struct {
Code int `json:"code"`
Data struct {
Name string `json:"name"`
Value string `json:"value"`
} `json:"data"`
}
if err := json.NewDecoder(rec.Body).Decode(&out); err != nil {
t.Fatalf("decode response: %v", err)
}
if out.Code != 0 {
t.Fatalf("expected code 0, got %d", out.Code)
}
if out.Data.Name != expectedName || out.Data.Value != expectedValue {
t.Fatalf("expected config (%q,%q), got (%q,%q)", expectedName, expectedValue, out.Data.Name, out.Data.Value)
}
}
+6 -1
View File
@@ -399,7 +399,12 @@ func (h *Handler) getConfigByName(w http.ResponseWriter, r *http.Request) {
return return
} }
cfg, err := h.repo.GetConfigByName(req.Name) if _, ok := r.Context().Value(middleware.ClaimsContextKey).(auth.Claims); !ok && !repo.IsPublicConfigKey(configName) {
response.WriteJSON(w, response.Err(401, "未登录或token已过期"))
return
}
cfg, err := h.repo.GetConfigByName(configName)
if err != nil { if err != nil {
response.WriteJSON(w, response.Err(-2, err.Error())) response.WriteJSON(w, response.Err(-2, err.Error()))
return return
@@ -272,7 +272,7 @@ func TestSystemUpgradeFailsFastBeforeMutatingFiles(t *testing.T) {
fakeDockerDir := t.TempDir() fakeDockerDir := t.TempDir()
fakeDockerPath := filepath.Join(fakeDockerDir, "docker") fakeDockerPath := filepath.Join(fakeDockerDir, "docker")
fakeDockerScript := "#!/bin/sh\ncase \"$1\" in\n --version)\n echo 'Docker version 27.0.0'\n exit 0\n ;;&\n compose)\n if [ \"$2\" = version ]; then\n echo 'Docker Compose version v2.33.0'\n exit 0\n fi\n exit 0\n ;;&\n inspect)\n echo 'No such object: flux-panel-backend' >&2\n exit 1\n ;;&\n *)\n exit 0\n ;;&\n esac\n" fakeDockerScript := "#!/bin/sh\ncase \"$1\" in\n --version)\n echo 'Docker version 27.0.0'\n exit 0\n ;;\n compose)\n if [ \"$2\" = version ]; then\n echo 'Docker Compose version v2.33.0'\n exit 0\n fi\n exit 0\n ;;\n inspect)\n echo 'No such object: flux-panel-backend' >&2\n exit 1\n ;;\n *)\n exit 0\n ;;\n esac\n"
if err := os.WriteFile(fakeDockerPath, []byte(fakeDockerScript), 0o755); err != nil { if err := os.WriteFile(fakeDockerPath, []byte(fakeDockerScript), 0o755); err != nil {
t.Fatalf("WriteFile() fake docker error = %v", err) t.Fatalf("WriteFile() fake docker error = %v", err)
} }
@@ -281,7 +281,7 @@ func TestSystemUpgradeFailsFastBeforeMutatingFiles(t *testing.T) {
t.Setenv(panelBackendContainerEnv, "flux-panel-backend") t.Setenv(panelBackendContainerEnv, "flux-panel-backend")
h := &Handler{} h := &Handler{}
req := httptest.NewRequest(http.MethodPost, "/api/v1/system/upgrade", strings.NewReader(`{"channel":"stable"}`)) req := httptest.NewRequest(http.MethodPost, "/api/v1/system/upgrade", strings.NewReader(`{"channel":"stable","version":"3.0.0"}`))
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
h.systemUpgrade(rr, req) h.systemUpgrade(rr, req)
@@ -34,12 +34,20 @@ func JWT(opts AuthOptions) func(http.Handler) http.Handler {
token := strings.TrimSpace(r.Header.Get("Authorization")) token := strings.TrimSpace(r.Header.Get("Authorization"))
if token == "" { if token == "" {
if allowsOptionalAuth(r.URL.Path) {
next.ServeHTTP(w, r)
return
}
response.WriteJSON(w, response.Err(401, "未登录或token已过期")) response.WriteJSON(w, response.Err(401, "未登录或token已过期"))
return return
} }
claims, ok := auth.ValidateToken(token, opts.JWTSecret) claims, ok := auth.ValidateToken(token, opts.JWTSecret)
if !ok { if !ok {
if allowsOptionalAuth(r.URL.Path) {
next.ServeHTTP(w, r)
return
}
response.WriteJSON(w, response.Err(401, "无效的token或token已过期")) response.WriteJSON(w, response.Err(401, "无效的token或token已过期"))
return return
} }
@@ -47,11 +55,19 @@ func JWT(opts AuthOptions) func(http.Handler) http.Handler {
if opts.GetUserAuthState != nil { if opts.GetUserAuthState != nil {
userID, err := strconv.ParseInt(claims.Sub, 10, 64) userID, err := strconv.ParseInt(claims.Sub, 10, 64)
if err != nil { if err != nil {
if allowsOptionalAuth(r.URL.Path) {
next.ServeHTTP(w, r)
return
}
response.WriteJSON(w, response.Err(401, "无效的token或token已过期")) response.WriteJSON(w, response.Err(401, "无效的token或token已过期"))
return return
} }
state, err := opts.GetUserAuthState(userID) state, err := opts.GetUserAuthState(userID)
if err != nil || state == nil || state.Status != 1 || state.RoleID != claims.RoleID || claims.IatMs <= state.PasswordChangedAt { if err != nil || state == nil || state.Status != 1 || state.RoleID != claims.RoleID || claims.IatMs <= state.PasswordChangedAt {
if allowsOptionalAuth(r.URL.Path) {
next.ServeHTTP(w, r)
return
}
response.WriteJSON(w, response.Err(401, "无效的token或token已过期")) response.WriteJSON(w, response.Err(401, "无效的token或token已过期"))
return return
} }
@@ -84,6 +100,10 @@ func RequireAdmin(next http.Handler) http.Handler {
}) })
} }
func allowsOptionalAuth(path string) bool {
return path == "/api/v1/config/get"
}
func shouldSkip(path string) bool { func shouldSkip(path string) bool {
switch { switch {
case strings.HasPrefix(path, "/flow/"): case strings.HasPrefix(path, "/flow/"):
@@ -162,11 +162,17 @@ func TestPublicConfigGetAndAuthConfigContract(t *testing.T) {
router.ServeHTTP(secretResp, secretReq) router.ServeHTTP(secretResp, secretReq)
assertCodeMsg(t, secretResp, 403, "禁止访问敏感配置") assertCodeMsg(t, secretResp, 403, "禁止访问敏感配置")
configReq := httptest.NewRequest(http.MethodPost, "/api/v1/config/get", bytes.NewBufferString(`{"name":"app_name"}`)) configReq := httptest.NewRequest(http.MethodPost, "/api/v1/config/get", bytes.NewBufferString(`{"name":"cloudflare_site_key"}`))
configReq.Header.Set("Content-Type", "application/json") configReq.Header.Set("Content-Type", "application/json")
configResp := httptest.NewRecorder() configResp := httptest.NewRecorder()
router.ServeHTTP(configResp, configReq) router.ServeHTTP(configResp, configReq)
assertCodeMsg(t, configResp, 401, "未登录或token已过期") assertCode(t, configResp, 0)
configSecretReq := httptest.NewRequest(http.MethodPost, "/api/v1/config/get", bytes.NewBufferString(`{"name":"jwt_secret"}`))
configSecretReq.Header.Set("Content-Type", "application/json")
configSecretResp := httptest.NewRecorder()
router.ServeHTTP(configSecretResp, configSecretReq)
assertCodeMsg(t, configSecretResp, 403, "禁止访问敏感配置")
} }
func TestOpenAPISubStoreContracts(t *testing.T) { func TestOpenAPISubStoreContracts(t *testing.T) {