Spaces:
Sleeping
Sleeping
| """Author RAG Chatbot SaaS β Anonymous Visitor Fingerprinting. | |
| Generates a stable, anonymous fingerprint for each visitor session. | |
| Used for unique-visitor counting in analytics β NOT for tracking across sites. | |
| Privacy rules: | |
| - No PII ever stored or logged | |
| - Raw IP is hashed immediately and never persisted | |
| - Fingerprint is a one-way SHA-256 hash β not reversible to IP | |
| - Fingerprint TTL is session-scoped (30 minutes inactivity = new fingerprint) | |
| RULE: This module must NEVER store or return a raw IP address. | |
| """ | |
| import hashlib | |
| import structlog | |
| logger = structlog.get_logger(__name__) | |
| def generate_visitor_fingerprint( | |
| ip_address: str, | |
| user_agent: str, | |
| accept_language: str = "", | |
| accept_encoding: str = "", | |
| ) -> str: | |
| """Generate a stable, anonymous visitor fingerprint. | |
| Combines multiple non-PII signals into a 32-char hex fingerprint. | |
| The raw IP is hashed immediately β the fingerprint itself is not | |
| reversible to any identifying information. | |
| Args: | |
| ip_address: Client IP address (used only in hash, never stored). | |
| user_agent: Browser User-Agent string. | |
| accept_language: Accept-Language header value. | |
| accept_encoding: Accept-Encoding header value. | |
| Returns: | |
| 32-character hex fingerprint string. | |
| """ | |
| components = [ | |
| ip_address or "unknown", | |
| user_agent or "", | |
| accept_language or "", | |
| accept_encoding or "", | |
| ] | |
| combined = "|".join(components) | |
| fingerprint = hashlib.sha256(combined.encode("utf-8")).hexdigest()[:32] | |
| logger.debug("Visitor fingerprint generated", fingerprint_prefix=fingerprint[:8]) | |
| return fingerprint | |
| def fingerprint_from_request(request) -> str: | |
| """Convenience wrapper: extract fingerprint signals from a FastAPI Request. | |
| Args: | |
| request: FastAPI / Starlette Request object. | |
| Returns: | |
| 32-character hex fingerprint string. | |
| """ | |
| ip = request.client.host if request.client else "unknown" | |
| forwarded_for = request.headers.get("X-Forwarded-For", "") | |
| if forwarded_for: | |
| # Use the leftmost IP (original client) when behind a proxy | |
| ip = forwarded_for.split(",")[0].strip() | |
| return generate_visitor_fingerprint( | |
| ip_address=ip, | |
| user_agent=request.headers.get("User-Agent", ""), | |
| accept_language=request.headers.get("Accept-Language", ""), | |
| accept_encoding=request.headers.get("Accept-Encoding", ""), | |
| ) | |