Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | 357x 357x 357x 446x 446x 446x 357x 357x 357x 357x 1x 357x 357x 357x 361x 1x 360x 357x 1x 4x 4x 3x 1x 325x 325x 19x 19x 3x 357x 357x 357x 357x 357x 357x 357x 357x 357x 357x 357x 357x 357x 357x | import { serveStatic } from "@hono/node-server/serve-static";
import type Database from "better-sqlite3-multiple-ciphers";
import { Hono } from "hono";
import { cors } from "hono/cors";
import type { ContainerContext } from "../crypto/lifecycle.js";
import { isDemoMode } from "../demo/demo-mode.js";
import type { SyncScheduler } from "../sync/sync-scheduler.js";
import { attachmentRoutes } from "./routes/attachments.js";
import { connectorRoutes } from "./routes/connectors.js";
import { draftRoutes } from "./routes/drafts.js";
import { encryptionRoutes } from "./routes/encryption.js";
import { identityRoutes } from "./routes/identities.js";
import { inboxRoutes } from "./routes/inbox.js";
import { labelRoutes } from "./routes/labels.js";
import { messageRoutes } from "./routes/messages.js";
import { searchRoutes } from "./routes/search.js";
import { sendRoutes } from "./routes/send.js";
import { syncRoutes } from "./routes/sync.js";
import { trustedSenderRoutes } from "./routes/trusted-senders.js";
import { webhookRoutes } from "./routes/webhook.js";
export function createApp(context: ContainerContext): { app: Hono } {
const app = new Hono();
app.use("*", cors());
// Content Security Policy — defense-in-depth against XSS.
// Even if the HTML sanitizer misses something, these headers prevent
// inline script execution and restrict resource loading.
app.use("*", async (c, next) => {
await next();
// Only set CSP on HTML responses (the SPA shell)
const ct = c.res.headers.get("content-type") ?? "";
Iif (ct.includes("text/html")) {
c.res.headers.set(
"Content-Security-Policy",
[
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: cid: https:",
"font-src 'self'",
"connect-src 'self'",
"frame-src 'self' blob:",
"object-src 'none'",
"base-uri 'self'",
].join("; "),
);
}
});
// Serve frontend static files
app.use("/assets/*", serveStatic({ root: "./frontend/dist" }));
app.get("/stork.svg", serveStatic({ root: "./frontend/dist", path: "stork.svg" }));
const api = new Hono();
// ── Demo mode indicator ─────────────────────────────────────────────────
api.get("/demo", (c) => {
return c.json({ demo: isDemoMode() });
});
// ── Always-accessible endpoints (health, status, setup, unlock) ─────────
api.route("/", encryptionRoutes(context));
// ── Push-based webhook endpoints — bypass lock middleware, handle lock state themselves ──
api.route("/webhook", webhookRoutes(context));
// ── Lock middleware — blocks all data routes until unlocked ──────────────
api.use("*", async (c, next) => {
if (context.state !== "unlocked") {
return c.json({ error: "Container is locked", state: context.state }, 423);
}
await next();
});
// ── Demo read-only middleware — blocks mutations on data routes ──────────
if (isDemoMode()) {
api.use("*", async (c, next) => {
const method = c.req.method;
if (method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE") {
return c.json({ error: "This is a read-only demo" }, 403);
}
await next();
});
}
// ── Data routes (all require unlocked state via middleware above) ────────
function getDb(): Database.Database {
Iif (!context.db) throw new Error("db not available");
return context.db;
}
function getScheduler(): SyncScheduler {
Iif (!context.scheduler) throw new Error("scheduler not available");
return context.scheduler;
}
function getR2Poller() {
return context.r2Poller;
}
api.route("/identities", identityRoutes(getDb));
api.route("/connectors", connectorRoutes(getDb, getScheduler, getR2Poller));
api.route("/inbox", inboxRoutes(getDb));
api.route("/messages", messageRoutes(getDb));
api.route("/labels", labelRoutes(getDb));
api.route("/attachments", attachmentRoutes(getDb));
api.route("/search", searchRoutes(getDb));
api.route("/sync", syncRoutes(getScheduler, getDb));
api.route("/send", sendRoutes(getDb));
api.route("/drafts", draftRoutes(getDb));
api.route("/", trustedSenderRoutes(getDb));
app.route("/api", api);
// SPA fallback — serve index.html for all non-API, non-asset routes
app.get("*", serveStatic({ root: "./frontend/dist", path: "index.html" }));
return { app };
}
|