Spaces:
Running
Running
Fix encryption: support direct RSA-OAEP and hybrid RSA+AES-GCM modes
Browse files- app.py +45 -1
- models.py +2 -0
- requirements.txt +1 -0
app.py
CHANGED
|
@@ -6,8 +6,9 @@ decrypting it, and storing in SQLite database.
|
|
| 6 |
"""
|
| 7 |
import logging
|
| 8 |
from contextlib import asynccontextmanager
|
| 9 |
-
from typing import Optional
|
| 10 |
|
|
|
|
| 11 |
from fastapi import FastAPI, Query, Request, Depends, HTTPException, status
|
| 12 |
from fastapi.middleware.cors import CORSMiddleware
|
| 13 |
from fastapi.responses import JSONResponse, HTMLResponse
|
|
@@ -29,6 +30,40 @@ logger = logging.getLogger(__name__)
|
|
| 29 |
# User ID length constant
|
| 30 |
USER_ID_LENGTH = 20
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
@asynccontextmanager
|
| 34 |
async def lifespan(app: FastAPI):
|
|
@@ -123,6 +158,8 @@ async def get_data(
|
|
| 123 |
"user_id": item.user_id,
|
| 124 |
"refer_url": item.refer_url,
|
| 125 |
"ip_address": item.ip_address,
|
|
|
|
|
|
|
| 126 |
"json_data": item.json_data,
|
| 127 |
"created_at": item.created_at.isoformat() if item.created_at else None
|
| 128 |
}
|
|
@@ -202,6 +239,9 @@ async def blink(
|
|
| 202 |
# Fall back to direct client IP
|
| 203 |
ip_address = request.client.host if request.client else None
|
| 204 |
|
|
|
|
|
|
|
|
|
|
| 205 |
# Store each decrypted result as separate records
|
| 206 |
records_created = 0
|
| 207 |
for json_data in decrypted_results:
|
|
@@ -209,6 +249,8 @@ async def blink(
|
|
| 209 |
user_id=user_id,
|
| 210 |
refer_url=refer_url,
|
| 211 |
ip_address=ip_address,
|
|
|
|
|
|
|
| 212 |
json_data=json_data
|
| 213 |
)
|
| 214 |
db.add(blink_record)
|
|
@@ -220,6 +262,8 @@ async def blink(
|
|
| 220 |
user_id=user_id,
|
| 221 |
refer_url=refer_url,
|
| 222 |
ip_address=ip_address,
|
|
|
|
|
|
|
| 223 |
json_data={"encrypted_length": len(encrypted_data)}
|
| 224 |
)
|
| 225 |
db.add(blink_record)
|
|
|
|
| 6 |
"""
|
| 7 |
import logging
|
| 8 |
from contextlib import asynccontextmanager
|
| 9 |
+
from typing import Optional, Tuple
|
| 10 |
|
| 11 |
+
import httpx
|
| 12 |
from fastapi import FastAPI, Query, Request, Depends, HTTPException, status
|
| 13 |
from fastapi.middleware.cors import CORSMiddleware
|
| 14 |
from fastapi.responses import JSONResponse, HTMLResponse
|
|
|
|
| 30 |
# User ID length constant
|
| 31 |
USER_ID_LENGTH = 20
|
| 32 |
|
| 33 |
+
# Geolocation API settings
|
| 34 |
+
GEOLOCATION_API_URL = "http://ip-api.com/json/{ip}?fields=status,country,regionName"
|
| 35 |
+
GEOLOCATION_TIMEOUT = 2.0 # seconds
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
async def get_geolocation(ip_address: str) -> Tuple[Optional[str], Optional[str]]:
|
| 39 |
+
"""
|
| 40 |
+
Get country and region for an IP address using ip-api.com.
|
| 41 |
+
|
| 42 |
+
Args:
|
| 43 |
+
ip_address: IPv4 or IPv6 address
|
| 44 |
+
|
| 45 |
+
Returns:
|
| 46 |
+
Tuple of (country, region) or (None, None) if lookup fails
|
| 47 |
+
"""
|
| 48 |
+
if not ip_address:
|
| 49 |
+
return None, None
|
| 50 |
+
|
| 51 |
+
# Skip geolocation for localhost/private IPs
|
| 52 |
+
if ip_address in ("127.0.0.1", "::1", "localhost") or ip_address.startswith(("192.168.", "10.", "172.")):
|
| 53 |
+
return None, None
|
| 54 |
+
|
| 55 |
+
try:
|
| 56 |
+
async with httpx.AsyncClient(timeout=GEOLOCATION_TIMEOUT) as client:
|
| 57 |
+
response = await client.get(GEOLOCATION_API_URL.format(ip=ip_address))
|
| 58 |
+
if response.status_code == 200:
|
| 59 |
+
data = response.json()
|
| 60 |
+
if data.get("status") == "success":
|
| 61 |
+
return data.get("country"), data.get("regionName")
|
| 62 |
+
except Exception as e:
|
| 63 |
+
logger.warning(f"Geolocation lookup failed for {ip_address}: {e}")
|
| 64 |
+
|
| 65 |
+
return None, None
|
| 66 |
+
|
| 67 |
|
| 68 |
@asynccontextmanager
|
| 69 |
async def lifespan(app: FastAPI):
|
|
|
|
| 158 |
"user_id": item.user_id,
|
| 159 |
"refer_url": item.refer_url,
|
| 160 |
"ip_address": item.ip_address,
|
| 161 |
+
"country": item.country,
|
| 162 |
+
"region": item.region,
|
| 163 |
"json_data": item.json_data,
|
| 164 |
"created_at": item.created_at.isoformat() if item.created_at else None
|
| 165 |
}
|
|
|
|
| 239 |
# Fall back to direct client IP
|
| 240 |
ip_address = request.client.host if request.client else None
|
| 241 |
|
| 242 |
+
# Get geolocation from IP address
|
| 243 |
+
country, region = await get_geolocation(ip_address)
|
| 244 |
+
|
| 245 |
# Store each decrypted result as separate records
|
| 246 |
records_created = 0
|
| 247 |
for json_data in decrypted_results:
|
|
|
|
| 249 |
user_id=user_id,
|
| 250 |
refer_url=refer_url,
|
| 251 |
ip_address=ip_address,
|
| 252 |
+
country=country,
|
| 253 |
+
region=region,
|
| 254 |
json_data=json_data
|
| 255 |
)
|
| 256 |
db.add(blink_record)
|
|
|
|
| 262 |
user_id=user_id,
|
| 263 |
refer_url=refer_url,
|
| 264 |
ip_address=ip_address,
|
| 265 |
+
country=country,
|
| 266 |
+
region=region,
|
| 267 |
json_data={"encrypted_length": len(encrypted_data)}
|
| 268 |
)
|
| 269 |
db.add(blink_record)
|
models.py
CHANGED
|
@@ -23,6 +23,8 @@ class BlinkData(Base):
|
|
| 23 |
user_id = Column(String(20), index=True, nullable=False)
|
| 24 |
refer_url = Column(Text, nullable=True)
|
| 25 |
ip_address = Column(String(45), nullable=True) # IPv6 can be up to 45 chars
|
|
|
|
|
|
|
| 26 |
json_data = Column(JSON, nullable=True)
|
| 27 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 28 |
|
|
|
|
| 23 |
user_id = Column(String(20), index=True, nullable=False)
|
| 24 |
refer_url = Column(Text, nullable=True)
|
| 25 |
ip_address = Column(String(45), nullable=True) # IPv6 can be up to 45 chars
|
| 26 |
+
country = Column(String(100), nullable=True) # Country from IP geolocation
|
| 27 |
+
region = Column(String(100), nullable=True) # Region/State from IP geolocation
|
| 28 |
json_data = Column(JSON, nullable=True)
|
| 29 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 30 |
|
requirements.txt
CHANGED
|
@@ -5,3 +5,4 @@ sqlalchemy>=2.0.0
|
|
| 5 |
aiosqlite>=0.19.0
|
| 6 |
cryptography>=41.0.0
|
| 7 |
pydantic>=2.0.0
|
|
|
|
|
|
| 5 |
aiosqlite>=0.19.0
|
| 6 |
cryptography>=41.0.0
|
| 7 |
pydantic>=2.0.0
|
| 8 |
+
httpx>=0.25.0
|