Add admin password reset functionality for users and customers
This commit is contained in:
@@ -9,12 +9,14 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/redis"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
redisv9 "github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// TokenType represents the type of token
|
||||
@@ -84,6 +86,9 @@ type AuthorizationService interface {
|
||||
|
||||
// GetBlacklistStats returns statistics about the blacklist
|
||||
GetBlacklistStats(ctx context.Context) (map[string]interface{}, error)
|
||||
|
||||
// InvalidateUserSessions marks all tokens issued before now as invalid for the user.
|
||||
InvalidateUserSessions(userID string) error
|
||||
}
|
||||
|
||||
// AuthorizationConfig holds the configuration for the authorization service
|
||||
@@ -308,6 +313,20 @@ func (s *Service) ValidateToken(tokenString string) (*TokenValidationResult, err
|
||||
}, nil
|
||||
}
|
||||
|
||||
if invalidated, err := s.isSessionInvalidated(claims.UserID, claims.IssuedAt); err != nil {
|
||||
s.logger.Error("Failed to check session invalidation", map[string]interface{}{
|
||||
"user_id": claims.UserID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
} else if invalidated {
|
||||
return &TokenValidationResult{
|
||||
Valid: false,
|
||||
Claims: nil,
|
||||
Error: "session invalidated",
|
||||
Expired: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &TokenValidationResult{
|
||||
Valid: true,
|
||||
Claims: claims,
|
||||
@@ -408,6 +427,58 @@ func (s *Service) RefreshAccessToken(refreshToken string) (string, error) {
|
||||
return newAccessToken, nil
|
||||
}
|
||||
|
||||
func sessionInvalidationKey(userID string) string {
|
||||
return "sessions:invalidated_at:" + userID
|
||||
}
|
||||
|
||||
// InvalidateUserSessions marks tokens issued before now as invalid for the given user.
|
||||
func (s *Service) InvalidateUserSessions(userID string) error {
|
||||
if s.redis == nil {
|
||||
s.logger.Warn("Redis client not available, session invalidation skipped", map[string]interface{}{
|
||||
"user_id": userID,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ttl := s.config.RefreshTokenExpiry + 5*time.Minute
|
||||
if err := s.redis.Set(ctx, sessionInvalidationKey(userID), strconv.FormatInt(now, 10), ttl); err != nil {
|
||||
return fmt.Errorf("failed to invalidate user sessions: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("User sessions invalidated", map[string]interface{}{
|
||||
"user_id": userID,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) isSessionInvalidated(userID string, issuedAt *jwt.NumericDate) (bool, error) {
|
||||
if s.redis == nil || issuedAt == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
value, err := s.redis.Get(ctx, sessionInvalidationKey(userID))
|
||||
if err != nil {
|
||||
if err == redisv9.Nil {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
invalidatedAt, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return issuedAt.Time.Unix() < invalidatedAt, nil
|
||||
}
|
||||
|
||||
// generateTokenHash generates a SHA-256 hash of the token for storage
|
||||
func (s *Service) generateTokenHash(tokenString string) string {
|
||||
hash := sha256.Sum256([]byte(tokenString))
|
||||
|
||||
@@ -25,6 +25,16 @@ func (m *MockRedisClient) Get(ctx context.Context, key string) (string, error) {
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockRedisClient) Incr(ctx context.Context, key string) (int64, error) {
|
||||
args := m.Called(ctx, key)
|
||||
return args.Get(0).(int64), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockRedisClient) Expire(ctx context.Context, key string, expiration time.Duration) error {
|
||||
args := m.Called(ctx, key, expiration)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockRedisClient) Del(ctx context.Context, keys ...string) error {
|
||||
args := m.Called(ctx, keys)
|
||||
return args.Error(0)
|
||||
|
||||
Reference in New Issue
Block a user