package auth import ( "errors" "time" "github.com/golang-jwt/jwt/v5" ) const ( tokenTypeAccess = "access" tokenTypeRefresh = "refresh" ) var ErrInvalidToken = errors.New("invalid token") type JWTManager struct { secret []byte accessTTL time.Duration refreshTTL time.Duration signingMethod jwt.SigningMethod } type Claims struct { UserID uint64 `json:"uid"` Phone string `json:"phone"` TokenType string `json:"typ"` jwt.RegisteredClaims } type TokenPair struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` TokenType string `json:"token_type"` ExpiresInSeconds int64 `json:"expires_in"` } func NewJWTManager(secret string) *JWTManager { return &JWTManager{ secret: []byte(secret), accessTTL: 2 * time.Hour, refreshTTL: 14 * 24 * time.Hour, signingMethod: jwt.SigningMethodHS256, } } func (m *JWTManager) GeneratePair(userID uint64, phone string) (TokenPair, error) { accessToken, err := m.generate(userID, phone, tokenTypeAccess, m.accessTTL) if err != nil { return TokenPair{}, err } refreshToken, err := m.generate(userID, phone, tokenTypeRefresh, m.refreshTTL) if err != nil { return TokenPair{}, err } return TokenPair{ AccessToken: accessToken, RefreshToken: refreshToken, TokenType: "Bearer", ExpiresInSeconds: int64(m.accessTTL.Seconds()), }, nil } func (m *JWTManager) Parse(tokenText, expectedType string) (*Claims, error) { claims := &Claims{} token, err := jwt.ParseWithClaims(tokenText, claims, func(token *jwt.Token) (any, error) { if token.Method != m.signingMethod { return nil, ErrInvalidToken } return m.secret, nil }) if err != nil || !token.Valid { return nil, ErrInvalidToken } if claims.TokenType != expectedType { return nil, ErrInvalidToken } return claims, nil } func (m *JWTManager) generate(userID uint64, phone string, tokenType string, ttl time.Duration) (string, error) { now := time.Now() claims := Claims{ UserID: userID, Phone: phone, TokenType: tokenType, RegisteredClaims: jwt.RegisteredClaims{ Subject: phone, IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(ttl)), }, } token := jwt.NewWithClaims(m.signingMethod, claims) return token.SignedString(m.secret) }