mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 07:06:36 +08:00
fix(config): serve public settings and enforce login CAP
Public config now comes from admin as a flat visibility=1 map instead of a cross-plugin query that compared an integer column to "visible". Login and register resolve CaptchaService per request so CAP is not skipped when user applies before cap.
This commit is contained in:
@@ -9,8 +9,8 @@ import (
|
||||
"Wavelet/core/contracts"
|
||||
"Wavelet/pkg/logger"
|
||||
"Wavelet/pkg/response"
|
||||
"context"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -28,13 +28,6 @@ func (p *Plugin) Name() string {
|
||||
return "system"
|
||||
}
|
||||
|
||||
// Inject declares required dependencies for the system domain plugin.
|
||||
func (p *Plugin) Inject() []reflect.Type {
|
||||
return []reflect.Type{
|
||||
reflect.TypeFor[contracts.DBService](),
|
||||
}
|
||||
}
|
||||
|
||||
// Manifest returns the plugin metadata.
|
||||
func (p *Plugin) Manifest() core.Manifest {
|
||||
return core.Manifest{
|
||||
@@ -47,37 +40,31 @@ func (p *Plugin) Manifest() core.Manifest {
|
||||
|
||||
// Apply registers system routes.
|
||||
func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
appName := ctx.Config().String("app.app_name", "Wavelet")
|
||||
|
||||
// 1. Health check
|
||||
ctx.Router().GET("/api/healthz", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||
})
|
||||
ctx.Router().RegisterWhitelist("/api/healthz")
|
||||
|
||||
// 2. Public config
|
||||
// 2. Public config — owned data comes from PublicConfigProvider (admin).
|
||||
ctx.Router().GET("/api/v1/config/public", func(c *gin.Context) {
|
||||
if p, err := core.Inject[contracts.PublicConfigProvider](ctx); err == nil && p != nil {
|
||||
data, err := p.PublicConfig(c.Request.Context())
|
||||
if err != nil {
|
||||
logger.ErrorF(c.Request.Context(), "[System] public config provider failed: %v", err)
|
||||
response.AbortInternal(c, "public config unavailable")
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OK(data))
|
||||
provider := resolvePublicConfigProvider(c.Request.Context(), ctx)
|
||||
if provider == nil {
|
||||
c.JSON(http.StatusOK, response.OK(map[string]string{}))
|
||||
return
|
||||
}
|
||||
configs, err := listPublicSystemConfigs(c.Request.Context(), ctx)
|
||||
data, err := provider.PublicConfig(c.Request.Context())
|
||||
if err != nil {
|
||||
logger.ErrorF(c.Request.Context(), "[System] query public system configs failed: %v", err)
|
||||
logger.ErrorF(c.Request.Context(), "[System] public config provider failed: %v", err)
|
||||
response.AbortInternal(c, "public config unavailable")
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OK(gin.H{
|
||||
"configs": configs,
|
||||
"app": gin.H{
|
||||
"name": appName,
|
||||
},
|
||||
}))
|
||||
if data == nil {
|
||||
data = map[string]string{}
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OK(data))
|
||||
})
|
||||
ctx.Router().RegisterWhitelist("/api/v1/config/public")
|
||||
|
||||
// 3. Custom injection
|
||||
ctx.Router().GET("/custom", func(c *gin.Context) {
|
||||
@@ -86,3 +73,15 @@ func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolvePublicConfigProvider(reqCtx context.Context, appCtx *core.Context) contracts.PublicConfigProvider {
|
||||
if p, err := core.InjectFrom[contracts.PublicConfigProvider](reqCtx); err == nil && p != nil {
|
||||
return p
|
||||
}
|
||||
if appCtx != nil {
|
||||
if p, err := core.Inject[contracts.PublicConfigProvider](appCtx); err == nil && p != nil {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,13 +18,17 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type stubPublic struct{ payload any }
|
||||
type stubPublic struct{ payload map[string]string }
|
||||
|
||||
func (s stubPublic) PublicConfig(context.Context) (any, error) { return s.payload, nil }
|
||||
func (s stubPublic) PublicConfig(context.Context) (map[string]string, error) {
|
||||
return s.payload, nil
|
||||
}
|
||||
|
||||
type errPublic struct{ err error }
|
||||
|
||||
func (s errPublic) PublicConfig(context.Context) (any, error) { return nil, s.err }
|
||||
func (s errPublic) PublicConfig(context.Context) (map[string]string, error) {
|
||||
return nil, s.err
|
||||
}
|
||||
|
||||
func TestPublicConfigUsesProviderWhenPresent(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
@@ -54,16 +58,22 @@ func TestPublicConfigDefaultWithoutProvider(t *testing.T) {
|
||||
if err := New().Apply(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ctx.Router().IsWhitelisted("/api/v1/config/public") {
|
||||
t.Fatal("GET /api/v1/config/public not whitelisted")
|
||||
}
|
||||
raw := invokePublicConfig(t, publicConfigHandler(t, ctx))
|
||||
var data map[string]any
|
||||
if err := json.Unmarshal(raw, &data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := data["configs"]; !ok {
|
||||
t.Fatalf("data = %s, want key configs", raw)
|
||||
if len(data) != 0 {
|
||||
t.Fatalf("data = %s, want empty flat map", raw)
|
||||
}
|
||||
if _, ok := data["app"]; !ok {
|
||||
t.Fatalf("data = %s, want key app", raw)
|
||||
if _, ok := data["configs"]; ok {
|
||||
t.Fatalf("data = %s, default payload must not wrap configs", raw)
|
||||
}
|
||||
if _, ok := data["app"]; ok {
|
||||
t.Fatalf("data = %s, default payload must not wrap app", raw)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"Wavelet/core"
|
||||
"Wavelet/core/contracts"
|
||||
"context"
|
||||
)
|
||||
|
||||
// publicSystemConfig 前端公共配置接口的只读投影。
|
||||
type publicSystemConfig struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// listPublicSystemConfigs 读取对前端可见的系统配置项。
|
||||
//
|
||||
// 注意:w_system_configs 的所有者插件是 admin,contracts 目前尚未暴露读取契约,
|
||||
// 因此此处仍只能直连只读查询;待 admin 提供 SettingsService 契约后应改为调用契约。
|
||||
func listPublicSystemConfigs(ctx context.Context, appCtx *core.Context) ([]publicSystemConfig, error) {
|
||||
dbSvc, err := core.Inject[contracts.DBService](appCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dbSvc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var configs []publicSystemConfig
|
||||
err = dbSvc.DB(ctx).Table("w_system_configs").
|
||||
Where("visibility = ?", "visible").
|
||||
Find(&configs).Error
|
||||
return configs, err
|
||||
}
|
||||
Reference in New Issue
Block a user