feat: matrix/graph data views, wire-generated anomalies, http cookie fix

- auth: session cookie Secure only over https — plain-http LAN access was
  silently dropping the cookie, killing WS auth ('connection unstable')
- wire ghost: server-side spike detection on jitter baseline (3σ + 2.5×mean,
  20KB/s floor, 20s throttle) — wire anomalies now flood every session with
  zero hardware, pushed to clients as {type:'anomaly'} frames
- telemetry cadence 3-5s for live graphs; ambient whispers unchanged
- frontend: MATRIX view (data-rain interleaved with live utterances/anomaly/
  telemetry strings), GRAPHS view (scrolling jitter/variance/dns lines +
  anomaly markers + counters), BOARD/MATRIX/GRAPHS switcher
- connection banner: 'connecting' is now neutral 'tuning the veil…', only
  unstable/closed warns
- db: recreated quantumancy(+_test) as UTF8 (was SQL_ASCII — crashed on
  non-ASCII spirit text); README quickstart updated
- i18n: seance.views.* EN/ES
This commit is contained in:
Indiana
2026-07-21 00:02:41 +00:00
parent 6edbbbbc2a
commit c372427ced
16 changed files with 1086 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
from datetime import datetime, timezone
from fastapi import APIRouter, Cookie, Depends, HTTPException, Response, status
from fastapi import APIRouter, Cookie, Depends, HTTPException, Request, Response, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -34,7 +34,12 @@ async def register(payload: RegisterRequest, db: AsyncSession = Depends(get_db))
@router.post("/login", response_model=UserOut)
async def login(payload: LoginRequest, response: Response, db: AsyncSession = Depends(get_db)):
async def login(
payload: LoginRequest,
request: Request,
response: Response,
db: AsyncSession = Depends(get_db),
):
user = await db.scalar(select(User).where(User.username == payload.username))
if user is None:
verify_password(payload.password, _DUMMY_PASSWORD_HASH)
@@ -51,12 +56,15 @@ async def login(payload: LoginRequest, response: Response, db: AsyncSession = De
db.add(session)
await db.commit()
# The app is reached two ways: https via the Cloudflare Tunnel (Secure
# required) and plain http on the LAN (a Secure cookie would be dropped
# by the browser entirely, silently breaking the séance socket).
response.set_cookie(
SESSION_COOKIE_NAME,
raw_token,
httponly=True,
samesite="lax",
secure=True,
secure=request.url.scheme == "https",
max_age=int(SESSION_TTL.total_seconds()),
)
return user
@@ -64,6 +72,7 @@ async def login(payload: LoginRequest, response: Response, db: AsyncSession = De
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
async def logout(
request: Request,
response: Response,
qm_session: str | None = Cookie(default=None, alias=SESSION_COOKIE_NAME),
db: AsyncSession = Depends(get_db),
@@ -74,7 +83,12 @@ async def logout(
if session is not None:
await db.delete(session)
await db.commit()
response.delete_cookie(SESSION_COOKIE_NAME, httponly=True, samesite="lax", secure=True)
response.delete_cookie(
SESSION_COOKIE_NAME,
httponly=True,
samesite="lax",
secure=request.url.scheme == "https",
)
@router.get("/me", response_model=UserOut)