feat(subscribe): add short token generation and validation logic

This commit is contained in:
Chang lue Tsen 2025-11-18 12:03:14 -05:00
parent 8a4cfcbdb3
commit e3999ba75f
7 changed files with 86 additions and 1 deletions

View File

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

View File

@ -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 == "" {

View File

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

View File

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

View File

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

38
pkg/tool/string.go Normal file
View File

@ -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 (FisherYates)
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
}

27
pkg/tool/string_test.go Normal file
View File

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