This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
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",
|
||||
}
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
if c.GetHeader("X-Signature-Enabled") != "1" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
appId := c.GetHeader("X-App-Id")
|
||||
if appId == "" {
|
||||
result.HttpResult(c, nil, xerr.NewErrCode(xerr.InvalidAccess))
|
||||
c.Abort()
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user