From bc8e7d08f2a6ed82369e72eda4e2d503b357bc0f Mon Sep 17 00:00:00 2001 From: Professor Fartsalot Date: Sun, 31 Aug 2025 23:40:23 -0400 Subject: [PATCH] Add JWT generation, creation and validation along with tests - from arieshi255/SnowcloakUtils --- global/global.go | 4 ++ go.mod | 2 + go.sum | 2 + jwt/jwt.go | 114 +++++++++++++++++++++++++++++++++++++++++++++++ jwt/jwt_test.go | 44 ++++++++++++++++++ 5 files changed, 166 insertions(+) create mode 100644 jwt/jwt.go create mode 100644 jwt/jwt_test.go diff --git a/global/global.go b/global/global.go index 5339968..3e5feeb 100644 --- a/global/global.go +++ b/global/global.go @@ -6,6 +6,10 @@ import ( "math/rand" ) +type SnowcloakConfigurationBase struct { + Jwt string +} + func GenerateRandomString(length int) string { // C# had optional parameters that allowed lowercase for chardata and gpose lobbies, Go doesn't. // We can probably get away with just uppercase. diff --git a/go.mod b/go.mod index 9f4722f..491bcdf 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module SnowcloakUtils go 1.25 require github.com/showwin/speedtest-go v1.7.10 + +require github.com/golang-jwt/jwt/v5 v5.3.0 // indirect diff --git a/go.sum b/go.sum index 0e7db32..fdce95e 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/showwin/speedtest-go v1.7.10 h1:9o5zb7KsuzZKn+IE2//z5btLKJ870JwO6ETayUkqRFw= github.com/showwin/speedtest-go v1.7.10/go.mod h1:Ei7OCTmNPdWofMadzcfgq1rUO7mvJy9Jycj//G7vyfA= diff --git a/jwt/jwt.go b/jwt/jwt.go new file mode 100644 index 0000000..a9ac521 --- /dev/null +++ b/jwt/jwt.go @@ -0,0 +1,114 @@ +package jwt + +import ( + "encoding/base64" + "log" + "sync" + + "SnowcloakUtils/global" + + "github.com/golang-jwt/jwt/v5" +) + +type SnowcloakClaimTypes struct { + Uid string `json:"uid"` + Alias string + CharaIdent string + Internal string + Continent string + jwt.RegisteredClaims +} + +type TokenProvider struct { + mu sync.Mutex + tokens map[string]string + config *global.SnowcloakConfigurationBase +} + +func NewTokenProvider(cfg *global.SnowcloakConfigurationBase) *TokenProvider { + return &TokenProvider{ + tokens: make(map[string]string), + config: cfg, + } +} + +func (p *TokenProvider) Token() string { + p.mu.Lock() + defer p.mu.Unlock() + + signingKey := p.config.Jwt + + if token, ok := p.tokens[signingKey]; ok { + return token + } + + token := p.GenerateToken("shard1", "teststringteststringteststringteststringteststringteststringteststringteststringteststringteststring") // Should be read from config file + p.tokens[signingKey] = token + return token +} + +func (p *TokenProvider) GenerateToken(shard string, authSigningKey string) string { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, SnowcloakClaimTypes{ + Uid: shard, + Internal: "true", + }) + + secret, err := base64.StdEncoding.DecodeString(authSigningKey) + + if err != nil { + log.Fatalf("Failed to decode secret into byte array") + } + + ss, err := token.SignedString(secret) + + if err != nil { + log.Fatalf("Failed to sign JWT with signing key") + } + + p.tokens[authSigningKey] = ss + + // log.Printf("Generated Token: %s", ss) + + return ss +} + +func CreateToken(claims *SnowcloakClaimTypes, authSigningKey string) string { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + secret, err := base64.StdEncoding.DecodeString(authSigningKey) + + if err != nil { + log.Fatalf("Failed to decode secret into byte array") + } + + ss, err := token.SignedString(secret) + + if err != nil { + log.Fatalf("Failed to sign JWT with signing key") + } + + return ss +} + +func ValidateToken(tokenString string, authSigningKey string) jwt.MapClaims { + secret, err := base64.StdEncoding.DecodeString(authSigningKey) + + if err != nil { + log.Fatalf("Failed to decode secret into byte array") + } + + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { + return secret, nil + }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()})) + + if err != nil { + log.Println("Failed to parse token from string") + return nil + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok { + return claims + } else { + return nil + } +} diff --git a/jwt/jwt_test.go b/jwt/jwt_test.go new file mode 100644 index 0000000..23a1a7c --- /dev/null +++ b/jwt/jwt_test.go @@ -0,0 +1,44 @@ +package jwt + +import ( + "SnowcloakUtils/global" + "fmt" + "testing" +) + +func TestCreateAndValidateToken(t *testing.T) { + fmt.Println("Test: token creation") + token1 := CreateToken(&SnowcloakClaimTypes{Uid: "0", CharaIdent: "test1", Alias: "myAlias1", Continent: "EU"}, "teststringteststringteststringteststringteststringteststringteststringteststringteststringteststring") + fmt.Println(token1) + fmt.Println("Test: token validation") + fmt.Println(ValidateToken(token1, "teststringteststringteststringteststringteststringteststringteststringteststringteststringteststring")) + fmt.Println("Test: bad token creation") + token2 := CreateToken(&SnowcloakClaimTypes{Uid: "423235", CharaIdent: "test1", Alias: "myAlias", Continent: "EU"}, "teststringteststringteststringteststringteststringteststringteststringteststringteststringteststring") + fmt.Println(token2) + fmt.Println("Test: bad token validation") + badValidate := ValidateToken(token2, "dGVzdHRlc3Rpbmd0dGVzdGluZw==") + if badValidate != nil { + t.Errorf("Token wasn't invalid") + } else { + fmt.Println("Didn't validate invalid token") + } +} + +func TestGenerateToken(t *testing.T) { + tokenProvider := NewTokenProvider(&global.SnowcloakConfigurationBase{Jwt: "teststringteststringteststringteststringteststringteststringteststringteststringteststringteststring"}) + + fmt.Println("Test: token generation") + fmt.Println(tokenProvider.GenerateToken("shard2", "teststringteststringteststringteststringteststringteststringteststringteststringteststringteststring")) + fmt.Println("Test: get cached token") + fmt.Println(tokenProvider.Token()) + fmt.Println("Test: update token") + fmt.Println(tokenProvider.GenerateToken("shard3", "teststringteststringteststringteststringteststringteststringteststringteststringteststringteststring")) + fmt.Println("Test: get updated token") + fmt.Println(tokenProvider.Token()) + fmt.Println("Test: create bad token") + fmt.Println(tokenProvider.GenerateToken("shard4", "dGVzdHRlc3Rpbmd0dGVzdGluZw==")) + fmt.Println("Test: get bad token") + fmt.Println(tokenProvider.Token()) + fmt.Println("Test: validate cached token") + fmt.Println(ValidateToken(tokenProvider.Token(), "teststringteststringteststringteststringteststringteststringteststringteststringteststringteststring")) +}