mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-06 16:13:48 +08:00
Sluglas, the Stripper Slug
This commit is contained in:
@@ -24,7 +24,7 @@ function rememberEvent(eventId) {
|
||||
|
||||
async function pollSentryEvent() {
|
||||
try {
|
||||
const response = await fetch("/api/sentry/status", { cache: "no-store" })
|
||||
const response = await fetch(galaxyPath("/api/sentry/status"), { cache: "no-store" })
|
||||
if (!response.ok) return
|
||||
const payload = await response.json()
|
||||
const event = payload?.lastEvent
|
||||
@@ -81,6 +81,13 @@ async function readJsonResponse(response) {
|
||||
try {
|
||||
return JSON.parse(body)
|
||||
} catch {
|
||||
const contentType = response.headers?.get("content-type") || ""
|
||||
if (contentType.includes("text/html") || body.trim().startsWith("<")) {
|
||||
if (response.status === 200) {
|
||||
throw new Error("Galaxy returned an HTML page instead of JSON data. Check your session or connection.")
|
||||
}
|
||||
throw new Error(`Galaxy returned an HTML error page (${response.status}). Check the device connection or Galaxy tunnel.`)
|
||||
}
|
||||
throw new Error(`Galaxy returned an unexpected ${response.status} response. Check the device connection or Galaxy tunnel.`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,14 +108,14 @@ async function sendTestEvent() {
|
||||
state.testBusy = true
|
||||
try {
|
||||
const response = await fetch(galaxyPath("/api/sentry/test"), { method: "POST" })
|
||||
const payload = await response.json()
|
||||
const payload = await readJsonResponse(response)
|
||||
if (!response.ok) {
|
||||
showSnackbar(payload.error || "Sentry test failed.")
|
||||
return
|
||||
}
|
||||
showSnackbar("Test capture started. The images will appear here shortly.")
|
||||
} catch (error) {
|
||||
showSnackbar("Network error — is the device reachable?")
|
||||
showSnackbar(error.message || "Network error — is the device reachable?")
|
||||
} finally {
|
||||
state.testBusy = false
|
||||
}
|
||||
@@ -128,6 +128,13 @@ async function readJsonResponse(response) {
|
||||
try {
|
||||
return JSON.parse(body)
|
||||
} catch {
|
||||
const contentType = response.headers?.get("content-type") || ""
|
||||
if (contentType.includes("text/html") || body.trim().startsWith("<")) {
|
||||
if (response.status === 200) {
|
||||
throw new Error("Galaxy returned an HTML page instead of JSON data. Check your session or connection.")
|
||||
}
|
||||
throw new Error(`Galaxy returned an HTML error page (${response.status}). Check the device connection or Galaxy tunnel.`)
|
||||
}
|
||||
throw new Error(`Galaxy returned an unexpected ${response.status} response. Check the device connection or Galaxy tunnel.`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ export function isGalaxyTunnel() {
|
||||
export function galaxyPath(path) {
|
||||
const suffix = path.startsWith("/") ? path : `/${path}`
|
||||
if (!isGalaxyTunnel()) return suffix
|
||||
if (suffix === "/api" || suffix.startsWith("/api/")) return suffix
|
||||
|
||||
const firstPathSegment = window.location.pathname.split("/").filter(Boolean)[0] || ""
|
||||
const slug = /^[A-Za-z0-9]{16}$/.test(firstPathSegment) ? `/${firstPathSegment}` : ""
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from test_dashboard_stats import FakeParams, MODULE_DIR, _install_server_import_stubs
|
||||
|
||||
|
||||
def _load_server_module():
|
||||
import importlib.util
|
||||
import sys
|
||||
|
||||
_install_server_import_stubs()
|
||||
spec = importlib.util.spec_from_file_location("sentry_routing_server", MODULE_DIR / "the_galaxy.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules["sentry_routing_server"] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
the_galaxy = _load_server_module()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(monkeypatch, tmp_path):
|
||||
assert the_galaxy._import_galaxy_web_symbols()
|
||||
monkeypatch.setattr(the_galaxy, "params", FakeParams())
|
||||
monkeypatch.setattr(the_galaxy, "_get_galaxy_dir", lambda: tmp_path)
|
||||
|
||||
app = the_galaxy.Flask(
|
||||
f"test_galaxy_{time.monotonic_ns()}",
|
||||
template_folder=str(MODULE_DIR / "templates"),
|
||||
static_folder=str(MODULE_DIR / "assets"),
|
||||
)
|
||||
the_galaxy.setup(app)
|
||||
return app.test_client()
|
||||
|
||||
|
||||
def test_slug_middleware_strips_16_char_slug(client):
|
||||
# Slug-prefixed API call to sentry push config
|
||||
response = client.get("/df70390ca648d7c3/api/sentry/push/config")
|
||||
assert response.status_code == 200
|
||||
assert "application/json" in response.headers.get("Content-Type", "")
|
||||
data = response.get_json()
|
||||
assert data["enabled"] is True
|
||||
assert len(data["publicKey"]) > 20
|
||||
|
||||
# Direct unslugged API call
|
||||
response_direct = client.get("/api/sentry/push/config")
|
||||
assert response_direct.status_code == 200
|
||||
assert response_direct.get_json()["publicKey"] == data["publicKey"]
|
||||
|
||||
|
||||
def test_slug_middleware_service_worker_and_headers(client):
|
||||
with client.get("/df70390ca648d7c3/service-worker.js") as response:
|
||||
assert response.status_code == 200
|
||||
assert response.headers.get("Service-Worker-Allowed") == "/"
|
||||
assert "no-store" in response.headers.get("Cache-Control", "")
|
||||
|
||||
with client.get("/service-worker.js") as response_direct:
|
||||
assert response_direct.status_code == 200
|
||||
assert response_direct.headers.get("Service-Worker-Allowed") == "/"
|
||||
|
||||
|
||||
def test_404_api_returns_json_not_html(client):
|
||||
# Non-existent API route without slug
|
||||
res1 = client.get("/api/nonexistent")
|
||||
assert res1.status_code == 404
|
||||
assert "application/json" in res1.headers.get("Content-Type", "")
|
||||
assert res1.get_json() == {"error": "Not found"}
|
||||
|
||||
# Non-existent API route with slug
|
||||
res2 = client.get("/df70390ca648d7c3/api/nonexistent")
|
||||
assert res2.status_code == 404
|
||||
assert "application/json" in res2.headers.get("Content-Type", "")
|
||||
assert res2.get_json() == {"error": "Not found"}
|
||||
|
||||
# POST to non-existent route returns 404 JSON
|
||||
res3 = client.post("/random_post_route")
|
||||
assert res3.status_code == 404
|
||||
assert "application/json" in res3.headers.get("Content-Type", "")
|
||||
|
||||
|
||||
def test_404_assets_returns_not_found_text(client):
|
||||
res = client.get("/assets/nonexistent_image.png")
|
||||
assert res.status_code == 404
|
||||
assert res.get_data(as_text=True) == "Not found"
|
||||
|
||||
|
||||
def test_404_spa_client_routes_return_html(client):
|
||||
# SPA route without slug returns index.html
|
||||
res1 = client.get("/sentry")
|
||||
assert res1.status_code == 200
|
||||
assert "text/html" in res1.headers.get("Content-Type", "")
|
||||
|
||||
# SPA route with slug returns index.html
|
||||
res2 = client.get("/df70390ca648d7c3/sentry")
|
||||
assert res2.status_code == 200
|
||||
assert "text/html" in res2.headers.get("Content-Type", "")
|
||||
|
||||
|
||||
def test_sentry_push_subscribe_lifecycle(client):
|
||||
subscription_payload = {
|
||||
"endpoint": "https://fcm.googleapis.com/fcm/send/test-endpoint-id",
|
||||
"expirationTime": None,
|
||||
"keys": {
|
||||
"p256dh": "BEl62iUYgUivxIkv69yViEuiBIa-Ib9-Skv60QVu3vW5PFGhmqazETUFAmeLbvDWP00n-5wViBRio5B-dQ31-10",
|
||||
"auth": "5KkU95j6j8gBsmVdYqC8pA",
|
||||
},
|
||||
}
|
||||
|
||||
res = client.post(
|
||||
"/api/sentry/push/subscribe",
|
||||
data=json.dumps(subscription_payload),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert res.status_code == 200
|
||||
assert res.get_json()["subscribed"] is True
|
||||
assert res.get_json()["subscriptionCount"] == 1
|
||||
|
||||
# Check config shows count 1
|
||||
res_cfg = client.get("/api/sentry/push/config")
|
||||
assert res_cfg.get_json()["subscriptionCount"] == 1
|
||||
|
||||
|
||||
def test_sentry_vapid_corrupt_file_self_healing(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(the_galaxy, "_get_galaxy_dir", lambda: tmp_path)
|
||||
key_path, _ = the_galaxy._sentry_push_paths()
|
||||
key_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Write 0-byte corrupted file
|
||||
key_path.write_bytes(b"")
|
||||
assert key_path.stat().st_size == 0
|
||||
|
||||
# Should self-heal and generate valid key
|
||||
vapid = the_galaxy._get_sentry_vapid()
|
||||
assert vapid is not None
|
||||
assert key_path.stat().st_size > 0
|
||||
pub_key = the_galaxy._sentry_vapid_public_key(vapid)
|
||||
assert len(pub_key) > 20
|
||||
@@ -6,6 +6,7 @@ import importlib
|
||||
import math
|
||||
import numbers
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import sysconfig
|
||||
import tarfile
|
||||
@@ -186,7 +187,9 @@ def _galaxy_runtime_dependency_paths() -> tuple[str, ...]:
|
||||
"/usr/local/venv/lib/python3.12/site-packages",
|
||||
]
|
||||
|
||||
for venv_name in (".venv", ".venv-linux-arm64"):
|
||||
is_arm = platform.machine().lower() in ("aarch64", "arm64")
|
||||
venv_names = (".venv-linux-arm64", ".venv") if is_arm else (".venv",)
|
||||
for venv_name in venv_names:
|
||||
venv_path = repo_root / venv_name / "lib"
|
||||
if venv_path.is_dir():
|
||||
candidates.extend(str(path) for path in venv_path.glob("python*/site-packages"))
|
||||
@@ -196,10 +199,14 @@ def _galaxy_runtime_dependency_paths() -> tuple[str, ...]:
|
||||
|
||||
REPO_THIRD_PARTY_PATH = Path(__file__).resolve().parents[2] / "third_party"
|
||||
GALAXY_RUNTIME_DEPENDENCY_PATHS = _galaxy_runtime_dependency_paths()
|
||||
for deps_path in GALAXY_DEPS_PATHS + GALAXY_RUNTIME_DEPENDENCY_PATHS:
|
||||
for deps_path in GALAXY_DEPS_PATHS:
|
||||
if os.path.isdir(deps_path) and deps_path not in sys.path:
|
||||
sys.path.insert(0, deps_path)
|
||||
|
||||
for deps_path in GALAXY_RUNTIME_DEPENDENCY_PATHS:
|
||||
if os.path.isdir(deps_path) and deps_path not in sys.path:
|
||||
sys.path.append(deps_path)
|
||||
|
||||
if REPO_THIRD_PARTY_PATH.is_dir() and str(REPO_THIRD_PARTY_PATH) not in sys.path:
|
||||
sys.path.insert(0, str(REPO_THIRD_PARTY_PATH))
|
||||
|
||||
@@ -949,16 +956,23 @@ def _get_sentry_vapid():
|
||||
except ModuleNotFoundError as error:
|
||||
raise RuntimeError("pywebpush is not installed") from error
|
||||
|
||||
private_key_path, _ = _sentry_push_paths()
|
||||
private_key_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if private_key_path.is_file():
|
||||
return Vapid.from_file(str(private_key_path))
|
||||
with _SENTRY_PUSH_LOCK:
|
||||
private_key_path, _ = _sentry_push_paths()
|
||||
private_key_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if private_key_path.is_file():
|
||||
try:
|
||||
if private_key_path.stat().st_size > 0:
|
||||
return Vapid.from_file(str(private_key_path))
|
||||
except Exception as error:
|
||||
cloudlog.warning("Galaxy: Existing Sentry VAPID private key was invalid, regenerating: %s", error)
|
||||
|
||||
vapid = Vapid()
|
||||
vapid.generate_keys()
|
||||
vapid.save_key(str(private_key_path))
|
||||
private_key_path.chmod(0o600)
|
||||
return vapid
|
||||
vapid = Vapid()
|
||||
vapid.generate_keys()
|
||||
temporary_path = private_key_path.with_suffix(".tmp")
|
||||
vapid.save_key(str(temporary_path))
|
||||
temporary_path.chmod(0o600)
|
||||
temporary_path.replace(private_key_path)
|
||||
return vapid
|
||||
|
||||
|
||||
def _sentry_vapid_public_key(vapid) -> str:
|
||||
@@ -4959,7 +4973,30 @@ def _set_lateral_maneuver_mode(enabled):
|
||||
|
||||
return _save_lateral_maneuver_status(status)
|
||||
|
||||
|
||||
_SLUG_PREFIX_RE = re.compile(r"^/([A-Za-z0-9]{16})(/.*)?$")
|
||||
|
||||
|
||||
class GalaxySlugMiddleware:
|
||||
"""WSGI middleware to normalize reverse-proxy requests prefixed with a 16-character tunnel slug."""
|
||||
|
||||
def __init__(self, wsgi_app):
|
||||
self.wsgi_app = wsgi_app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
path_info = environ.get("PATH_INFO", "")
|
||||
match = _SLUG_PREFIX_RE.match(path_info)
|
||||
if match:
|
||||
environ["HTTP_X_GALAXY_SLUG"] = match.group(1)
|
||||
remainder = match.group(2)
|
||||
environ["PATH_INFO"] = remainder if remainder else "/"
|
||||
return self.wsgi_app(environ, start_response)
|
||||
|
||||
|
||||
def setup(app):
|
||||
if not isinstance(app.wsgi_app, GalaxySlugMiddleware):
|
||||
app.wsgi_app = GalaxySlugMiddleware(app.wsgi_app)
|
||||
|
||||
model_status_debug = {
|
||||
"last_signature": None,
|
||||
"last_log_time": 0.0,
|
||||
@@ -5005,6 +5042,19 @@ def setup(app):
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(_):
|
||||
is_api = (
|
||||
request.path == "/api"
|
||||
or request.path.startswith("/api/")
|
||||
or "/api/" in request.path
|
||||
or request.is_json
|
||||
or (request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html)
|
||||
)
|
||||
if is_api or request.method not in ("GET", "HEAD"):
|
||||
return jsonify({"error": "Not found"}), 404
|
||||
|
||||
if request.path.startswith(("/assets/", "/screen_recordings/", "/thumbnails/", "/video/")):
|
||||
return "Not found", 404
|
||||
|
||||
response = make_response(render_template("index.html"))
|
||||
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
|
||||
response.headers["Pragma"] = "no-cache"
|
||||
@@ -8186,14 +8236,19 @@ def setup(app):
|
||||
def sentry_service_worker():
|
||||
response = send_from_directory(app.static_folder, "service-worker.js", mimetype="application/javascript")
|
||||
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
|
||||
response.headers["Service-Worker-Allowed"] = "/"
|
||||
return response
|
||||
|
||||
@app.route("/api/sentry/push/config", methods=["GET"])
|
||||
def sentry_push_config():
|
||||
try:
|
||||
public_key = _sentry_vapid_public_key(_get_sentry_vapid())
|
||||
except Exception:
|
||||
except (RuntimeError, ModuleNotFoundError) as error:
|
||||
cloudlog.warning("Galaxy: Sentry Web Push dependencies unavailable: %s", error)
|
||||
return jsonify({"enabled": False, "error": "Web Push dependencies are unavailable."}), 503
|
||||
except Exception as error:
|
||||
cloudlog.exception("Galaxy: Failed to initialize Sentry Web Push: %s", error)
|
||||
return jsonify({"enabled": False, "error": f"Push notification service error: {error}"}), 500
|
||||
|
||||
return jsonify({
|
||||
"enabled": True,
|
||||
@@ -8209,8 +8264,12 @@ def setup(app):
|
||||
|
||||
try:
|
||||
_get_sentry_vapid()
|
||||
except Exception:
|
||||
except (RuntimeError, ModuleNotFoundError) as error:
|
||||
cloudlog.warning("Galaxy: Sentry Web Push dependencies unavailable: %s", error)
|
||||
return jsonify({"error": "Web Push dependencies are unavailable."}), 503
|
||||
except Exception as error:
|
||||
cloudlog.exception("Galaxy: Failed to initialize Sentry Web Push for subscription: %s", error)
|
||||
return jsonify({"error": f"Push notification service error: {error}"}), 500
|
||||
|
||||
with _SENTRY_PUSH_LOCK:
|
||||
subscriptions = _load_sentry_push_subscriptions()
|
||||
|
||||
+11
-4
@@ -86,9 +86,16 @@ class Vapid01(object):
|
||||
:type private_key: bytes
|
||||
|
||||
"""
|
||||
# not sure why, but load_pem_private_key fails to deserialize
|
||||
return cls.from_der(
|
||||
b''.join(private_key.splitlines()[1:-1]))
|
||||
try:
|
||||
key = serialization.load_pem_private_key(
|
||||
private_key,
|
||||
password=None,
|
||||
backend=default_backend()
|
||||
)
|
||||
return cls(key)
|
||||
except Exception:
|
||||
lines = [line.strip() for line in private_key.splitlines() if line.strip() and not line.strip().startswith(b"-----")]
|
||||
return cls.from_der(b''.join(lines))
|
||||
|
||||
@classmethod
|
||||
def from_der(cls, private_key):
|
||||
@@ -197,7 +204,7 @@ class Vapid01(object):
|
||||
|
||||
def generate_keys(self):
|
||||
"""Generate a valid ECDSA Key Pair."""
|
||||
self.private_key = ec.generate_private_key(ec.SECP256R1,
|
||||
self.private_key = ec.generate_private_key(ec.SECP256R1(),
|
||||
default_backend())
|
||||
|
||||
def private_pem(self):
|
||||
|
||||
Reference in New Issue
Block a user