Created
July 20, 2022 02:30
-
-
Save 9bany/61cc40d8de40895b23af69a539308437 to your computer and use it in GitHub Desktop.
Token maker in go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package token | |
import "time" | |
type Maker interface { | |
CreateToken(username string, duration time.Duration) (string, error) | |
VerifyToken(token string) (*Payload, error) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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