Keep client secrets in add-on storage; host only verifies and serves the local API.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
"""Native messaging host: 127.0.0.1 HTTP <-> Firefox extension stdio."""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
@@ -13,10 +14,6 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
from fab_auth import CLIENTS_PATH, handle_admin, verify_bearer # noqa: E402
|
||||
|
||||
if sys.platform == "win32":
|
||||
import msvcrt
|
||||
|
||||
@@ -57,6 +54,33 @@ def read_from_extension() -> dict[str, Any] | None:
|
||||
return json.loads(body.decode("utf-8"))
|
||||
|
||||
|
||||
def _header(headers: dict[str, str], name: str) -> str:
|
||||
want = name.lower()
|
||||
for key, value in headers.items():
|
||||
if key.lower() == want:
|
||||
return (value or "").strip()
|
||||
return ""
|
||||
|
||||
|
||||
def verify_token_with_extension(token: str, timeout: float = 8.0) -> dict[str, Any]:
|
||||
if not token.startswith("fab_"):
|
||||
raise PermissionError("unrecognized token")
|
||||
digest = hashlib.sha256(token.encode("ascii")).hexdigest()
|
||||
req_id = str(uuid.uuid4())
|
||||
event = threading.Event()
|
||||
slot: dict[str, Any] = {}
|
||||
with PENDING_LOCK:
|
||||
PENDING[req_id] = (event, slot)
|
||||
send_to_extension({"type": "auth", "id": req_id, "token_hash": digest})
|
||||
if not event.wait(timeout):
|
||||
with PENDING_LOCK:
|
||||
PENDING.pop(req_id, None)
|
||||
raise PermissionError("extension did not verify token")
|
||||
if not slot.get("ok"):
|
||||
raise PermissionError(slot.get("error") or "unknown token")
|
||||
return slot.get("client") or {}
|
||||
|
||||
|
||||
def call_extension(method: str, args: list[Any] | None = None, timeout: float = 15.0) -> dict[str, Any]:
|
||||
req_id = str(uuid.uuid4())
|
||||
event = threading.Event()
|
||||
@@ -111,7 +135,10 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if not self._gate_ok():
|
||||
return False
|
||||
try:
|
||||
verify_bearer({k: v for k, v in self.headers.items()})
|
||||
auth = _header({k: v for k, v in self.headers.items()}, "Authorization")
|
||||
if not auth.lower().startswith("bearer "):
|
||||
raise PermissionError("Bearer token required")
|
||||
verify_token_with_extension(auth[7:].strip())
|
||||
except PermissionError as exc:
|
||||
self._send(401, {"ok": False, "error": str(exc)})
|
||||
return False
|
||||
@@ -130,7 +157,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if parsed.path == "/health":
|
||||
if not self._gate_ok():
|
||||
return
|
||||
self._send(200, {"ok": True, "service": "firefox-agent-bridge", "port": PORT})
|
||||
self._send(200, {"ok": True, "service": "bookmarks-api", "port": PORT})
|
||||
return
|
||||
if not self._authorize("GET", parsed.path, b""):
|
||||
return
|
||||
@@ -208,22 +235,6 @@ def stdin_loop() -> None:
|
||||
break
|
||||
if message is None:
|
||||
break
|
||||
if message.get("type") == "admin":
|
||||
try:
|
||||
result = handle_admin(str(message.get("method") or ""), message.get("args") or [])
|
||||
send_to_extension(
|
||||
{"type": "admin-result", "id": message.get("id"), "ok": True, "result": result}
|
||||
)
|
||||
except Exception as exc:
|
||||
send_to_extension(
|
||||
{
|
||||
"type": "admin-result",
|
||||
"id": message.get("id"),
|
||||
"ok": False,
|
||||
"error": str(exc),
|
||||
}
|
||||
)
|
||||
continue
|
||||
req_id = str(message.get("id") or "")
|
||||
with PENDING_LOCK:
|
||||
pending = PENDING.pop(req_id, None)
|
||||
@@ -237,8 +248,6 @@ def stdin_loop() -> None:
|
||||
|
||||
def main() -> int:
|
||||
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
if not CLIENTS_PATH.exists():
|
||||
log(f"no registered clients at {CLIENTS_PATH}; use the add-on Manage clients page")
|
||||
threading.Thread(target=stdin_loop, name="fab-stdin", daemon=True).start()
|
||||
server = ThreadingHTTPServer((HOST, PORT), Handler)
|
||||
log(f"listening on http://{HOST}:{PORT}")
|
||||
|
||||
Reference in New Issue
Block a user