Files
business-card-scanner/app/security.py
Lucas Orth ab993e98e1 Visitenkarten-Scanner: Stapelscan, Extraktion, Übersicht, vCard-Export
Selbst gehostete PWA, die Visitenkarten von einem Foto freistellt, ausliest
und als Kontakt bereitstellt.

- Stapelscan: OpenCV findet die Kartenrechtecke über mehrere Binärmasken,
  entzerrt sie perspektivisch und schneidet sie einzeln aus. Ohne Fund gilt
  das ganze Foto als eine Karte.
- Extraktion: ein Aufruf je Zuschnitt an das Vision-Modell mit
  JSON-Schema. Kein vorgeschaltetes OCR - das würde Layout und
  Schriftgrößen wegwerfen, aus denen die Feldzuordnung entsteht.
- Metadaten: Aufnahmezeit und GPS aus den EXIF-Daten des Fotos, Ortsname
  über Nominatim, Browserstandort nur als Rückfallebene.
- Übersicht mit Volltextsuche und Filtern, Detailansicht mit Korrekturmaske.
- Notizfeld je Karte, Erinnerungen per Mail inklusive Nachholen verpasster
  Termine nach einem Neustart.
- vCard 3.0 einzeln und als Sammeldatei, Karte gilt danach als exportiert.
- Anmeldung über ein Passwort, Sitzung als signiertes Cookie.
- Deployment per Tag-Push nach den Konventionen in DEPLOY.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 11:45:56 +02:00

50 lines
1.2 KiB
Python

"""Anmeldung ueber ein einzelnes Passwort, Sitzung als signiertes Cookie."""
import hashlib
import hmac
import time
from fastapi import HTTPException, Request
from . import config
COOKIE = "bcs_session"
def enabled() -> bool:
return bool(config.APP_PASSWORD)
def _sign(payload: str) -> str:
return hmac.new(
config.SECRET_KEY.encode(), payload.encode(), hashlib.sha256
).hexdigest()
def issue_token() -> str:
expires = str(int(time.time()) + config.SESSION_DAYS * 86400)
return f"{expires}.{_sign(expires)}"
def valid_token(token: str | None) -> bool:
if not token or "." not in token:
return False
expires, signature = token.split(".", 1)
if not hmac.compare_digest(signature, _sign(expires)):
return False
try:
return int(expires) > time.time()
except ValueError:
return False
def check_password(candidate: str) -> bool:
return hmac.compare_digest(candidate, config.APP_PASSWORD)
def require_auth(request: Request) -> None:
"""FastAPI-Abhaengigkeit fuer alle geschuetzten Endpunkte."""
if not enabled():
return
if not valid_token(request.cookies.get(COOKIE)):
raise HTTPException(status_code=401, detail="Nicht angemeldet")