from http.server import HTTPServer, BaseHTTPRequestHandler from urllib.parse import urlparse import json import time from pathlib import Path from lane_common.policy import lane_policy from lane_evidence.schema import make_event from lane_evidence.broker import EvidenceBroker from lane_accounts.server import SESSION_MANAGER from .storage import AccountBucketStorage from .corp_link import process_corp_token class BucketHandler(BaseHTTPRequestHandler): ctx = None def _json(self, status: int, payload: dict) -> None: body = json.dumps({**payload, **lane_policy()}, sort_keys=True).encode("utf-8") self.send_response(status) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def _get_context(self) -> dict | None: token = self.headers.get("X-Lane-Session") if not token: return None return SESSION_MANAGER.get_context(token) def do_POST(self) -> None: parsed = urlparse(self.path) ctx = self._get_context() if ctx is None: self._json(401, {"error": "unauthorized"}) return account = ctx["account"] device_id = ctx["device_id"] account_id = account["id"] length = int(self.headers.get("Content-Length", "0")) body = self.rfile.read(length) corp_token = self.headers.get("X-Corp-SSO-Token") corp = process_corp_token(corp_token) if corp_token else None if parsed.path == "/bucket/upload": logical_key = self.headers.get("X-Logical-Key", "upload.bin") obj_hash = self.ctx["storage"].store(account_id, device_id, body, logical_key) event = make_event( ts=int(time.time()), account_id=account_id, device_id=device_id, object_hash=obj_hash, logical_key=logical_key, action="upload", corp=corp, ) self.ctx["broker"].append_event(event) self._json(200, {"object_hash": obj_hash, "logical_key": logical_key}) return self._json(404, {"error": "not_found"}) def do_GET(self) -> None: parsed = urlparse(self.path) ctx = self._get_context() if ctx is None: self._json(401, {"error": "unauthorized"}) return account = ctx["account"] device_id = ctx["device_id"] account_id = account["id"] if parsed.path.startswith("/bucket/object/"): object_hash = parsed.path.split("/bucket/object/")[1] data = self.ctx["storage"].load(account_id, device_id, object_hash) if data is None: self.send_response(404) self.end_headers() return event = make_event( ts=int(time.time()), account_id=account_id, device_id=device_id, object_hash=object_hash, logical_key="unknown", action="read", corp=None, ) self.ctx["broker"].append_event(event) self.send_response(200) self.send_header("Content-Type", "application/octet-stream") self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) return self._json(404, {"error": "not_found"}) def run() -> None: base_dir = Path("/lane-estate/buckets") db_path = Path("/lane-estate/buckets.db") storage = AccountBucketStorage(base_dir, db_path) evidence_dir = Path("/lane-estate/evidence") endpoints = [ "https://albertlane.net/evidence/ingest", ] signing_key = b"lane-evidence-signing-key" broker = EvidenceBroker(evidence_dir, endpoints, signing_key) BucketHandler.ctx = { "storage": storage, "broker": broker, } server = HTTPServer(("0.0.0.0", 8082), BucketHandler) server.serve_forever()