package auth import ( "fmt" "time" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) type TokenPair struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresAt int64 `json:"expires_at"` } type Claims struct { UserID uuid.UUID `json:"user_id"` Email string `json:"email"` Role string `json:"role"` jwt.RegisteredClaims } type JWTManager struct { secret []byte accessExpiry time.Duration refreshExpiry time.Duration } func NewJWTManager(secret string, accessExpiry, refreshExpiry time.Duration) *JWTManager { return &JWTManager{ secret: []byte(secret), accessExpiry: accessExpiry, refreshExpiry: refreshExpiry, } } func (m *JWTManager) GenerateTokenPair(userID uuid.UUID, email, role string) (*TokenPair, error) { now := time.Now() accessExp := now.Add(m.accessExpiry) accessClaims := &Claims{ UserID: userID, Email: email, Role: role, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(accessExp), IssuedAt: jwt.NewNumericDate(now), Subject: userID.String(), }, } accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims) accessStr, err := accessToken.SignedString(m.secret) if err != nil { return nil, fmt.Errorf("sign access token: %w", err) } refreshClaims := &Claims{ UserID: userID, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(now.Add(m.refreshExpiry)), IssuedAt: jwt.NewNumericDate(now), Subject: userID.String(), }, } refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims) refreshStr, err := refreshToken.SignedString(m.secret) if err != nil { return nil, fmt.Errorf("sign refresh token: %w", err) } return &TokenPair{ AccessToken: accessStr, RefreshToken: refreshStr, ExpiresAt: accessExp.Unix(), }, nil } func (m *JWTManager) ValidateToken(tokenStr string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (any, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return m.secret, nil }) if err != nil { return nil, fmt.Errorf("parse token: %w", err) } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, fmt.Errorf("invalid token claims") } return claims, nil }