Initial commit
This commit is contained in:
0
fingerp/__init__.py
Normal file
0
fingerp/__init__.py
Normal file
12
fingerp/db.py
Normal file
12
fingerp/db.py
Normal 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
75
fingerp/main.py
Normal 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
13
fingerp/models.py
Normal 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
61
fingerp/static/index.html
Normal 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
6
fingerp/utils.py
Normal 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()
|
||||
Reference in New Issue
Block a user