Add guard functionality and export page config

This commit is contained in:
Max 2024-01-03 18:03:27 +08:00
parent 5031904964
commit 76d9811048
7 changed files with 247 additions and 7 deletions

View file

@ -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

141
sui/api/guards.go Normal file
View file

@ -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
}

View file

@ -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

View file

@ -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" + `<script name="config" type="json">` + "\n" +
page.ExportConfig() +
"\n</script>\n\n",
)
}
// Page Data
if page.Codes.DATA.Code != "" {
doc.Find("body").AppendHtml("\n\n" + `<script name="data" type="json">` + "\n" +
@ -45,10 +56,7 @@ func (page *Page) Compile(option *BuildOption) (string, error) {
)
}
// Replace the document
page.Config = page.GetConfig()
page.ReplaceDocument(doc)
html, err := doc.Html()
if err != nil {
return "", err

View file

@ -13,6 +13,7 @@ import (
)
var stmtRe = regexp.MustCompile(`\{\{([^}]+)\}\}`)
var propRe = regexp.MustCompile(`\[\{([^}]+)\}\]`)
// Data data for the template
type Data map[string]interface{}
@ -26,8 +27,24 @@ var options = []expr.Option{
// New create a new expression
func (data Data) New(stmt string) (*vm.Program, error) {
stmt = strings.TrimSpace(strings.TrimRight(strings.TrimLeft(stmt, "{{ "), "}}"))
stmt = strings.TrimSpace(strings.TrimRight(strings.TrimLeft(stmt, "[{ "), "}]"))
stmt = stmtRe.ReplaceAllStringFunc(stmt, func(stmt string) string {
matches := stmtRe.FindStringSubmatch(stmt)
if len(matches) > 0 {
stmt = strings.ReplaceAll(stmt, matches[0], matches[1])
}
return stmt
})
stmt = propRe.ReplaceAllStringFunc(stmt, func(stmt string) string {
matches := propRe.FindStringSubmatch(stmt)
if len(matches) > 0 {
stmt = strings.ReplaceAll(stmt, matches[0], matches[1])
}
return stmt
})
stmt = strings.TrimSpace(stmt)
// &#39; => ' &#34; => "
stmt = strings.ReplaceAll(stmt, "&#39;", "'")
stmt = strings.ReplaceAll(stmt, "&#34;", "\"")

View file

@ -38,7 +38,7 @@ func (page *Page) GetConfig() *PageConfig {
if page.Codes.CONF.Code != "" {
var config PageConfig
err := jsoniter.Unmarshal([]byte(page.Codes.CONF.Code), &config)
err := jsoniter.UnmarshalFromString(page.Codes.CONF.Code, &config)
if err == nil {
page.Config = &config
}
@ -57,6 +57,24 @@ func (page *Page) GetConfig() *PageConfig {
return page.Config
}
// ExportConfig export the config
func (page *Page) ExportConfig() string {
if page.Config == nil {
return ""
}
config, err := jsoniter.MarshalToString(map[string]interface{}{
"title": page.Config.Title,
"guard": page.Config.Guard,
})
if err != nil {
log.Error("[sui] export page config error %s", err.Error())
return ""
}
return config
}
// Data get the data deprecated
func (page *Page) Data(request *Request) (Data, map[string]interface{}, error) {

View file

@ -13,6 +13,8 @@ import (
type Cache struct {
Data string
Global string
Config string
Guard string
HTML string
}