capsite/docs: cookie-rotation wiring diagram (source)
Who holds what after mint: Alice holds the store id (rotate, revoke), diigo-cache and a leaked-id Mallory hold only the instance, whose init.headerStore reaches the store's read-only facet. Drawn as an SVG page in the style of MarkM's ERTP slides. Generated-by: Claude Opus 5.5
This commit is contained in:
1 parent
4dd3fe6bde
commit
055b572297
1 file changed
+133
@@ -0,0 +1,133 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html><head><meta charset="utf-8">
|
||||||
|
<title>webapp cookie rotation: who holds what</title>
|
||||||
|
<!-- drawn after the style of MarkM's ERTP slides; cookie-rotation-wiring.png is a headless-Chromium screenshot of this page -->
|
||||||
|
<style>
|
||||||
|
html, body { margin: 0; background: #fff; }
|
||||||
|
.sans { font-family: "Helvetica Neue", "DejaVu Sans", sans-serif; }
|
||||||
|
.obj { font-family: "DejaVu Serif", serif; font-style: italic; }
|
||||||
|
.mono { font-family: "DejaVu Sans Mono", monospace; }
|
||||||
|
</style></head>
|
||||||
|
<body>
|
||||||
|
<svg id="c" xmlns="http://www.w3.org/2000/svg" width="1120" height="820" viewBox="0 0 1120 820">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="ball" cx="40%" cy="35%" r="70%">
|
||||||
|
<stop offset="0%" stop-color="#7cc8f8"/>
|
||||||
|
<stop offset="100%" stop-color="#3aa6ee"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="objfill" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#6cbcf4"/>
|
||||||
|
<stop offset="100%" stop-color="#4aa8ec"/>
|
||||||
|
</linearGradient>
|
||||||
|
<marker id="head" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto-start-reverse">
|
||||||
|
<path d="M0,0 L10,5 L0,10 z" fill="#1d4f7a"/>
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
<script>
|
||||||
|
const svg = document.getElementById("c");
|
||||||
|
const NS = "http://www.w3.org/2000/svg";
|
||||||
|
const el = (name, attrs, parent = svg) => {
|
||||||
|
const e = document.createElementNS(NS, name);
|
||||||
|
for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v);
|
||||||
|
parent.appendChild(e);
|
||||||
|
return e;
|
||||||
|
};
|
||||||
|
const text = (x, y, s, cls, attrs = {}) => {
|
||||||
|
const t = el("text", { x, y, class: cls, "text-anchor": "middle", fill: "#1d3550", ...attrs });
|
||||||
|
t.textContent = s;
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
|
||||||
|
const INK = "#1d4f7a";
|
||||||
|
const dot = (x, y) => el("circle", { cx: x, cy: y, r: 4.5, fill: INK });
|
||||||
|
/** a reference: dot at the holder, arrowhead at the target */
|
||||||
|
const ref = (x1, y1, x2, y2, bend = 0, extra = {}) => {
|
||||||
|
dot(x1, y1);
|
||||||
|
const mx = (x1 + x2) / 2 - (y2 - y1) * bend, my = (y1 + y2) / 2 + (x2 - x1) * bend;
|
||||||
|
el("path", { d: `M${x1},${y1} Q${mx},${my} ${x2},${y2}`, fill: "none", stroke: INK, "stroke-width": 2, "marker-end": "url(#head)", ...extra });
|
||||||
|
};
|
||||||
|
|
||||||
|
// maker box: a pale rounded region, with stacked instances inside
|
||||||
|
function maker(x, y, w, h, label) {
|
||||||
|
el("rect", { x, y, width: w, height: h, rx: 26, fill: "#d6ecfb", stroke: "#b5dbf5", "stroke-width": 1.5 });
|
||||||
|
text(x + 18, y + 30, label, "obj", { "text-anchor": "start", "font-size": 20 });
|
||||||
|
for (let i = 2; i >= 0; i -= 1) {
|
||||||
|
el("rect", {
|
||||||
|
x: x + 18 + i * 9, y: y + 48 + i * 9, width: w - 52, height: h - 82, rx: 22,
|
||||||
|
fill: "#c8e5fa", stroke: "#8ec9f2", "stroke-width": 1.5, "fill-opacity": 0.9,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// an object: rounded square, optionally stacked (several made)
|
||||||
|
function obj(x, y, w, h, label, stacked = false) {
|
||||||
|
const n = stacked ? 3 : 1;
|
||||||
|
for (let i = n - 1; i >= 0; i -= 1) {
|
||||||
|
el("rect", { x: x + i * 7, y: y - i * 7, width: w, height: h, rx: 12, fill: "url(#objfill)", stroke: "#2f8fd6", "stroke-width": 1.5 });
|
||||||
|
}
|
||||||
|
text(x + w / 2, y + h / 2 + 7, label, "obj", { "font-size": 19 });
|
||||||
|
}
|
||||||
|
function actor(cx, cy, label) {
|
||||||
|
el("circle", { cx, cy, r: 52, fill: "url(#ball)", stroke: "#2f8fd6", "stroke-width": 1.5 });
|
||||||
|
text(cx, cy + 6, label, "sans", { "font-size": label.length > 8 ? 15 : 17 });
|
||||||
|
}
|
||||||
|
// a table like the ERTP ledger
|
||||||
|
function table(x, y, title, rows, colW) {
|
||||||
|
const rowH = 28, w = colW[0] + colW[1];
|
||||||
|
el("rect", { x, y, width: w, height: 36 + rowH * rows.length, fill: "#5fb5f2", stroke: "#2f8fd6", "stroke-width": 1.5 });
|
||||||
|
text(x + w / 2, y + 25, title, "obj", { "font-size": 18 });
|
||||||
|
rows.forEach(([k, v], i) => {
|
||||||
|
const ry = y + 36 + i * rowH;
|
||||||
|
el("line", { x1: x, y1: ry, x2: x + w, y2: ry, stroke: "#2f8fd6", "stroke-width": 1.2 });
|
||||||
|
text(x + 10, ry + 19, k, "mono", { "text-anchor": "start", "font-size": 14 });
|
||||||
|
if (v) text(x + colW[0] + 10, ry + 19, v, "mono", { "text-anchor": "start", "font-size": 14 });
|
||||||
|
});
|
||||||
|
el("line", { x1: x + colW[0], y1: y + 36, x2: x + colW[0], y2: y + 36 + rowH * rows.length, stroke: "#1d4f7a", "stroke-width": 1.5 });
|
||||||
|
return { rowY: (i) => y + 36 + i * rowH + rowH / 2, x, w, colX: x + colW[0] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// speech bubble (Alice rotates after logging in again)
|
||||||
|
el("path", {
|
||||||
|
d: "M30,22 h470 a16,16 0 0 1 16,16 v34 a16,16 0 0 1 -16,16 h-330 l-38,48 l6,-48 h-102 a16,16 0 0 1 -16,-16 v-34 a16,16 0 0 1 16,-16 z",
|
||||||
|
fill: "#d9d9d9", stroke: "#555", "stroke-width": 2,
|
||||||
|
});
|
||||||
|
const code = el("text", { x: 46, y: 62, class: "mono", "font-size": 17, fill: "#222" });
|
||||||
|
code.innerHTML = '<tspan fill="#b0177b">await</tspan> E(store).set(<tspan fill="#1a7f37">"cookie"</tspan>, freshCookie);';
|
||||||
|
|
||||||
|
// actors
|
||||||
|
actor(150, 200, "Alice");
|
||||||
|
actor(560, 200, "diigo-cache");
|
||||||
|
actor(930, 200, "Mallory");
|
||||||
|
|
||||||
|
// makers
|
||||||
|
maker(30, 320, 470, 470, "makeMapStore");
|
||||||
|
maker(560, 320, 530, 470, "makeWebApp");
|
||||||
|
|
||||||
|
// MapStoreDo insides
|
||||||
|
obj(80, 420, 120, 80, "store");
|
||||||
|
obj(80, 600, 120, 80, "readOnly", true);
|
||||||
|
const kv = table(260, 430, "kv", [["cookie", "session=…"], ["", ""], ["", ""], ["", ""], ["", ""]], [90, 110]);
|
||||||
|
|
||||||
|
// WebAppDo insides
|
||||||
|
obj(620, 560, 140, 90, "access", true);
|
||||||
|
const init = table(830, 420, "init", [["url", "diigo…"], ["headers", "{…}"], ["headerStore", ""], ["", ""]], [130, 85]);
|
||||||
|
|
||||||
|
// references
|
||||||
|
ref(140, 228, 125, 418, 0.05); // Alice → store
|
||||||
|
ref(172, 224, 508, 210, 0.04); // Alice → diigo-cache (she wired it)
|
||||||
|
ref(560 - 6, 200 + 18, 660, 553, 0.12); // cache → access
|
||||||
|
ref(930 - 14, 200 + 20, 720, 553, -0.08); // Mallory → access
|
||||||
|
ref(160, 470, kv.x - 2, kv.rowY(0), 0.05); // store → kv
|
||||||
|
ref(150, 666, kv.x - 2, kv.rowY(0) + 6, -0.25); // readOnly → kv (read)
|
||||||
|
ref(760, 600, init.x - 2, init.rowY(2), -0.1); // access → init
|
||||||
|
ref(init.colX + 40, init.rowY(2), 208, 640, -0.28); // headerStore → readOnly
|
||||||
|
|
||||||
|
// captions
|
||||||
|
const cap = (x, y, s, attrs = {}) => text(x, y, s, "sans", { "font-size": 14, fill: "#33536f", ...attrs });
|
||||||
|
cap(930, 118, "holds the instance id:", {});
|
||||||
|
cap(930, 136, "uses the cookie, can't reach store", {});
|
||||||
|
cap(330, 300, "storeId: rotate · revoke", {});
|
||||||
|
cap(760, 806, "init.headerStore → the store's read-only facet, read at every send", {});
|
||||||
|
cap(265, 806, "shapes: keyShape \"cookie\", valueShape M.string()", {});
|
||||||
|
</script>
|
||||||
|
</body></html>
|
||||||
Reference in new issue
Block a user