All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 8m7s
Co-Authored-By: claude-flow <ruv@ruv.net>
133 lines
3.0 KiB
Go
133 lines
3.0 KiB
Go
package middleware
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/perfect-panel/server/internal/svc"
|
|
"github.com/perfect-panel/server/pkg/result"
|
|
"github.com/perfect-panel/server/pkg/signature"
|
|
"github.com/perfect-panel/server/pkg/xerr"
|
|
)
|
|
|
|
var (
|
|
publicSignaturePrefixes = []string{
|
|
"/v1/auth",
|
|
"/v1/auth/oauth",
|
|
"/v1/common",
|
|
"/v1/public",
|
|
}
|
|
defaultSignatureSkipPrefixes = []string{
|
|
"/v1/notify/",
|
|
"/v1/iap/notifications",
|
|
"/v1/telegram/webhook",
|
|
"/v1/subscribe/config",
|
|
"/v1/common/log/report",
|
|
}
|
|
)
|
|
|
|
func SignatureMiddleware(srvCtx *svc.ServiceContext) func(c *gin.Context) {
|
|
skipPrefixes := collectSignatureSkipPrefixes(srvCtx)
|
|
return func(c *gin.Context) {
|
|
path := c.Request.URL.Path
|
|
if !isPublicSignaturePath(path) {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
for _, prefix := range skipPrefixes {
|
|
if strings.HasPrefix(path, prefix) {
|
|
c.Next()
|
|
return
|
|
}
|
|
}
|
|
if !srvCtx.Config.Signature.EnableSignature {
|
|
c.Next()
|
|
return
|
|
}
|
|
appId := c.GetHeader("X-App-Id")
|
|
if appId == "" {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
timestamp := c.GetHeader("X-Timestamp")
|
|
nonce := c.GetHeader("X-Nonce")
|
|
sig := c.GetHeader("X-Signature")
|
|
if timestamp == "" || nonce == "" || sig == "" {
|
|
result.HttpResult(c, nil, xerr.NewErrCode(xerr.SignatureMissing))
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
var bodyBytes []byte
|
|
if c.Request.Body != nil {
|
|
bodyBytes, _ = io.ReadAll(c.Request.Body)
|
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
|
}
|
|
|
|
sts := signature.BuildStringToSign(
|
|
c.Request.Method,
|
|
path,
|
|
c.Request.URL.RawQuery,
|
|
bodyBytes,
|
|
appId,
|
|
timestamp,
|
|
nonce,
|
|
)
|
|
if err := srvCtx.SignatureValidator.Validate(c.Request.Context(), appId, timestamp, nonce, sig, sts); err != nil {
|
|
result.HttpResult(c, nil, xerr.NewErrCode(mapSignatureErr(err)))
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func isPublicSignaturePath(path string) bool {
|
|
for _, prefix := range publicSignaturePrefixes {
|
|
if strings.HasPrefix(path, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func collectSignatureSkipPrefixes(srvCtx *svc.ServiceContext) []string {
|
|
prefixSet := make(map[string]struct{}, len(defaultSignatureSkipPrefixes)+len(srvCtx.Config.AppSignature.SkipPrefixes)+1)
|
|
for _, prefix := range defaultSignatureSkipPrefixes {
|
|
prefixSet[prefix] = struct{}{}
|
|
}
|
|
for _, prefix := range srvCtx.Config.AppSignature.SkipPrefixes {
|
|
if strings.TrimSpace(prefix) == "" {
|
|
continue
|
|
}
|
|
prefixSet[prefix] = struct{}{}
|
|
}
|
|
if path := strings.TrimSpace(srvCtx.Config.Subscribe.SubscribePath); path != "" {
|
|
prefixSet[path] = struct{}{}
|
|
}
|
|
|
|
prefixes := make([]string, 0, len(prefixSet))
|
|
for prefix := range prefixSet {
|
|
prefixes = append(prefixes, prefix)
|
|
}
|
|
return prefixes
|
|
}
|
|
|
|
func mapSignatureErr(err error) uint32 {
|
|
switch err {
|
|
case signature.ErrSignatureMissing:
|
|
return xerr.SignatureMissing
|
|
case signature.ErrSignatureExpired:
|
|
return xerr.SignatureExpired
|
|
case signature.ErrSignatureReplay:
|
|
return xerr.SignatureReplay
|
|
default:
|
|
return xerr.SignatureInvalid
|
|
}
|
|
}
|