Enhance guard handling and template configuration merging
- Update the OAuth guard to prevent automatic response writing on failure, allowing for custom error handling. - Introduce a mechanism to register default guard redirects from template configurations, improving guard management. - Refactor the page configuration merging process to prioritize page-specific settings while allowing inheritance from templates. - Ensure guards can be explicitly disabled in page configurations, enhancing flexibility in guard application.
This commit is contained in:
parent
069f60f1f8
commit
621290d578
7 changed files with 111 additions and 34 deletions
|
|
@ -77,7 +77,6 @@ func withStaticFileServer(c *gin.Context) {
|
|||
|
||||
// Sui file server
|
||||
if strings.HasSuffix(c.Request.URL.Path, ".sui") {
|
||||
|
||||
// Default index.sui
|
||||
if filepath.Base(c.Request.URL.Path) == ".sui" {
|
||||
c.Request.URL.Path = strings.TrimSuffix(c.Request.URL.Path, ".sui") + "index.sui"
|
||||
|
|
@ -92,10 +91,15 @@ func withStaticFileServer(c *gin.Context) {
|
|||
|
||||
html, code, err := r.Render()
|
||||
if err != nil {
|
||||
if code == 301 || code == 302 {
|
||||
url := err.Error()
|
||||
// fmt.Println("Redirect to: ", url)
|
||||
c.Redirect(code, url)
|
||||
if code == 301 || code == 302 {
|
||||
url := err.Error()
|
||||
c.Redirect(code, url)
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
// Guard already sent response (e.g., OAuth writes its own 401)
|
||||
if c.Writer.Written() {
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ func guardCookieTrace(r *Request) error {
|
|||
// OAuth 2.1 guard - authentication only
|
||||
// This guard validates the token and sets authorized info
|
||||
// ACL checks are performed separately in Run() for API calls
|
||||
// NOTE: This guard does NOT write HTTP responses on failure, so that
|
||||
// the caller (Guard/apiGuard) can handle redirects or custom error responses.
|
||||
func guardOAuth(r *Request) error {
|
||||
if r.context == nil {
|
||||
return fmt.Errorf("Context is nil")
|
||||
|
|
@ -108,11 +110,22 @@ func guardOAuth(r *Request) error {
|
|||
|
||||
c := r.context
|
||||
|
||||
// Authenticate only (validates token and sets authorized info)
|
||||
if !oauth.OAuth.Authenticate(c) {
|
||||
return fmt.Errorf("Not authenticated")
|
||||
// Check token first without writing response.
|
||||
// oauth.Authenticate() writes JSON + aborts on failure, which prevents
|
||||
// the caller from doing redirects. So we check the token manually first.
|
||||
token := oauth.OAuth.GetAccessToken(c)
|
||||
if token == "" {
|
||||
return fmt.Errorf("Exception|401:Not authenticated")
|
||||
}
|
||||
|
||||
if _, err := oauth.OAuth.VerifyToken(token); err != nil {
|
||||
return fmt.Errorf("Exception|401:Invalid or expired token")
|
||||
}
|
||||
|
||||
// Token is valid, now call Authenticate to set up the full context
|
||||
// (session ID, authorized info, etc.). This will succeed since token is valid.
|
||||
oauth.OAuth.Authenticate(c)
|
||||
|
||||
// Get authorized info from context
|
||||
info := authorized.GetInfo(c)
|
||||
if info != nil {
|
||||
|
|
|
|||
|
|
@ -237,6 +237,13 @@ func (r *Request) MakeCache() (*core.Cache, int, error) {
|
|||
guardRedirect = parts[1]
|
||||
}
|
||||
|
||||
// Fallback: if guard has no redirect, check template default redirect
|
||||
if guardRedirect == "" && guard != "" && guard != "-" {
|
||||
if defaultRedirect, has := core.DefaultGuardRedirects[guard]; has {
|
||||
guardRedirect = defaultRedirect
|
||||
}
|
||||
}
|
||||
|
||||
// Cache store
|
||||
cacheStore = conf.CacheStore
|
||||
cacheTime = conf.Cache
|
||||
|
|
@ -303,8 +310,8 @@ func (r *Request) MakeCache() (*core.Cache, int, error) {
|
|||
// Guard the page
|
||||
func (r *Request) Guard(c *core.Cache) (int, error) {
|
||||
|
||||
// Guard not set
|
||||
if c.Guard == "" || r.context == nil {
|
||||
// Guard not set or explicitly disabled
|
||||
if c.Guard == "" || c.Guard == "-" || r.context == nil {
|
||||
return 200, nil
|
||||
}
|
||||
|
||||
|
|
@ -312,32 +319,27 @@ func (r *Request) Guard(c *core.Cache) (int, error) {
|
|||
if guard, has := Guards[c.Guard]; has {
|
||||
err := guard(r)
|
||||
if err != nil {
|
||||
// Redirect the page (should refector before release)
|
||||
// Redirect the page (takes priority over guard's own response)
|
||||
if c.GuardRedirect != "" {
|
||||
redirect := c.GuardRedirect
|
||||
data := core.Data{}
|
||||
// Here may have a security issue, should be refector, in the future.
|
||||
// Copy the script pointer to the request For page backend script execution
|
||||
r.Request.Script = c.Script
|
||||
if c.Data != "" {
|
||||
data, err = r.Request.ExecString(c.Data)
|
||||
if err != nil {
|
||||
return 500, fmt.Errorf("data error, please re-complie the page %s", err.Error())
|
||||
}
|
||||
|
||||
// Append error code and message as query parameters
|
||||
ex := exception.Err(err, 403)
|
||||
msg := url.QueryEscape(ex.Message)
|
||||
if strings.Contains(redirect, "?") {
|
||||
redirect = fmt.Sprintf("%s&code=%d&message=%s", redirect, ex.Code, msg)
|
||||
} else {
|
||||
redirect = fmt.Sprintf("%s?code=%d&message=%s", redirect, ex.Code, msg)
|
||||
}
|
||||
|
||||
if c.Global != "" {
|
||||
global, err := r.Request.ExecString(c.Global)
|
||||
if err != nil {
|
||||
return 500, fmt.Errorf("global data error, please re-complie the page %s", err.Error())
|
||||
}
|
||||
data["$global"] = global
|
||||
}
|
||||
|
||||
redirect, _ = data.Replace(redirect)
|
||||
return 302, fmt.Errorf("%s", redirect)
|
||||
}
|
||||
|
||||
// Guard already sent response (e.g., OAuth writes its own 401)
|
||||
if r.context != nil && r.context.IsAborted() {
|
||||
return 403, err
|
||||
}
|
||||
|
||||
// Return the error
|
||||
ex := exception.Err(err, 403)
|
||||
return ex.Code, fmt.Errorf("%s", ex.Message)
|
||||
|
|
@ -348,6 +350,10 @@ func (r *Request) Guard(c *core.Cache) (int, error) {
|
|||
// Developer custom guard
|
||||
err := r.processGuard(c.Guard)
|
||||
if err != nil {
|
||||
// Guard already sent response
|
||||
if r.context != nil && r.context.IsAborted() {
|
||||
return 403, err
|
||||
}
|
||||
ex := exception.Err(err, 403)
|
||||
return ex.Code, fmt.Errorf("%s", ex.Message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ import (
|
|||
// SUIs the loaded SUI instances
|
||||
var SUIs = map[string]SUI{}
|
||||
|
||||
// DefaultGuardRedirects stores default guard redirect URLs from template configs.
|
||||
// Key is guard name (e.g. "oauth"), value is redirect URL (e.g. "/dashboard/auth/entry").
|
||||
// Registered by template loading (e.g. agent storage) and used by MakeCache as fallback.
|
||||
var DefaultGuardRedirects = map[string]string{}
|
||||
|
||||
// RouteMatchers the route matchers for the SUI instance
|
||||
var RouteMatchers = map[*regexp.Regexp][][]*Matcher{}
|
||||
|
||||
|
|
|
|||
|
|
@ -185,8 +185,9 @@ type Template struct {
|
|||
GlobalData []byte `json:"-"`
|
||||
Scripts *TemplateScirpts `json:"scripts,omitempty"`
|
||||
Translator string `json:"translator,omitempty"`
|
||||
BuildScript *Script `json:"-"` // __build.backend.ts / __build.backend.js
|
||||
GlobalScript *Script `json:"-"` // __global.backend.ts / __global.backend.js
|
||||
Config *PageSetting `json:"config,omitempty"` // Default page config (guard, api, etc.)
|
||||
BuildScript *Script `json:"-"` // __build.backend.ts / __build.backend.js
|
||||
GlobalScript *Script `json:"-"` // __global.backend.ts / __global.backend.js
|
||||
}
|
||||
|
||||
// TemplateScirpts is the struct for the template scripts
|
||||
|
|
|
|||
|
|
@ -90,6 +90,12 @@ func (agent *Agent) GetTemplate(id string) (core.ITemplate, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Register default guard redirect from template config
|
||||
if tmpl.Template.Config != nil && strings.Contains(tmpl.Template.Config.Guard, ":") {
|
||||
parts := strings.SplitN(tmpl.Template.Config.Guard, ":", 2)
|
||||
core.DefaultGuardRedirects[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
// Load __document.html
|
||||
documentFile := filepath.Join(agent.root, "__document.html")
|
||||
if agent.fs.IsFile(documentFile) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
|
@ -157,17 +158,52 @@ func (page *Page) GetConfig() *core.PageConfig {
|
|||
if fs.IsFile(confFile) {
|
||||
content, err := fs.ReadFile(confFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
return page.mergeTemplateConfig(nil)
|
||||
}
|
||||
|
||||
var config core.PageConfig
|
||||
if err := jsoniter.Unmarshal(content, &config); err == nil {
|
||||
p.Config = &config
|
||||
return p.Config
|
||||
return page.mergeTemplateConfig(p.Config)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return page.mergeTemplateConfig(nil)
|
||||
}
|
||||
|
||||
// mergeTemplateConfig merges template default config into page config (page config takes priority).
|
||||
// Use guard: "-" in page config to explicitly disable guard inheritance.
|
||||
func (page *Page) mergeTemplateConfig(cfg *core.PageConfig) *core.PageConfig {
|
||||
tmplConfig := page.tmpl.Template.Config
|
||||
if tmplConfig == nil {
|
||||
return cfg
|
||||
}
|
||||
|
||||
if cfg == nil {
|
||||
cfg = &core.PageConfig{PageSetting: *tmplConfig}
|
||||
page.Page.Config = cfg
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Merge guard (page config takes priority, "-" means explicitly no guard)
|
||||
if cfg.Guard == "" {
|
||||
// Page has no guard, use template's guard (with redirect)
|
||||
cfg.Guard = tmplConfig.Guard
|
||||
} else if !strings.Contains(cfg.Guard, ":") && strings.Contains(tmplConfig.Guard, ":") {
|
||||
// Page has guard without redirect (e.g. "oauth"), template has redirect (e.g. "oauth:/login")
|
||||
// Inherit redirect from template if same guard type
|
||||
tmplParts := strings.SplitN(tmplConfig.Guard, ":", 2)
|
||||
if tmplParts[0] == cfg.Guard {
|
||||
cfg.Guard = tmplConfig.Guard
|
||||
}
|
||||
}
|
||||
|
||||
// Merge API guard config
|
||||
if cfg.API == nil && tmplConfig.API != nil {
|
||||
cfg.API = tmplConfig.API
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
// SaveTemp save the page temporarily (not supported for agent pages)
|
||||
|
|
@ -293,6 +329,9 @@ func (page *Page) Build(globalCtx *core.GlobalBuildContext, option *core.BuildOp
|
|||
}
|
||||
}
|
||||
|
||||
// Merge template default config before compile (page config takes priority)
|
||||
page.GetConfig()
|
||||
|
||||
html, config, warnings, err := page.Page.Compile(ctx, option)
|
||||
if err != nil {
|
||||
return warnings, fmt.Errorf("Compile the page %s error: %s", page.Route, err.Error())
|
||||
|
|
@ -444,6 +483,9 @@ func (page *Page) Trans(globalCtx *core.GlobalBuildContext, option *core.BuildOp
|
|||
warnings := []string{}
|
||||
ctx := core.NewBuildContext(globalCtx)
|
||||
|
||||
// Merge template default config before compile
|
||||
page.GetConfig()
|
||||
|
||||
_, _, messages, err := page.Page.Compile(ctx, option)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue