package utils import ( "errors" "time" "github.com/golang-jwt/jwt/v5" ) var ( ErrInvalidToken = errors.New("توکن نامعتبر است") ErrExpiredToken = errors.New("توکن منقضی شده است") ) // TokenType نوع توکن، برای جلوگیری از استفاده از refresh token به‌جای access و برعکس type TokenType string const ( AccessToken TokenType = "access" RefreshToken TokenType = "refresh" ) // Claims دیتای داخل JWT type Claims struct { UserID uint `json:"user_id"` IsAdmin bool `json:"is_admin"` Type TokenType `json:"type"` jwt.RegisteredClaims } // JWTManager تولید و اعتبارسنجی توکن‌ها type JWTManager struct { accessSecret []byte refreshSecret []byte accessTTL time.Duration refreshTTL time.Duration } func NewJWTManager(accessSecret, refreshSecret string, accessTTL, refreshTTL time.Duration) *JWTManager { return &JWTManager{ accessSecret: []byte(accessSecret), refreshSecret: []byte(refreshSecret), accessTTL: accessTTL, refreshTTL: refreshTTL, } } func (m *JWTManager) GenerateAccessToken(userID uint, isAdmin bool) (string, error) { return m.generate(userID, isAdmin, AccessToken, m.accessSecret, m.accessTTL) } func (m *JWTManager) GenerateRefreshToken(userID uint, isAdmin bool) (string, error) { return m.generate(userID, isAdmin, RefreshToken, m.refreshSecret, m.refreshTTL) } func (m *JWTManager) generate(userID uint, isAdmin bool, tokenType TokenType, secret []byte, ttl time.Duration) (string, error) { now := time.Now() claims := Claims{ UserID: userID, IsAdmin: isAdmin, Type: tokenType, RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(ttl)), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(secret) } func (m *JWTManager) ParseAccessToken(tokenStr string) (*Claims, error) { return m.parse(tokenStr, m.accessSecret, AccessToken) } func (m *JWTManager) ParseRefreshToken(tokenStr string) (*Claims, error) { return m.parse(tokenStr, m.refreshSecret, RefreshToken) } func (m *JWTManager) parse(tokenStr string, secret []byte, expectedType TokenType) (*Claims, error) { claims := &Claims{} token, err := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, ErrInvalidToken } return secret, nil }) if err != nil { if errors.Is(err, jwt.ErrTokenExpired) { return nil, ErrExpiredToken } return nil, ErrInvalidToken } if !token.Valid { return nil, ErrInvalidToken } if claims.Type != expectedType { return nil, ErrInvalidToken } return claims, nil }