package auth import ( "errors" "time" "github.com/golang-jwt/jwt/v5" ) var ( ErrInvalidToken = errors.New("invalid token") ErrExpiredToken = errors.New("token expired") ) type Claims struct { UserID int64 `json:"user_id"` Username string `json:"username"` Role string `json:"role"` jwt.RegisteredClaims } type JWTManager struct { secret []byte expiryH int } func NewJWTManager(secret string, expiryH int) *JWTManager { return &JWTManager{ secret: []byte(secret), expiryH: expiryH, } } func (m *JWTManager) Generate(userID int64, username, role string) (string, time.Time, error) { expiresAt := time.Now().Add(time.Duration(m.expiryH) * time.Hour) claims := &Claims{ UserID: userID, Username: username, Role: role, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expiresAt), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) signed, err := token.SignedString(m.secret) return signed, expiresAt, err } func (m *JWTManager) Validate(tokenStr string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, ErrInvalidToken } return m.secret, 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 }