Skip to content
This repository has been archived by the owner on Jun 18, 2024. It is now read-only.

Commit

Permalink
fix: failed to parse machine token errors
Browse files Browse the repository at this point in the history
After migrating to an updated jwt package, tokens created with the old
library could not be parsed anymore. Now parsing works for old and new
generation jwt tokens.
  • Loading branch information
zbindenren committed Aug 5, 2021
1 parent a0ef11e commit 2cf360b
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 13 deletions.
82 changes: 69 additions & 13 deletions internal/auth/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package auth

import (
"fmt"
"strconv"
"strings"
"time"

jwt "github.com/golang-jwt/jwt/v4"
Expand All @@ -21,28 +23,22 @@ func NewTokenHandler(secret, issuer string) *TokenHandler {
}
}

// TokenClaims is like jwt standard claims with additional list of namespaces.
type TokenClaims struct {
jwt.StandardClaims
Namespaces []string `json:"namespaces,omitempty"`
}

// Create creates a new token. If expires is 0, it never expires.
func (t *TokenHandler) Create(id string, expires time.Duration, namespaces ...string) (string, error) {
now := time.Now()

claims := TokenClaims{
StandardClaims: jwt.StandardClaims{
CompatibleStandardClaims: CompatibleStandardClaims{
Id: id,
Issuer: t.issuer,
IssuedAt: now.Unix(),
NotBefore: now.Unix(),
IssuedAt: TokenTime(now.Unix()),
NotBefore: TokenTime(now.Unix()),
},
Namespaces: namespaces,
}

if expires > 0 {
claims.ExpiresAt = now.Add(expires).Unix()
claims.ExpiresAt = TokenTime(now.Add(expires).Unix())
}

token := jwt.New(jwt.SigningMethodHS256)
Expand All @@ -64,15 +60,15 @@ func (t *TokenHandler) Validate(token string) (*User, error) {
return nil, fmt.Errorf("invalid token: %w", err)
}

if claims.StandardClaims.Issuer != t.issuer {
if claims.CompatibleStandardClaims.Issuer != t.issuer {
return nil, fmt.Errorf("wrong issuer is '%s', not %s", tknClaims.Issuer, t.issuer)
}

u := User{
Username: claims.StandardClaims.Id,
Username: claims.CompatibleStandardClaims.Id,
Namespaces: claims.Namespaces,
Kind: MachineToken,
ExpiresAt: time.Unix(claims.StandardClaims.ExpiresAt, 0),
ExpiresAt: claims.ExpiresAt.Time(),
}

return &u, nil
Expand All @@ -89,3 +85,63 @@ func (t *TokenHandler) IsMachine(token string) (bool, error) {

return tknClaims["iss"] == t.issuer, nil
}

// TokenClaims is like jwt standard claims with additional list of namespaces.
type TokenClaims struct {
CompatibleStandardClaims
Namespaces []string `json:"namespaces,omitempty"`
}

// CompatibleStandardClaims is the same as jwt.StandardClaims but also
// allows float64 for durations (in order to be backward compatible to old library).
type CompatibleStandardClaims struct {
Audience string `json:"aud,omitempty"`
ExpiresAt TokenTime `json:"exp,omitempty"`
Id string `json:"jti,omitempty"` //nolint: revive,stylecheck // we to use the same name as the library
IssuedAt TokenTime `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore TokenTime `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}

// Valid validates standard claims.
func (c CompatibleStandardClaims) Valid() error {
claims := jwt.StandardClaims{
Audience: c.Audience,
ExpiresAt: int64(c.ExpiresAt),
Id: c.Id,
IssuedAt: int64(c.IssuedAt),
Issuer: c.Issuer,
NotBefore: int64(c.NotBefore),
Subject: c.Subject,
}

return claims.Valid()
}

// TokenTime represent the token times. It is an int64 with custom marshal and unmarshl
// to also alow float64 as token times for backward compatibility reasons. Old jwt package
// created float64 token times.
type TokenTime int64

// Time converts TokenTime to time.Time.
func (t *TokenTime) Time() time.Time {
return time.Unix(int64(*t), 0)
}

func (t *TokenTime) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%d", t)), nil
}

func (t *TokenTime) UnmarshalJSON(b []byte) error {
stripped := strings.ReplaceAll(string(b), `"`, "")

f, err := strconv.ParseFloat(stripped, 64)
if err != nil {
return err
}

*t = TokenTime(f)

return nil
}
45 changes: 45 additions & 0 deletions internal/auth/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,48 @@ func TestJWT(t *testing.T) {
assert.Nil(t, u)
})
}

func TestJWTCompatibility(t *testing.T) {

/*
{
"exp": 1628181960.502017,
"jti":"username",
"iat": 1628178360.502017,
"iss": "issuer",
"nbf": 1628178360.502017,
"namespaces": [
"name space1",
"namespace2"
]
}
*/
const oldToken = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjgxODE5NjAuNTAyMDE3LCJqdGkiOiJ1c2VybmFtZSIsImlhdCI6MTYyODE3ODM2MC41MDIwMTcsImlzcyI6Imlzc3VlciIsIm5iZiI6MTYyODE3ODM2MC41MDIwMTcsIm5hbWVzcGFjZXMiOlsibmFtZXNwYWNlMSIsIm5hbWVzcGFjZTIiXX0.ILa9euUbxTsVMBcJwCrhyTdbfV6-PO0c9jB9SFO3KD0`

/*
{
"exp": 1628182190,
"jti": "username",
"iat": 1628178590,
"iss": "issuer",
"nbf": 1628178590,
"namespaces": [
"namespace1",
"namespace2"
]
}
*/
const token = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjgxODIxOTAsImp0aSI6InVzZXJuYW1lIiwiaWF0IjoxNjI4MTc4NTkwLCJpc3MiOiJpc3N1ZXIiLCJuYmYiOjE2MjgxNzg1OTAsIm5hbWVzcGFjZXMiOlsibmFtZXNwYWNlMSIsIm5hbWVzcGFjZTIiXX0.bOiL4Bz4uQcF4t2FDqI081jrpO9AS3RyRNAhN63mnxY`

th := NewTokenHandler("thesecret", "issuer")

t.Run("valid old token", func(t *testing.T) {
_, err := th.Validate(oldToken)
assert.NoError(t, err)
})

t.Run("valid token", func(t *testing.T) {
_, err := th.Validate(token)
assert.NoError(t, err)
})
}

0 comments on commit 2cf360b

Please sign in to comment.