diff --git a/service/middleware.go b/service/middleware.go index 77b7d9d8..977122db 100644 --- a/service/middleware.go +++ b/service/middleware.go @@ -1,6 +1,7 @@ package service import ( + "fmt" "path/filepath" "strings" @@ -81,6 +82,13 @@ func withStaticFileServer(c *gin.Context) { html, code, err := r.Render() if err != nil { + if code == 301 || code == 302 { + fmt.Println(err.Error()) + c.Redirect(code, err.Error()) + c.Done() + return + } + log.Error("Sui Render Error: %s", err.Error()) c.AbortWithError(code, err) return diff --git a/sui/api/guards.go b/sui/api/guards.go new file mode 100644 index 00000000..8dbe93fe --- /dev/null +++ b/sui/api/guards.go @@ -0,0 +1,141 @@ +package api + +import ( + "bytes" + "fmt" + "io" + "strings" + + "github.com/gin-gonic/gin" + jsoniter "github.com/json-iterator/go" + "github.com/yaoapp/gou/process" + "github.com/yaoapp/yao/helper" +) + +// Guards middlewares +var Guards = map[string]func(c *gin.Context) error{ + "bearer-jwt": guardBearerJWT, // Bearer JWT + "query-jwt": guardQueryJWT, // Get JWT Token from query string "__tk" + "cookie-jwt": guardCookieJWT, // Get JWT Token from cookie "__tk" + +} + +// JWT Bearer JWT +func guardBearerJWT(c *gin.Context) error { + + tokenString := c.Request.Header.Get("Authorization") + tokenString = strings.TrimSpace(strings.TrimPrefix(tokenString, "Bearer ")) + if tokenString == "" { + c.JSON(403, gin.H{"code": 403, "message": "No permission"}) + c.Abort() + return fmt.Errorf("No permission") + } + + claims := helper.JwtValidate(tokenString) + c.Set("__sid", claims.SID) + return nil +} + +// JWT Bearer JWT +func guardCookieJWT(c *gin.Context) error { + tokenString, err := c.Cookie("__tk") + if err != nil { + c.JSON(403, gin.H{"code": 403, "message": "No permission"}) + c.Abort() + return fmt.Errorf("No permission") + } + + if tokenString == "" { + c.JSON(403, gin.H{"code": 403, "message": "No permission"}) + c.Abort() + return fmt.Errorf("No permission") + } + + claims := helper.JwtValidate(tokenString) + c.Set("__sid", claims.SID) + return nil +} + +// JWT Bearer JWT +func guardQueryJWT(c *gin.Context) error { + tokenString := c.Query("__tk") + if tokenString == "" { + c.JSON(403, gin.H{"code": 403, "message": "No permission"}) + c.Abort() + return fmt.Errorf("No permission") + } + + claims := helper.JwtValidate(tokenString) + c.Set("__sid", claims.SID) + return nil +} + +// ProcessGuard guard process +func (r *Request) processGuard(name string) error { + var body interface{} + c := r.context + + if c.Request.Body != nil { + + bodyBytes, err := io.ReadAll(c.Request.Body) + if err == nil { + if strings.HasPrefix(strings.ToLower(c.Request.Header.Get("Content-Type")), "application/json") { + jsoniter.Unmarshal(bodyBytes, &body) + } else { + body = string(bodyBytes) + } + } + + // Reset body + c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + } + + params := map[string]string{} + for _, param := range c.Params { + params[param.Key] = param.Value + } + + args := []interface{}{ + r.URL, // page url + r.Params, // page params + r.Query, // query string + r.Payload, // payload + r.Headers, // Request headers + } + + process, err := process.Of(name, args...) + if err != nil { + c.JSON(403, gin.H{"code": 403, "message": fmt.Sprintf("Guard: %s %s", name, err.Error())}) + c.Abort() + return err + } + + if sid, has := c.Get("__sid"); has { // 设定会话ID + if sid, ok := sid.(string); ok { + process.WithSID(sid) + } + } + + if global, has := c.Get("__global"); has { // 设定全局变量 + if global, ok := global.(map[string]interface{}); ok { + process.WithGlobal(global) + } + } + + v, err := process.Exec() + if err != nil { + return err + } + + if data, ok := v.(map[string]interface{}); ok { + if sid, ok := data["__sid"].(string); ok { + c.Set("__sid", sid) + } + + if global, ok := data["__global"].(map[string]interface{}); ok { + c.Set("__global", global) + } + } + + return nil +} diff --git a/sui/api/request.go b/sui/api/request.go index 5e6fdf5b..316eaf10 100644 --- a/sui/api/request.go +++ b/sui/api/request.go @@ -8,7 +8,9 @@ import ( "strings" "github.com/gin-gonic/gin" + jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/application" + "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/sui/core" ) @@ -17,6 +19,7 @@ import ( type Request struct { File string *core.Request + context *gin.Context } // NewRequestContext is the constructor for Request. @@ -45,7 +48,8 @@ func NewRequestContext(c *gin.Context) (*Request, int, error) { path := strings.TrimSuffix(c.Request.URL.Path, ".sui") return &Request{ - File: file, + File: file, + context: c, Request: &core.Request{ Method: c.Request.Method, Query: c.Request.URL.Query(), @@ -82,6 +86,21 @@ func (r *Request) Render() (string, int, error) { return "", 500, err } + guard := "" + configText := "" + configSel := doc.Find("script[name=config]") + if configSel != nil && configSel.Length() > 0 { + configText = configSel.Text() + configSel.Remove() + + var conf core.PageConfig + err := jsoniter.UnmarshalFromString(configText, &conf) + if err != nil { + return "", 500, fmt.Errorf("config error, please re-complie the page %s", err.Error()) + } + guard = conf.Guard + } + dataText := "" dataSel := doc.Find("script[name=data]") if dataSel != nil && dataSel.Length() > 0 { @@ -107,10 +126,31 @@ func (r *Request) Render() (string, int, error) { Data: dataText, Global: globalDataText, HTML: html, + Guard: guard, + Config: configText, } log.Trace("The page %s is cached", r.File) } + // Guard the page + if c.Guard != "" && r.context != nil { + + if guard, has := Guards[c.Guard]; has { + err := guard(r.context) + if err != nil { + ex := exception.Err(err, 403) + return "", ex.Code, fmt.Errorf("%s", ex.Message) + } + } else { + // Process the guard + err := r.processGuard(c.Guard) + if err != nil { + ex := exception.Err(err, 403) + return "", ex.Code, fmt.Errorf("%s", ex.Message) + } + } + } + var err error data := core.Data{} if c.Data != "" { @@ -128,6 +168,12 @@ func (r *Request) Render() (string, int, error) { data["$global"] = global } + // Set the page request data + data["$payload"] = r.Request.Payload + data["$query"] = r.Request.Query + data["$param"] = r.Request.Params + data["$url"] = r.Request.URL + printData := false if r.Query != nil && r.Query.Has("__sui_print_data") { printData = true diff --git a/sui/core/compile.go b/sui/core/compile.go index 0c2cc37f..35da3ad3 100644 --- a/sui/core/compile.go +++ b/sui/core/compile.go @@ -29,6 +29,17 @@ func (page *Page) Compile(option *BuildOption) (string, error) { } } + // Page Config + page.Config = page.GetConfig() + + // Config Data + if page.Config != nil { + doc.Find("body").AppendHtml("\n\n" + `\n\n", + ) + } + // Page Data if page.Codes.DATA.Code != "" { doc.Find("body").AppendHtml("\n\n" + `