// Package auth implements parsing and validation of the JWT tokens used by // the game server. Both the game token and the user token are HS256-signed // with the same shared secret (the main server's secret_key_base). package auth import ( "fmt" "github.com/golang-jwt/jwt/v5" ) // GameClaims is the payload of the game token issued by the main server // when a match is created. type GameClaims struct { MatchID string `json:"match_id"` Player1ID int64 `json:"player1_id"` Player2ID int64 `json:"player2_id"` GameServer string `json:"game_server"` jwt.RegisteredClaims } // UserClaims is the payload of the personal token of a player. type UserClaims struct { UserID int64 `json:"user_id"` jwt.RegisteredClaims } func keyFunc(secret []byte) jwt.Keyfunc { return func(t *jwt.Token) (any, error) { if t.Method.Alg() != jwt.SigningMethodHS256.Alg() { return nil, fmt.Errorf("unexpected signing method: %s", t.Method.Alg()) } return secret, nil } } // ParseGameToken verifies the signature and the expiration time of a game // token and returns its claims. func ParseGameToken(secret []byte, tokenString string) (*GameClaims, error) { claims := &GameClaims{} _, err := jwt.ParseWithClaims(tokenString, claims, keyFunc(secret), jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}), jwt.WithExpirationRequired(), ) if err != nil { return nil, fmt.Errorf("invalid game token: %w", err) } if claims.MatchID == "" { return nil, fmt.Errorf("invalid game token: empty match_id") } return claims, nil } // ParseUserToken verifies the signature and the expiration time of a user // token and returns its claims. func ParseUserToken(secret []byte, tokenString string) (*UserClaims, error) { claims := &UserClaims{} _, err := jwt.ParseWithClaims(tokenString, claims, keyFunc(secret), jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}), jwt.WithExpirationRequired(), ) if err != nil { return nil, fmt.Errorf("invalid user token: %w", err) } return claims, nil }