Arag / app /services /session_core /fingerprint.py
AuthorBot
Restructure project for HF Spaces deployment
772f852
Raw
History Blame Contribute Delete
2.47 kB
"""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", ""),
)