mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
fix: allow public config fallback for cached login
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user