Files
new-api-custom/pkg/jsplugin/utils.go
T
cneicy a5f7b8e586
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m40s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 42m16s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
2026-09-26 04:49:11 +08:00

159 lines
5.2 KiB
Go

package jsplugin
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"net/url"
"sort"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/grafana/sobek"
)
type volcSignRequest struct {
Method string `json:"method"`
URL string `json:"url"`
Headers map[string]string `json:"headers"`
Body string `json:"body"`
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
Region string `json:"region"`
Service string `json:"service"`
Timestamp int64 `json:"timestamp"`
}
func injectGlobals(runtime *sobek.Runtime, identity func() string, now func() time.Time, logOutput func(string)) error {
utils := map[string]any{
"hasCapability": HasCapability,
"json": map[string]any{
"clone": func(call sobek.FunctionCall) sobek.Value {
if sobek.IsUndefined(call.Argument(0)) {
panic(runtime.NewTypeError("json.clone requires a JSON value"))
}
budget := &jsonStateBudget{ctx: context.Background(), bytes: MaxJSONToolBytes, nodes: maxJSONToolNodes}
value, err := newJSONStateNode(call.Argument(0), 0, budget)
if err != nil {
panic(runtime.NewTypeError("json.clone: %s", err))
}
cloned, err := value.jsValue(runtime)
if err != nil {
panic(runtime.NewGoError(err))
}
return cloned
},
},
"unixNow": func() int64 { return now().Unix() },
"jwtSignHS256": func(claims map[string]any, secret string) (string, error) {
return jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(claims)).SignedString([]byte(secret))
},
"hmacSHA256": func(message, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
_, _ = mac.Write([]byte(message))
return hex.EncodeToString(mac.Sum(nil))
},
"base64": func(value string) string { return base64.StdEncoding.EncodeToString([]byte(value)) },
"base64URL": func(value string) string {
return base64.RawURLEncoding.EncodeToString([]byte(value))
},
"base64URLDecode": func(value string) (string, error) {
decoded, err := base64.RawURLEncoding.DecodeString(value)
return string(decoded), err
},
"uuid": func() string { return uuid.NewString() },
"volcSignV4": func(request volcSignRequest) (map[string]string, error) {
return signVolcV4(request, now)
},
}
if err := runtime.Set("utils", utils); err != nil {
return err
}
console := runtime.NewObject()
if err := console.Set("log", func(call sobek.FunctionCall) sobek.Value {
parts := make([]string, len(call.Arguments))
for i, argument := range call.Arguments {
parts[i] = argument.String()
}
if logOutput != nil {
logOutput(identity() + " " + strings.Join(parts, " "))
}
return sobek.Undefined()
}); err != nil {
return err
}
return runtime.Set("console", console)
}
func signVolcV4(request volcSignRequest, now func() time.Time) (map[string]string, error) {
parsedURL, err := url.Parse(request.URL)
if err != nil || parsedURL.Host == "" {
return nil, fmt.Errorf("invalid Volcengine signing URL")
}
region := request.Region
if region == "" {
region = "cn-north-1"
}
service := request.Service
if service == "" {
service = "cv"
}
timestamp := now().UTC()
if request.Timestamp != 0 {
timestamp = time.Unix(request.Timestamp, 0).UTC()
}
xDate := timestamp.Format("20060102T150405Z")
shortDate := timestamp.Format("20060102")
bodyHash := sha256.Sum256([]byte(request.Body))
requestPath := parsedURL.EscapedPath()
if requestPath == "" {
requestPath = "/"
}
headers := make(map[string]string, len(request.Headers)+3)
for name, value := range request.Headers {
headers[strings.ToLower(name)] = strings.TrimSpace(value)
}
headers["host"] = parsedURL.Host
headers["x-date"] = xDate
headers["x-content-sha256"] = hex.EncodeToString(bodyHash[:])
keys := make([]string, 0, len(headers))
for name := range headers {
keys = append(keys, name)
}
sort.Strings(keys)
var canonicalHeaders strings.Builder
for _, name := range keys {
canonicalHeaders.WriteString(name)
canonicalHeaders.WriteByte(':')
canonicalHeaders.WriteString(headers[name])
canonicalHeaders.WriteByte('\n')
}
signedHeaders := strings.Join(keys, ";")
canonicalRequest := strings.Join([]string{
strings.ToUpper(request.Method), requestPath, parsedURL.Query().Encode(),
canonicalHeaders.String(), signedHeaders, hex.EncodeToString(bodyHash[:]),
}, "\n")
canonicalHash := sha256.Sum256([]byte(canonicalRequest))
scope := fmt.Sprintf("%s/%s/%s/request", shortDate, region, service)
stringToSign := fmt.Sprintf("HMAC-SHA256\n%s\n%s\n%s", xDate, scope, hex.EncodeToString(canonicalHash[:]))
sign := func(key []byte, value string) []byte {
mac := hmac.New(sha256.New, key)
_, _ = mac.Write([]byte(value))
return mac.Sum(nil)
}
signingKey := sign(sign(sign([]byte(request.SecretKey), shortDate), region), service)
signingKey = sign(signingKey, "request")
signature := hex.EncodeToString(sign(signingKey, stringToSign))
return map[string]string{
"Authorization": fmt.Sprintf("HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s", request.AccessKey, scope, signedHeaders, signature),
"X-Date": xDate,
"X-Content-Sha256": hex.EncodeToString(bodyHash[:]),
}, nil
}