Files
backend/cloudprint-backend/pkg/jwt/jwt.go

106 lines
2.6 KiB
Go

package jwt
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
var (
ErrInvalidToken = errors.New("invalid token")
ErrExpiredToken = errors.New("token has expired")
)
type Claims struct {
UserID int32 `json:"user_id"`
UserType string `json:"user_type"` // "user" or "admin"
Username string `json:"username,omitempty"`
jwt.RegisteredClaims
}
type JWTManager struct {
secretKey []byte
accessTokenTTL time.Duration
refreshTokenTTL time.Duration
}
func NewJWTManager(secret string, accessTokenTTL, refreshTokenTTL time.Duration) *JWTManager {
return &JWTManager{
secretKey: []byte(secret),
accessTokenTTL: accessTokenTTL,
refreshTokenTTL: refreshTokenTTL,
}
}
func (m *JWTManager) GenerateAccessToken(userID int32, userType string, username string) (string, error) {
now := time.Now()
claims := &Claims{
UserID: userID,
UserType: userType,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(m.accessTokenTTL)),
IssuedAt: jwt.NewNumericDate(now),
NotBefore: jwt.NewNumericDate(now),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(m.secretKey)
}
func (m *JWTManager) GenerateRefreshToken(userID int32, userType string) (string, error) {
now := time.Now()
claims := &Claims{
UserID: userID,
UserType: userType,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(m.refreshTokenTTL)),
IssuedAt: jwt.NewNumericDate(now),
NotBefore: jwt.NewNumericDate(now),
Subject: "refresh",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(m.secretKey)
}
func (m *JWTManager) ValidateToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, ErrInvalidToken
}
return m.secretKey, nil
})
if err != nil {
if errors.Is(err, jwt.ErrTokenExpired) {
return nil, ErrExpiredToken
}
return nil, ErrInvalidToken
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, ErrInvalidToken
}
return claims, nil
}
func (m *JWTManager) RefreshAccessToken(refreshToken string) (string, error) {
claims, err := m.ValidateToken(refreshToken)
if err != nil {
return "", err
}
// Only refresh tokens with subject "refresh" can be used
if claims.Subject != "refresh" {
return "", ErrInvalidToken
}
return m.GenerateAccessToken(claims.UserID, claims.UserType, claims.Username)
}