From e3999ba75f999fb1ef76b38fb3a2c3db9c04f1d3 Mon Sep 17 00:00:00 2001 From: Chang lue Tsen Date: Tue, 18 Nov 2025 12:03:14 -0500 Subject: [PATCH] feat(subscribe): add short token generation and validation logic --- apis/types.api | 1 + internal/handler/subscribe.go | 16 ++++++++ .../public/user/queryUserSubscribeLogic.go | 2 + internal/server.go | 2 +- internal/types/types.go | 1 + pkg/tool/string.go | 38 +++++++++++++++++++ pkg/tool/string_test.go | 27 +++++++++++++ 7 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 pkg/tool/string.go create mode 100644 pkg/tool/string_test.go diff --git a/apis/types.api b/apis/types.api index 3cecd4a..3f50d02 100644 --- a/apis/types.api +++ b/apis/types.api @@ -470,6 +470,7 @@ type ( Upload int64 `json:"upload"` Token string `json:"token"` Status uint8 `json:"status"` + Short string `json:"short"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } diff --git a/internal/handler/subscribe.go b/internal/handler/subscribe.go index bf72a19..6c228ed 100644 --- a/internal/handler/subscribe.go +++ b/internal/handler/subscribe.go @@ -23,6 +23,22 @@ func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { ua := c.GetHeader("User-Agent") req.UA = c.Request.Header.Get("User-Agent") req.Flag = c.Query("flag") + if svcCtx.Config.Subscribe.PanDomain { + domain := c.Request.Host + domainArr := strings.Split(domain, ".") + short, err := tool.FixedUniqueString(req.Token, 8, "") + if err != nil { + logger.Errorf("[SubscribeHandler] Generate short token failed: %v", err) + c.String(http.StatusInternalServerError, "Internal Server") + c.Abort() + return + } + if short != domainArr[0] { + c.String(http.StatusForbidden, "Access denied") + c.Abort() + return + } + } if svcCtx.Config.Subscribe.UserAgentLimit { if ua == "" { diff --git a/internal/logic/public/user/queryUserSubscribeLogic.go b/internal/logic/public/user/queryUserSubscribeLogic.go index 757f6fc..55e3770 100644 --- a/internal/logic/public/user/queryUserSubscribeLogic.go +++ b/internal/logic/public/user/queryUserSubscribeLogic.go @@ -60,6 +60,8 @@ func (l *QueryUserSubscribeLogic) QueryUserSubscribe() (resp *types.QueryUserSub } } + short, _ := tool.FixedUniqueString(item.Token, 8, "") + sub.Short = short sub.ResetTime = calculateNextResetTime(&sub) resp.List = append(resp.List, sub) } diff --git a/internal/server.go b/internal/server.go index 559c510..47969be 100644 --- a/internal/server.go +++ b/internal/server.go @@ -49,7 +49,7 @@ func initServer(svc *svc.ServiceContext) *gin.Engine { } r.Use(sessions.Sessions("ppanel", sessionStore)) // use cors middleware - r.Use(middleware.TraceMiddleware(svc), middleware.LoggerMiddleware(svc), middleware.CorsMiddleware, middleware.PanDomainMiddleware(svc), gin.Recovery()) + r.Use(middleware.TraceMiddleware(svc), middleware.LoggerMiddleware(svc), middleware.CorsMiddleware, gin.Recovery()) // register handlers handler.RegisterHandlers(r, svc) diff --git a/internal/types/types.go b/internal/types/types.go index 20a2b0f..dfe28bd 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -2597,6 +2597,7 @@ type UserSubscribe struct { Upload int64 `json:"upload"` Token string `json:"token"` Status uint8 `json:"status"` + Short string `json:"short"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } diff --git a/pkg/tool/string.go b/pkg/tool/string.go new file mode 100644 index 0000000..2567dc0 --- /dev/null +++ b/pkg/tool/string.go @@ -0,0 +1,38 @@ +package tool + +import ( + "crypto/sha256" + "encoding/binary" + "errors" + "math/rand" +) + +func FixedUniqueString(s string, length int, alphabet string) (string, error) { + if alphabet == "" { + alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + } + if length <= 0 { + return "", errors.New("length must be > 0") + } + if length > len(alphabet) { + return "", errors.New("length greater than available unique characters") + } + + // Generate deterministic seed from SHA256 + hash := sha256.Sum256([]byte(s)) + seed := int64(binary.LittleEndian.Uint64(hash[:8])) // 前 8 字节 + + r := rand.New(rand.NewSource(seed)) + + // Copy alphabet to mutable array + data := []rune(alphabet) + + // Deterministic shuffle (Fisher–Yates) + for i := len(data) - 1; i > 0; i-- { + j := r.Intn(i + 1) + data[i], data[j] = data[j], data[i] + } + + // Take first N characters + return string(data[:length]), nil +} diff --git a/pkg/tool/string_test.go b/pkg/tool/string_test.go new file mode 100644 index 0000000..0c44086 --- /dev/null +++ b/pkg/tool/string_test.go @@ -0,0 +1,27 @@ +package tool + +import ( + "testing" +) + +func TestFixedUniqueString(t *testing.T) { + a := "example" + b := "example1" + c := "example" + + strA1, err := FixedUniqueString(a, 8, "") + strB1, err := FixedUniqueString(b, 8, "") + strC1, err := FixedUniqueString(c, 8, "") + if err != nil { + t.Logf("Error: %v", err.Error()) + return + } + if strA1 != strC1 { + t.Errorf("Expected strA1 and strC1 to be equal, got %s and %s", strA1, strC1) + } + if strA1 == strB1 { + t.Errorf("Expected strA1 and strB1 to be different, got %s and %s", strA1, strB1) + } + t.Logf("strA1 and strB1 are not equal, strA1: %s, strB1: %s", strA1, strB1) + t.Logf("strA1 and strC1 are equal,strA1: %s, strC1: %s", strA1, strC1) +}