diff --git a/README.md b/README.md index c1669ff..ae0eb8d 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ sudo -u postgres psql -c "CREATE DATABASE quantumancy_test OWNER quantumancy ENC ```bash cp .env.example .env -# edit .env: set a real SESSION_SECRET, confirm DATABASE_URL and OLLAMA_BASE_URL +# edit .env: confirm DATABASE_URL and OLLAMA_BASE_URL ``` **3. Backend:** @@ -256,13 +256,12 @@ Any Ollama tag works — override with `OLLAMA_FAST_MODEL` / `OLLAMA_CHAT_MODEL` All settings live in `backend/app/config.py` and are read from the environment or `.env` (repo root when run via systemd; CWD otherwise). Required: -`DATABASE_URL`, `OLLAMA_BASE_URL`, `SESSION_SECRET`. +`DATABASE_URL`, `OLLAMA_BASE_URL`. | Env var | Default | Purpose | |---|---|---| | `DATABASE_URL` | — (required) | asyncpg connection string, e.g. `postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy` | | `OLLAMA_BASE_URL` | — (required) | Ollama REST endpoint, e.g. `http://10.30.20.107:11434` | -| `SESSION_SECRET` | — (required) | present in `Settings` but not currently read anywhere else in the app — session auth actually runs on random per-login tokens hashed into `auth_sessions` (`backend/app/models/auth_session.py`), not a signing secret. Still required to boot; set it to any random string. | | `PORT` | `7777` | HTTP listen port | | `OLLAMA_FAST_MODEL` | `granite4.1:3b` | fast tier: fragments, wire whispers | | `OLLAMA_CHAT_MODEL` | `minicpm-v4.5:latest` | chat tier: direct contact, entity minting | @@ -328,7 +327,6 @@ concurrent producers never interleave): cd backend && source venv/bin/activate DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy_test \ OLLAMA_BASE_URL=http://10.30.20.107:11434 \ -SESSION_SECRET=test-secret \ python -m pytest -v ``` diff --git a/backend/app/main.py b/backend/app/main.py index be8a761..5f9a429 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -86,4 +86,17 @@ async def serve_spa(full_path: str): status_code=503, detail="Frontend not built. Run `npm run build` in frontend/ and restart.", ) + + # Vite emits root-level static files (favicon.ico, favicon.svg, + # apple-touch-icon.png, og-image.png, …) straight into dist/ rather than + # dist/assets/ — the only mounted static dir. Without this, requests for + # them fall through to the SPA fallback below and get index.html back + # instead of the actual file (browsers silently ignore it; social-media + # link-preview crawlers fetching og:image get an HTML page). + if full_path: + dist_root = FRONTEND_DIST.resolve() + candidate = (dist_root / full_path).resolve() + if candidate.is_file() and dist_root in candidate.parents: + return FileResponse(candidate) + return FileResponse(index_file) diff --git a/backend/tests/test_frontend_serving.py b/backend/tests/test_frontend_serving.py index a2f591d..9df8fb5 100644 --- a/backend/tests/test_frontend_serving.py +++ b/backend/tests/test_frontend_serving.py @@ -24,3 +24,38 @@ async def test_missing_frontend_build_returns_clear_error(client, monkeypatch, t response = await client.get("/some/route") assert response.status_code == 503 assert "npm run build" in response.json()["detail"] + + +@pytest.mark.asyncio +async def test_root_level_static_file_is_served_directly(client, monkeypatch, tmp_path): + # Vite emits favicon.ico, og-image.png, etc. straight into dist/, not + # dist/assets/ (the only mounted static dir) — these must be served as + # themselves, not swallowed by the SPA fallback. + import app.main as main_module + + monkeypatch.setattr(main_module, "FRONTEND_DIST", tmp_path) + (tmp_path / "index.html").write_text('
') + (tmp_path / "favicon.svg").write_text("fake favicon") + + response = await client.get("/favicon.svg") + assert response.status_code == 200 + assert response.text == "fake favicon" + assert "html" not in response.headers["content-type"] + + +@pytest.mark.asyncio +async def test_static_file_lookup_cannot_escape_dist_directory(monkeypatch, tmp_path): + # Bypasses the HTTP client, which normalizes ".." segments out of URLs + # before they're ever sent — this exercises the route function's own + # guard directly against a full_path value an unusual client could send. + import app.main as main_module + + dist_dir = tmp_path / "dist" + dist_dir.mkdir() + (dist_dir / "index.html").write_text('
') + secret = tmp_path / "secret.txt" + secret.write_text("should never be served") + + monkeypatch.setattr(main_module, "FRONTEND_DIST", dist_dir) + response = await main_module.serve_spa("../secret.txt") + assert response.path == dist_dir / "index.html" diff --git a/frontend/index.html b/frontend/index.html index ab2f0e4..e10441b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,6 +4,30 @@ Quantumancy + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..d921b1c Binary files /dev/null and b/frontend/public/apple-touch-icon.png differ diff --git a/frontend/public/favicon-16x16.png b/frontend/public/favicon-16x16.png new file mode 100644 index 0000000..e1db651 Binary files /dev/null and b/frontend/public/favicon-16x16.png differ diff --git a/frontend/public/favicon-32x32.png b/frontend/public/favicon-32x32.png new file mode 100644 index 0000000..7a2b89e Binary files /dev/null and b/frontend/public/favicon-32x32.png differ diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..f79cacb Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..679f028 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/icon-192.png b/frontend/public/icon-192.png new file mode 100644 index 0000000..b2e2104 Binary files /dev/null and b/frontend/public/icon-192.png differ diff --git a/frontend/public/icon-512.png b/frontend/public/icon-512.png new file mode 100644 index 0000000..661e5b5 Binary files /dev/null and b/frontend/public/icon-512.png differ diff --git a/frontend/public/og-image.png b/frontend/public/og-image.png new file mode 100644 index 0000000..de5169c Binary files /dev/null and b/frontend/public/og-image.png differ