Initial commit

This commit is contained in:
2026-03-24 18:28:37 -04:00
commit e2b6081811
11 changed files with 720 additions and 0 deletions

0
fingerp/__init__.py Normal file
View File

12
fingerp/db.py Normal file
View File

@ -0,0 +1,12 @@
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, declarative_base
DATABASE_URL = "sqlite+aiosqlite:///./fingerprints.db"
engine = create_async_engine(DATABASE_URL, echo=False)
AsyncSessionLocal = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
Base = declarative_base()

75
fingerp/main.py Normal file
View File

@ -0,0 +1,75 @@
from fastapi import FastAPI, Request, Depends
from fastapi.responses import HTMLResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from pathlib import Path
from .db import AsyncSessionLocal, engine, Base
from .models import Fingerprint
from .utils import hash_fingerprint
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
async def root():
static_path = Path(__file__).parent / "static" / "index.html"
return static_path.read_text()
# Dependency
async def get_db():
async with AsyncSessionLocal() as session:
yield session
@app.on_event("startup")
async def startup():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
@app.post("/fingerprint")
async def collect_fingerprint(request: Request, db: AsyncSession = Depends(get_db)):
body = await request.json()
fp_hash = hash_fingerprint(body)
ip = request.client.host
user_agent = request.headers.get("user-agent", "")
record = Fingerprint(
fingerprint_hash=fp_hash,
raw_data=body,
ip_address=ip,
user_agent=user_agent,
)
db.add(record)
await db.commit()
return {
"status": "stored",
"fingerprint_hash": fp_hash,
}
@app.get("/fingerprint/{fp_hash}")
async def get_fingerprint(fp_hash: str, db: AsyncSession = Depends(get_db)):
result = await db.execute(
select(Fingerprint).where(Fingerprint.fingerprint_hash == fp_hash)
)
rows = result.scalars().all()
return {
"count": len(rows),
"records": [
{
"ip": r.ip_address,
"created_at": r.created_at,
"user_agent": r.user_agent,
}
for r in rows
],
}

13
fingerp/models.py Normal file
View File

@ -0,0 +1,13 @@
from sqlalchemy import Column, Integer, String, DateTime, JSON
from datetime import datetime
from .db import Base
class Fingerprint(Base):
__tablename__ = "fingerprints"
id = Column(Integer, primary_key=True, index=True)
fingerprint_hash = Column(String, index=True)
raw_data = Column(JSON)
ip_address = Column(String)
user_agent = Column(String)
created_at = Column(DateTime, default=datetime.utcnow)

61
fingerp/static/index.html Normal file
View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fingerprint Collector</title>
</head>
<body>
<h1>Fingerprint Collector</h1>
<p id="status">Collecting fingerprint...</p>
<script>
function getCanvasFingerprint() {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) return "";
ctx.textBaseline = "top";
ctx.font = "16px Arial";
ctx.fillText("fingerprint", 10, 10);
return canvas.toDataURL();
}
async function submitFingerprint() {
const fingerprint = getCanvasFingerprint();
try {
const response = await fetch("/fingerprint", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ fingerprint: fingerprint }),
});
const result = await response.json();
const hash = result.fingerprint_hash;
const checkResponse = await fetch("/fingerprint/" + hash);
const checkResult = await checkResponse.json();
const shortHash = hash.substring(0, 8);
if (checkResult.count > 1) {
document.getElementById("status").textContent =
"Hello, " + shortHash + ". We've seen you before!";
} else {
document.getElementById("status").textContent =
"Hello, " + shortHash + ". I see you're new here";
}
} catch (error) {
document.getElementById("status").textContent =
"Error: " + error.message;
}
}
submitFingerprint();
</script>
</body>
</html>

6
fingerp/utils.py Normal file
View File

@ -0,0 +1,6 @@
import hashlib
import json
def hash_fingerprint(data: dict) -> str:
normalized = json.dumps(data, sort_keys=True)
return hashlib.sha256(normalized.encode()).hexdigest()