diff --git a/bliss-cli/live-editor/artifact-editor.eta b/bliss-cli/live-editor/artifact-editor.eta deleted file mode 100644 index 7e5c1a8..0000000 --- a/bliss-cli/live-editor/artifact-editor.eta +++ /dev/null @@ -1,57 +0,0 @@ -
-
- <% if (it.kind === 'route') { %><%= it.artifact.verb %> <%= it.artifact.path %><% } else { %><%= it.artifact.name %><% } %> - <%= it.kind %> -
-
- <% if (it.kind === 'route' && it.artifact.verb !== 'GET') { %> -

Saving does not replay this <%= it.artifact.verb %> route.

- <% } %> - -
<%= it.kind === 'route' ? it.artifact.handler : it.artifact.content %>
- <% if (it.kind === 'template') { %> -
- Rendering test -
<%= it.artifact.test_object || '' %>
- -
- <% } %> - -
-
- diff --git a/bliss-cli/live-editor/inspector.js b/bliss-cli/live-editor/inspector.js deleted file mode 100644 index b4f228d..0000000 --- a/bliss-cli/live-editor/inspector.js +++ /dev/null @@ -1,3 +0,0 @@ -function handler(req, res) { - res.render("inspector/slideout", {}); -} diff --git a/bliss-cli/live-editor/install.js b/bliss-cli/live-editor/install.js deleted file mode 100644 index 8804c56..0000000 --- a/bliss-cli/live-editor/install.js +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env node - -// Install/update the live editor using Bliss's public CLI workflow. This file -// intentionally does not open the Bliss database directly. -const { spawnSync } = require("node:child_process"); -const path = require("node:path"); - -const repo = path.resolve(__dirname, "../.."); -const cli = path.join(repo, "bliss-cli/bliss"); - -function bliss(...args) { - const result = spawnSync(process.execPath, [cli, ...args], { - cwd: repo, - env: process.env, - encoding: "utf8", - }); - if (result.status !== 0) throw new Error(result.stderr || result.stdout); - return result.stdout.trim(); -} - -function json(...args) { - return JSON.parse(bliss(...args)); -} - -const structures = json("structures"); -let structure = structures.find((item) => item.name === "__bliss_live_editor"); -if (!structure) { - const created = json("create-structure", "__bliss_live_editor"); - structure = { id: created.id }; -} - -const structureId = String(structure.id); -bliss("update-settings", structureId, "--route-prefix", "/_bliss"); -let state = json("structure", structureId); -// No db is attached: the editor reads structure sources through the /plumbing -// API (owner-checked), not by mounting the prime db — which is no longer -// attachable to other structures anyway. - -const templates = [ - ["inspector/slideout", "slideout.eta"], - ["inspector/route_editor", "route-editor.eta"], - ["inspector/template_editor", "template-editor.eta"], - ["inspector/artifact_editor", "artifact-editor.eta"], -]; -for (const [name, filename] of templates) { - let template = state.templates.find((item) => item.name === name); - if (!template) { - template = json("create-template", structureId, name); - } - bliss( - "update-template", - structureId, - String(template.id), - "--content-file", - path.join(__dirname, filename), - ); -} - -const routes = [ - ["/inspector", "inspector.js"], - ["/editor/route/:id", "route-editor.js"], - ["/editor/template/:id", "template-editor.js"], -]; -for (const [routePath, filename] of routes) { - let route = state.routes.find( - (item) => item.verb === "GET" && item.path === routePath, - ); - if (!route) { - route = json("create-route", structureId, "GET", routePath); - } - bliss( - "update-route", - structureId, - String(route.id), - "--handler-file", - path.join(__dirname, filename), - ); -} - -console.log(`live editor installed as Structure ${structureId}`); diff --git a/bliss-cli/live-editor/route-editor.eta b/bliss-cli/live-editor/route-editor.eta deleted file mode 100644 index 212859e..0000000 --- a/bliss-cli/live-editor/route-editor.eta +++ /dev/null @@ -1,28 +0,0 @@ -
-
<%= it.sourceRoute.verb %> <%= it.sourceRoute.path %>
- <% if (it.sourceRoute.verb !== "GET") { %> -
Rerunning this <%= it.sourceRoute.verb %> request can repeat writes or external side effects. Bliss will not rerun it automatically.
- <% } %> -
<%= it.sourceRoute.handler %>
- -
- diff --git a/bliss-cli/live-editor/route-editor.js b/bliss-cli/live-editor/route-editor.js deleted file mode 100644 index 8026c52..0000000 --- a/bliss-cli/live-editor/route-editor.js +++ /dev/null @@ -1,14 +0,0 @@ -// Read a route's source through the plumbing API rather than mounting the prime -// db directly. The prime db is no longer attachable to other structures, and -// going through /plumbing means this read is owner-checked: forwarding the -// caller's session cookie, plumbing 404s any route in a structure they can't -// see, so the live editor can only open what the user is already allowed to edit. -async function handler(req, res) { - const base = `${req.protocol}://${req.get("host")}`; - const response = await fetch(`${base}/plumbing/routes/${req.params.id}`, { - headers: { cookie: req.headers.cookie || "" }, - }); - if (!response.ok) return res.status(response.status).send("Route not found"); - const artifact = await response.json(); - res.render("inspector/artifact_editor", { kind: "route", artifact }); -} diff --git a/bliss-cli/live-editor/slideout.eta b/bliss-cli/live-editor/slideout.eta deleted file mode 100644 index e3fe878..0000000 --- a/bliss-cli/live-editor/slideout.eta +++ /dev/null @@ -1,200 +0,0 @@ - - diff --git a/bliss-cli/live-editor/template-editor.eta b/bliss-cli/live-editor/template-editor.eta deleted file mode 100644 index f2aa49d..0000000 --- a/bliss-cli/live-editor/template-editor.eta +++ /dev/null @@ -1,25 +0,0 @@ -
-
<%= it.sourceTemplate.name %>
-
<%= it.sourceTemplate.content %>
- -
- diff --git a/bliss-cli/live-editor/template-editor.js b/bliss-cli/live-editor/template-editor.js deleted file mode 100644 index e366ee1..0000000 --- a/bliss-cli/live-editor/template-editor.js +++ /dev/null @@ -1,13 +0,0 @@ -// Read a template's source through the plumbing API rather than mounting the -// prime db directly (see route-editor.js for the why). Forwarding the caller's -// session cookie keeps the read owner-checked: plumbing 404s any template in a -// structure the user can't see. -async function handler(req, res) { - const base = `${req.protocol}://${req.get("host")}`; - const response = await fetch(`${base}/plumbing/templates/${req.params.id}`, { - headers: { cookie: req.headers.cookie || "" }, - }); - if (!response.ok) return res.status(response.status).send("Template not found"); - const artifact = await response.json(); - res.render("inspector/artifact_editor", { kind: "template", artifact }); -} diff --git a/proof_plumbing_owner_checks.js b/proof_plumbing_owner_checks.js deleted file mode 100644 index fc3bd5e..0000000 --- a/proof_plumbing_owner_checks.js +++ /dev/null @@ -1,83 +0,0 @@ -// Proof: the /plumbing read API only ever hands out an artifact to a caller who -// may see the structure it BELONGS TO — including via the id-only endpoints the -// live editor uses, and even when a private db is aliased into a public one. -// -// It mounts the REAL plumbing.js router on a throwaway express app (temp db), -// with a tiny middleware that fakes a logged-in session from an ?as= query, and -// makes real HTTP requests. So the assertions exercise the shipped access logic. - -const fs = require("fs"); -const os = require("os"); -const path = require("path"); -const express = require("express"); - -const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "bliss-plumbing-")); -fs.mkdirSync(path.join(tmp, "dbs")); -process.chdir(tmp); - -const PROJECT = "/home/happy/code/bliss"; -const model = require(path.join(PROJECT, "db.js")); -model.applyMigrations(); - -// ---- scenario ------------------------------------------------------------ -const alice = model.createUser("alice", "x"); // member of the private structure -const bob = model.createUser("bob", "x"); // outsider - -// PRIVATE structure owned by alice: its own route, template, and db. -const priv = model.createStructure("private-vault", alice); -const privRoute = model.createRoute("GET", "/secret", priv, "handler(){}"); -const privTemplate = model.createTemplate(priv, "secret_tpl", "top secret", ""); -const secretsDbId = model.createDb(priv, "secrets"); -model.setStructurePrivacy(priv, true, alice); - -// PUBLIC structure owned by bob that ALIASES alice's private db under "secrets". -const pub = model.createStructure("public-front", bob); -model.attachDb(pub, secretsDbId, "secrets"); - -// ---- app: real plumbing router, faked session from ?as= ------------------ -const app = express(); -app.use((req, _res, next) => { - const as = req.query.as; - req.session = { userId: as === "alice" ? alice : as === "bob" ? bob : null }; - next(); -}); -app.use("/plumbing", require(path.join(PROJECT, "plumbing.js"))); -const server = app.listen(0); -const port = server.address().port; - -async function get(url) { - const res = await fetch(`http://127.0.0.1:${port}${url}`); - return res.status; -} - -function expect(label, actual, wanted) { - const ok = actual === wanted; - console.log(` ${ok ? "PASS" : "FAIL"} | ${label} (got ${actual}, want ${wanted})`); - return ok; -} - -(async () => { - const results = []; - - console.log("\nid-only endpoints (what the live editor calls):"); - results.push(expect("alice reads her private route ", await get(`/plumbing/routes/${privRoute}?as=alice`), 200)); - results.push(expect("bob reads alice's private route ", await get(`/plumbing/routes/${privRoute}?as=bob`), 404)); - results.push(expect("anon reads alice's private route ", await get(`/plumbing/routes/${privRoute}`), 404)); - results.push(expect("alice reads her private template ", await get(`/plumbing/templates/${privTemplate}?as=alice`), 200)); - results.push(expect("bob reads alice's private template", await get(`/plumbing/templates/${privTemplate}?as=bob`), 404)); - - console.log("\nstructure-scoped endpoints reject id-laundering:"); - // bob CAN see his public structure, but the route id belongs to the private one. - results.push(expect("bob: pub structure + private routeId", await get(`/plumbing/structures/${pub}/routes/${privRoute}?as=bob`), 404)); - - console.log("\naliased private db is not leaked through a public structure:"); - results.push(expect("bob reads aliased private db ", await get(`/plumbing/structures/${pub}/dbs/${secretsDbId}?as=bob`), 404)); - results.push(expect("alice reads that db via her structure", await get(`/plumbing/structures/${priv}/dbs/${secretsDbId}?as=alice`), 200)); - - const pass = results.every(Boolean); - console.log(`\n${pass ? "PROVEN" : "FAILED"}: plumbing gates every artifact against its own structure's access rules.`); - - server.close(); - fs.rmSync(tmp, { recursive: true, force: true }); - process.exit(pass ? 0 : 1); -})();