Add FCM device registration and Gitea bug reporter

This commit is contained in:
2026-09-15 01:01:08 +02:00
parent b2af866cf0
commit b96b1890d2
33 changed files with 1232 additions and 18 deletions
+29 -1
View File
@@ -30,6 +30,11 @@ from fastapi.encoders import jsonable_encoder
from fastapi.staticfiles import StaticFiles
from jinja2 import Environment, FileSystemLoader, select_autoescape
from fastapi.exceptions import RequestValidationError
from fastapi.exception_handlers import request_validation_exception_handler
from feature_schema import FEATURE_SCHEMA
import push_devices
import bug_reporter
from i18n import (
LANGUAGE_COOKIE, current_language, current_page, gettext as _,
language_url, safe_return_path, format_time, format_datetime,
@@ -484,7 +489,7 @@ def ensure_schema():
with get_db_connection() as connection:
with connection.cursor() as cursor:
for statement in statements:
for statement in [*statements, *FEATURE_SCHEMA]:
cursor.execute(statement)
if INITIAL_ADMIN_USERNAME and INITIAL_ADMIN_PASSWORD:
@@ -528,6 +533,13 @@ async def lifespan(_app: FastAPI):
app = FastAPI(title="MetalCircle", lifespan=lifespan)
@app.exception_handler(RequestValidationError)
async def safe_validation_error(request: Request, exc: RequestValidationError):
if request.url.path.startswith('/api/push/'):
return JSONResponse({'error': 'invalid_device'}, status_code=422)
return await request_validation_exception_handler(request, exc)
@app.middleware("http")
async def security_controls(request: Request, call_next):
if COOKIE_SECURE and request.url.path not in {"/impressum", "/datenschutz"}:
@@ -554,6 +566,8 @@ async def security_controls(request: Request, call_next):
bucket_name, limit, window = "account", 10, 60 * 60
elif path.startswith("/messages/"):
bucket_name, limit, window = "messages", 30, 60
elif path == '/bug-report':
bucket_name, limit, window = 'bug_reports', 10, 60 * 60
elif any(part in path for part in ("/photos", "/patches")) or path in {"/profile", "/concerts"}:
bucket_name, limit, window = "uploads", 20, 60 * 60
else:
@@ -588,6 +602,7 @@ async def require_login(request: Request, call_next):
or request.url.path.startswith("/register/")
or request.url.path.startswith("/password-reset")
or request.url.path in {"/impressum", "/datenschutz"}
or request.url.path == '/api/push/session'
or request.url.path.startswith("/language/")
or request.url.path == "/profile/export"
or request.url.path.startswith("/static/")
@@ -597,6 +612,8 @@ async def require_login(request: Request, call_next):
if get_current_user(request):
return await call_next(request)
if request.url.path.startswith('/api/push/'):
return JSONResponse({'error': 'authentication_required'}, status_code=401)
return login_redirect("/")
@@ -2475,6 +2492,7 @@ def login_page(request: Request, next: str = "/"):
@app.post("/login")
def login(
request: Request,
username: str = Form(...),
password: str = Form(...),
next: str = Form("/"),
@@ -2508,6 +2526,12 @@ def login(
status_code=401,
)
# Replacing a login revokes the previous session and its push-device bindings.
old_token = request.cookies.get(SESSION_COOKIE)
if old_token:
with get_db_connection() as connection:
connection.execute('DELETE FROM sessions WHERE token_hash=%s', (hash_token(old_token),))
connection.commit()
response = RedirectResponse(next_path, status_code=303)
return attach_session(response, create_session(row[0]))
@@ -5087,3 +5111,7 @@ def search_venues(q: str):
unique_results[key] = venue
return list(unique_results.values())[:10]
push_devices.register_routes(app, get_db_connection, get_current_user, SESSION_COOKIE)
bug_reporter.register_routes(app, templates, get_db_connection, get_current_user)