// Package auth provides cryptographic core for authentication. package auth import ( "errors" "strconv" "time" "github.com/golang-jwt/jwt/v5" ) // ErrInvalidToken is returned when a token is invalid or cannot be parsed. var ErrInvalidToken = errors.New("auth: invalid token") // TokenManager handles issuing and parsing JWT access tokens. type TokenManager struct { secret []byte } // NewTokenManager creates a TokenManager using the provided secret key. func NewTokenManager(secret string) *TokenManager { return &TokenManager{secret: []byte(secret)} } // IssueAccessToken issues a signed JWT access token for the given userID with TTL. func (t *TokenManager) IssueAccessToken(userID int64, ttl time.Duration) (string, error) { now := time.Now() claims := jwt.RegisteredClaims{ Subject: strconv.FormatInt(userID, 10), IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(ttl)), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(t.secret) } // ParseAccessToken parses and validates the JWT access token, returning the userID. func (t *TokenManager) ParseAccessToken(tokenStr string) (int64, error) { token, err := jwt.ParseWithClaims(tokenStr, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) { if token.Method != jwt.SigningMethodHS256 { return nil, ErrInvalidToken } return t.secret, nil }) if err != nil { return 0, ErrInvalidToken } claims, ok := token.Claims.(*jwt.RegisteredClaims) if !ok || !token.Valid { return 0, ErrInvalidToken } userID, err := strconv.ParseInt(claims.Subject, 10, 64) if err != nil { return 0, ErrInvalidToken } return userID, nil }