ABSA / src /utils /ip_location_service.py
parthnuwal7's picture
Adding Mongo+Redis concept
4b62d23
"""
IP and location logging service with Redis gating.
"""
import os
import logging
import requests
from typing import Optional, Dict, Any
from .redis_service import get_redis_service
from .mongodb_service import get_mongodb_service
logger = logging.getLogger(__name__)
class IPLocationService:
"""
Service for logging IP and location data.
Uses Redis gate to limit IPinfo API calls (once per device per 5 minutes).
"""
def __init__(self):
self.redis_service = get_redis_service()
self.mongodb_service = get_mongodb_service()
self.ipinfo_token = os.getenv("IPINFO_TOKEN")
if not self.ipinfo_token:
logger.warning("IPINFO_TOKEN not set. IP location logging disabled.")
def _fetch_ip_location(self, ip_address: str) -> Optional[Dict[str, Any]]:
"""
Fetch IP location from IPinfo API.
Args:
ip_address: IP address to lookup
Returns:
Location data or None
"""
if not self.ipinfo_token:
return None
try:
url = f"https://ipinfo.io/{ip_address}"
headers = {"Authorization": f"Bearer {self.ipinfo_token}"}
response = requests.get(url, headers=headers, timeout=5)
if response.status_code == 200:
data = response.json()
# Extract relevant fields
location_data = {
"ip": data.get("ip"),
"city": data.get("city"),
"region": data.get("region"),
"country": data.get("country"),
"loc": data.get("loc"), # latitude,longitude
"org": data.get("org"), # ISP/organization
"timezone": data.get("timezone")
}
return location_data
else:
logger.warning(f"IPinfo API returned status {response.status_code}")
return None
except Exception as e:
logger.error(f"Failed to fetch IP location: {str(e)}")
return None
def log_session_metadata(
self,
device_id: str,
ip_address: str,
user_id: Optional[str] = None,
user_agent: Optional[str] = None
) -> bool:
"""
Log session metadata with IP and location (gated by Redis).
Only logs if Redis gate allows (once per device per 5 minutes).
Args:
device_id: Device identifier
ip_address: Client IP address
user_id: Optional user identifier
user_agent: Optional user agent string
Returns:
True if logged, False if skipped or failed
"""
# Check Redis gate (5 minute TTL)
should_log = self.redis_service.should_log_ip(device_id, ttl_seconds=300)
if not should_log:
logger.debug(f"IP logging skipped for device {device_id} (within TTL window)")
return False
# Fetch IP location
location_data = self._fetch_ip_location(ip_address)
# Prepare metadata
metadata = {
"ip": ip_address,
"user_agent": user_agent
}
# Add location data if available
if location_data:
metadata.update(location_data)
# Log SESSION_METADATA event to MongoDB
success = self.mongodb_service.log_event(
event_type="SESSION_METADATA",
device_id=device_id,
user_id=user_id,
metadata=metadata
)
if success:
logger.info(f"Session metadata logged for device {device_id}")
else:
logger.warning(f"Failed to log session metadata for device {device_id}")
return success
# Singleton instance
_ip_location_service = None
def get_ip_location_service() -> IPLocationService:
"""Get IP location service singleton."""
global _ip_location_service
if _ip_location_service is None:
_ip_location_service = IPLocationService()
return _ip_location_service