Files
upstream-centrifugo/internal/cli/gensubtoken.go
upstream-mirror b352a9a30d
Some checks failed
test / Lint (push) Failing after 3s
test / Test with Go 1.26.8 (push) Failing after 2s
snapshot of https://github.com/centrifugal/centrifugo.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:17:21 +05:00

124 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package cli
import (
"encoding/json"
"errors"
"fmt"
"os"
"time"
"github.com/centrifugal/centrifugo/v6/internal/config"
"github.com/centrifugal/centrifugo/v6/internal/confighelpers"
"github.com/centrifugal/centrifugo/v6/internal/jwtverify"
"github.com/cristalhq/jwt/v5"
"github.com/spf13/cobra"
"github.com/tidwall/sjson"
)
func GenSubToken() *cobra.Command {
var genSubTokenConfigFile string
var genSubTokenUser string
var genSubTokenChannel string
var genSubTokenTTL int64
var genSubTokenQuiet bool
var genSubTokenCmd = &cobra.Command{
Use: "gensubtoken",
Short: "Generate sample subscription JWT for user",
Long: `Generate sample subscription JWT for user`,
Run: func(cmd *cobra.Command, args []string) {
genSubToken(cmd, genSubTokenConfigFile, genSubTokenUser, genSubTokenChannel, genSubTokenTTL, genSubTokenQuiet)
},
}
genSubTokenCmd.Flags().StringVarP(&genSubTokenConfigFile, "config", "c", "config.json", "path to config file")
genSubTokenCmd.Flags().StringVarP(&genSubTokenUser, "user", "u", "", "user ID")
genSubTokenCmd.Flags().StringVarP(&genSubTokenChannel, "channel", "s", "", "channel")
genSubTokenCmd.Flags().Int64VarP(&genSubTokenTTL, "ttl", "t", 3600*24*7, "token TTL in seconds, use -1 for token without expiration")
genSubTokenCmd.Flags().BoolVarP(&genSubTokenQuiet, "quiet", "q", false, "only output the token without anything else")
return genSubTokenCmd
}
func genSubToken(
cmd *cobra.Command, genSubTokenConfigFile string, genSubTokenUser string,
genSubTokenChannel string, genSubTokenTTL int64, genSubTokenQuiet bool,
) {
cfg, _, err := config.GetConfig(cmd, genSubTokenConfigFile)
if err != nil {
fmt.Printf("error getting config: %v\n", err)
os.Exit(1)
}
if genSubTokenChannel == "" {
fmt.Println("channel is required")
os.Exit(1)
}
verifierConfig, err := confighelpers.MakeVerifierConfig(cfg.Client.Token)
if err != nil {
fmt.Printf("error: %v\n", err)
os.Exit(1)
}
if cfg.Client.SubscriptionToken.Enabled {
verifierConfig, err = confighelpers.MakeVerifierConfig(cfg.Client.SubscriptionToken.Token)
if err != nil {
fmt.Printf("error: %v\n", err)
os.Exit(1)
}
}
token, err := generateSubToken(verifierConfig, genSubTokenUser, genSubTokenChannel, genSubTokenTTL)
if err != nil {
fmt.Printf("error: %v\n", err)
os.Exit(1)
}
var user = fmt.Sprintf("user \"%s\"", genSubTokenUser)
if genSubTokenUser == "" {
user = "anonymous user"
}
exp := "without expiration"
if genSubTokenTTL >= 0 {
exp = fmt.Sprintf("with expiration TTL %s", time.Duration(genSubTokenTTL)*time.Second)
}
if genSubTokenQuiet {
fmt.Print(token)
return
}
fmt.Printf("HMAC SHA-256 JWT for %s and channel \"%s\" %s:\n%s\n", user, genSubTokenChannel, exp, token)
}
// generateSubToken generates sample subscription JWT for user.
func generateSubToken(config jwtverify.VerifierConfig, user string, channel string, ttlSeconds int64) (string, error) {
if config.HMACSecretKey == "" {
return "", errors.New("gensubtoken only supports generating HMAC-signed tokens, but no HMAC secret key is set in the configuration (client.subscription_token.token.hmac_secret_key, or client.token.hmac_secret_key when subscription token is not separately configured) – if you use RSA/ECDSA/JWKS token verification you need to generate the JWT with your own tooling")
}
signer, err := jwt.NewSignerHS(jwt.HS256, []byte(config.HMACSecretKey))
if err != nil {
return "", fmt.Errorf("error creating HMAC signer: %w", err)
}
builder := jwt.NewBuilder(signer)
claims := jwtverify.SubscribeTokenClaims{
RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: user,
},
Channel: channel,
}
if ttlSeconds > 0 {
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Duration(ttlSeconds) * time.Second))
}
encodedClaims, err := json.Marshal(claims)
if err != nil {
return "", err
}
if config.UserIDClaim != "" {
encodedClaims, err = sjson.SetBytes(encodedClaims, config.UserIDClaim, user)
if err != nil {
return "", err
}
}
token, err := builder.Build(encodedClaims)
if err != nil {
return "", err
}
return token.String(), nil
}