Skip to content

Instantly share code, notes, and snippets.

@9bany
Created July 20, 2022 02:30
Show Gist options
  • Save 9bany/61cc40d8de40895b23af69a539308437 to your computer and use it in GitHub Desktop.
Save 9bany/61cc40d8de40895b23af69a539308437 to your computer and use it in GitHub Desktop.
Token maker in go
package token
import (
"errors"
"fmt"
"time"
"github.com/dgrijalva/jwt-go"
)
const minSecertKeySize = 32
type JWTMaker struct {
secretKey string
}
func NewJWTMaker(secretKey string) (Maker, error) {
if len(secretKey) < minSecertKeySize {
return nil, fmt.Errorf("invalid key size: must be at least %d characters", minSecertKeySize)
}
return &JWTMaker{secretKey}, nil
}
func (maker *JWTMaker) CreateToken(username string, duration time.Duration) (string, error) {
payload, err := NewPayload(username, duration)
if err != nil {
return "", err
}
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
return jwtToken.SignedString([]byte(maker.secretKey))
}
func (maker *JWTMaker) VerifyToken(token string) (*Payload, error) {
keyFunc := func(token *jwt.Token) (interface{}, error) {
_, ok := token.Method.(*jwt.SigningMethodHMAC)
if !ok {
return nil, ErrInvalidToken
}
return []byte(maker.secretKey), nil
}
jwtToken, err := jwt.ParseWithClaims(token, &Payload{}, keyFunc)
if err != nil {
verr, ok := err.(*jwt.ValidationError)
if ok && errors.Is(verr.Inner, ErrExpiredToken) {
return nil, ErrExpiredToken
}
return nil, ErrInvalidToken
}
payload, ok := jwtToken.Claims.(*Payload)
if !ok {
return nil, ErrInvalidToken
}
return payload, nil
}
package token
import (
"browng/rony-o-cloud/util"
"testing"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/stretchr/testify/require"
)
func TestJWTMaker(t *testing.T) {
maker, err := NewJWTMaker(util.RandomString(33))
require.NoError(t, err)
username := util.RandomOwnerName()
duration := time.Minute
issueAt := time.Now()
expiredAt := issueAt.Add(duration)
token, err := maker.CreateToken(username, duration)
require.NoError(t, err)
require.NotEmpty(t, token)
payload, err := maker.VerifyToken(token)
require.NoError(t, err)
require.NotEmpty(t, payload)
require.NotZero(t, payload.ID)
require.Equal(t, username, payload.Username)
require.WithinDuration(t, issueAt, payload.IssueAt, time.Second)
require.WithinDuration(t, expiredAt, payload.ExpireAt, time.Second)
}
func TestJWTTokenExpired(t *testing.T) {
maker, err := NewJWTMaker(util.RandomString(33))
require.NoError(t, err)
token, err := maker.CreateToken(util.RandomOwnerName(), -time.Minute)
require.NoError(t, err)
require.NotEmpty(t, token)
payload, err := maker.VerifyToken(token)
require.EqualError(t, err, ErrExpiredToken.Error())
require.Nil(t, payload)
}
func TestInvalidJWTTokenAlgNone(t *testing.T) {
payload, err := NewPayload(util.RandomOwnerName(), time.Minute)
require.NoError(t, err)
jwtToken := jwt.NewWithClaims(jwt.SigningMethodNone, payload)
token, err := jwtToken.SignedString(jwt.UnsafeAllowNoneSignatureType)
require.NoError(t, err)
maker, err := NewJWTMaker(util.RandomString(32))
require.NoError(t, err)
payload, err = maker.VerifyToken(token)
require.Error(t, err)
require.EqualError(t, err, ErrInvalidToken.Error())
require.Nil(t, payload)
}
func TestInvalidKeySecretSize(t *testing.T) {
maker, err := NewJWTMaker(util.RandomString(10))
require.Error(t, err)
require.EqualError(t, err, ErrInvalidKeySize.Error())
require.Nil(t, maker)
}
package token
import "time"
type Maker interface {
CreateToken(username string, duration time.Duration) (string, error)
VerifyToken(token string) (*Payload, error)
}
package token
import (
"errors"
"fmt"
"time"
"github.com/aead/chacha20poly1305"
"github.com/google/uuid"
)
var ErrExpiredToken = errors.New("token has expired")
var ErrInvalidToken = errors.New("token invalid")
var ErrInvalidKeySize = fmt.Errorf("invalid key size: must be at least %d characters", minSecertKeySize)
var ErrInvalidPasetoKeySize = fmt.Errorf("inavlid key size: must be exacly %d characters", chacha20poly1305.KeySize)
type Payload struct {
ID uuid.UUID `json:"id"`
Username string `json:"username"`
IssueAt time.Time `json:"issue_at"`
ExpireAt time.Time `json:"expired_at"`
}
func NewPayload(username string, duration time.Duration) (*Payload, error) {
tokenID, err := uuid.NewRandom()
if err != nil {
return nil, err
}
payload := &Payload{
ID: tokenID,
Username: username,
IssueAt: time.Now(),
ExpireAt: time.Now().Add(duration),
}
return payload, nil
}
func (payload *Payload) Valid() error {
if time.Now().After(payload.ExpireAt) {
return ErrExpiredToken
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment