113 lines
3.4 KiB
Go
113 lines
3.4 KiB
Go
// Package tencentsig implements Tencent UserSig v2 signing shared by Chat and RTC.
|
|
package tencentsig
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/zlib"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Config contains the paid Tencent application credentials required to sign UserSig.
|
|
type Config struct {
|
|
// SDKAppID is the application ID assigned by Tencent Cloud.
|
|
SDKAppID int64
|
|
// SecretKey is the application key; it must never leave backend configuration.
|
|
SecretKey string
|
|
// TTL controls how long the generated UserSig remains valid.
|
|
TTL time.Duration
|
|
}
|
|
|
|
// Result is the transport-neutral UserSig output shared by Chat and RTC wrappers.
|
|
type Result struct {
|
|
// SDKAppID lets clients initialize the matching Tencent SDK.
|
|
SDKAppID int64 `json:"sdk_app_id"`
|
|
// UserID is the Tencent identifier derived from the immutable internal user_id.
|
|
UserID string `json:"user_id"`
|
|
// UserSig is the signed credential passed to Tencent SDK login or enterRoom.
|
|
UserSig string `json:"user_sig"`
|
|
// ExpireAtMS lets clients refresh before Tencent rejects the credential.
|
|
ExpireAtMS int64 `json:"expire_at_ms"`
|
|
}
|
|
|
|
// GenerateUserSig signs a Tencent UserSig with the HMAC-SHA256 v2 algorithm.
|
|
func GenerateUserSig(cfg Config, userID string, now time.Time) (Result, error) {
|
|
userID = strings.TrimSpace(userID)
|
|
if cfg.SDKAppID <= 0 || cfg.SecretKey == "" || cfg.TTL <= 0 || userID == "" {
|
|
// UserSig protects paid Tencent resources; incomplete config must fail closed.
|
|
return Result{}, fmt.Errorf("tencent usersig config is incomplete")
|
|
}
|
|
|
|
current := now.Unix()
|
|
expire := int64(cfg.TTL.Seconds())
|
|
rawSig := signPayload(cfg.SecretKey, userID, cfg.SDKAppID, current, expire)
|
|
payload := map[string]any{
|
|
"TLS.ver": "2.0",
|
|
"TLS.identifier": userID,
|
|
"TLS.sdkappid": cfg.SDKAppID,
|
|
"TLS.expire": expire,
|
|
"TLS.time": current,
|
|
"TLS.sig": rawSig,
|
|
}
|
|
|
|
payloadBytes, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return Result{}, err
|
|
}
|
|
|
|
compressed, err := zlibCompress(payloadBytes)
|
|
if err != nil {
|
|
return Result{}, err
|
|
}
|
|
|
|
return Result{
|
|
SDKAppID: cfg.SDKAppID,
|
|
UserID: userID,
|
|
UserSig: encodeTencentBase64(compressed),
|
|
ExpireAtMS: now.Add(cfg.TTL).UnixMilli(),
|
|
}, nil
|
|
}
|
|
|
|
func signPayload(secret string, userID string, sdkAppID int64, current int64, expire int64) string {
|
|
// Tencent v2 signing string is line-based and key names are case-sensitive.
|
|
content := "TLS.identifier:" + userID + "\n" +
|
|
"TLS.sdkappid:" + strconv.FormatInt(sdkAppID, 10) + "\n" +
|
|
"TLS.time:" + strconv.FormatInt(current, 10) + "\n" +
|
|
"TLS.expire:" + strconv.FormatInt(expire, 10) + "\n"
|
|
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
_, _ = mac.Write([]byte(content))
|
|
|
|
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
|
}
|
|
|
|
func zlibCompress(payload []byte) ([]byte, error) {
|
|
var buffer bytes.Buffer
|
|
writer := zlib.NewWriter(&buffer)
|
|
if _, err := writer.Write(payload); err != nil {
|
|
_ = writer.Close()
|
|
return nil, err
|
|
}
|
|
if err := writer.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return buffer.Bytes(), nil
|
|
}
|
|
|
|
func encodeTencentBase64(payload []byte) string {
|
|
// Tencent UserSig uses standard base64 with URL-hostile characters remapped.
|
|
encoded := base64.StdEncoding.EncodeToString(payload)
|
|
encoded = strings.ReplaceAll(encoded, "+", "*")
|
|
encoded = strings.ReplaceAll(encoded, "/", "-")
|
|
encoded = strings.ReplaceAll(encoded, "=", "_")
|
|
|
|
return encoded
|
|
}
|