package gateway import ( "context" "fmt" "time" "go.uber.org/zap" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/service" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/jwt" ) // RefreshTokenService handles token refresh operations type RefreshTokenService interface { RefreshToken(ctx context.Context, input *RefreshTokenInput) (*RefreshTokenResponse, error) } // RefreshTokenInput represents the input for token refresh type RefreshTokenInput struct { RefreshToken string } // RefreshTokenResponse represents the response after successful token refresh type RefreshTokenResponse struct { // User details UserID string `json:"user_id"` UserEmail string `json:"user_email"` UserName string `json:"user_name"` UserRole string `json:"user_role"` // Tenant details TenantID string `json:"tenant_id"` // Session and new tokens SessionID string `json:"session_id"` AccessToken string `json:"access_token"` AccessExpiry time.Time `json:"access_expiry"` RefreshToken string `json:"refresh_token"` RefreshExpiry time.Time `json:"refresh_expiry"` RefreshedAt time.Time `json:"refreshed_at"` } type refreshTokenService struct { sessionService service.SessionService jwtProvider jwt.Provider logger *zap.Logger } // NewRefreshTokenService creates a new refresh token service func NewRefreshTokenService( sessionService service.SessionService, jwtProvider jwt.Provider, logger *zap.Logger, ) RefreshTokenService { return &refreshTokenService{ sessionService: sessionService, jwtProvider: jwtProvider, logger: logger.Named("refresh-token-service"), } } // RefreshToken validates the refresh token and generates new access/refresh tokens // CWE-613: Validates session still exists before issuing new tokens func (s *refreshTokenService) RefreshToken(ctx context.Context, input *RefreshTokenInput) (*RefreshTokenResponse, error) { s.logger.Info("processing token refresh request") // Validate the refresh token and extract session ID sessionID, err := s.jwtProvider.ValidateToken(input.RefreshToken) if err != nil { s.logger.Warn("invalid refresh token", zap.Error(err)) return nil, fmt.Errorf("invalid or expired refresh token") } s.logger.Debug("refresh token validated", zap.String("session_id", sessionID)) // Retrieve the session to ensure it still exists // CWE-613: This prevents using a refresh token after logout/session deletion session, err := s.sessionService.GetSession(ctx, sessionID) if err != nil { s.logger.Warn("session not found or expired", zap.String("session_id", sessionID), zap.Error(err)) return nil, fmt.Errorf("session not found or expired") } s.logger.Info("session retrieved for token refresh", zap.String("session_id", sessionID), zap.String("user_id", session.UserUUID.String()), zap.String("tenant_id", session.TenantID.String())) // Generate new JWT access and refresh tokens // Both tokens are regenerated to maintain rotation best practices accessToken, accessExpiry, refreshToken, refreshExpiry, err := s.jwtProvider.GenerateTokenPair( session.ID, AccessTokenDuration, RefreshTokenDuration, ) if err != nil { s.logger.Error("failed to generate new token pair", zap.Error(err)) return nil, fmt.Errorf("failed to generate new tokens") } s.logger.Info("token refresh completed successfully", zap.String("user_id", session.UserUUID.String()), zap.String("tenant_id", session.TenantID.String()), zap.String("session_id", session.ID)) return &RefreshTokenResponse{ UserID: session.UserUUID.String(), UserEmail: session.UserEmail, UserName: session.UserName, UserRole: session.UserRole, TenantID: session.TenantID.String(), SessionID: session.ID, AccessToken: accessToken, AccessExpiry: accessExpiry, RefreshToken: refreshToken, RefreshExpiry: refreshExpiry, RefreshedAt: time.Now().UTC(), }, nil }