Files
api_tmpl/internal/auth/jwt_maker.go
T
2026-01-08 01:15:07 +00:00

129 lines
2.9 KiB
Go

package auth
import (
"errors"
"fmt"
"time"
"github.com/FernandoJVideira/simple_bank/internal/config"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
// JWTMaker is a JSON Web Token maker
type JWTMaker struct {
secretKey string
config config.Config
}
func NewJWTClaims(payload *Payload, config config.Config) jwt.Claims {
return &jwt.MapClaims{
"iat": jwt.NewNumericDate(payload.IssuedAt),
"iss": config.TokenISS,
"exp": jwt.NewNumericDate(payload.ExpiredAt),
"nbf": jwt.NewNumericDate(payload.IssuedAt),
"aud": []string{config.TokenAUD},
"sub": payload.Username,
"role": payload.Role,
"jti": payload.ID.String(),
}
}
// NewJWTMaker creates a new JWTMaker
func NewJWTMaker(config config.Config) (Maker, error) {
if len(config.TokenKey) < config.MinTokenSize {
return nil, fmt.Errorf("invalid key size: must be at least %d characters", config.MinTokenSize)
}
return &JWTMaker{secretKey: config.TokenKey, config: config}, nil
}
func (maker *JWTMaker) CreateToken(username string, role string, duration time.Duration) (string, *Payload, error) {
payload, err := NewPayload(username, role, duration)
if err != nil {
return "", nil, err
}
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, NewJWTClaims(payload, maker.config))
signedString, err := jwtToken.SignedString([]byte(maker.secretKey))
return signedString, payload, err
}
func (maker *JWTMaker) VerifyToken(token string) (*Payload, error) {
payloadClaims := jwt.MapClaims{}
keyFunc := func(token *jwt.Token) (any, error) {
_, ok := token.Method.(*jwt.SigningMethodHMAC)
if !ok {
return nil, ErrInvalidToken
}
return []byte(maker.secretKey), nil
}
jwtToken, err := jwt.ParseWithClaims(token, payloadClaims, keyFunc)
if err != nil {
if errors.Is(err, jwt.ErrTokenExpired) {
return nil, ErrExpiredToken
} else if errors.Is(err, ErrInvalidToken) {
return nil, ErrInvalidToken
} else {
return nil, err
}
}
payloadClaims, ok := jwtToken.Claims.(jwt.MapClaims)
if !ok {
return nil, ErrInvalidToken
}
payload, err := parseTypes(payloadClaims)
if err != nil {
return nil, err
}
return payload, nil
}
func parseTypes(claims jwt.MapClaims) (*Payload, error) {
// Safe type assertions with error checking
jti, ok := claims["jti"].(string)
if !ok {
return nil, ErrInvalidToken
}
id, err := uuid.Parse(jti)
if err != nil {
return nil, ErrInvalidToken
}
username, ok := claims["sub"].(string)
if !ok {
return nil, ErrInvalidToken
}
role, ok := claims["role"].(string)
if !ok {
return nil, ErrInvalidToken
}
// Parse numeric dates
nbf, ok := claims["nbf"].(float64)
if !ok {
return nil, ErrInvalidToken
}
exp, ok := claims["exp"].(float64)
if !ok {
return nil, ErrInvalidToken
}
return &Payload{
ID: id,
Username: username,
Role: role,
IssuedAt: time.Unix(int64(nbf), 0),
ExpiredAt: time.Unix(int64(exp), 0),
}, nil
}