oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1-- Publishing: a link is one document's read-only capability, held by whoever
2-- has the URL rather than by anybody the app can name.
3--
4-- This is the third rung of the suite doctrine's ruling 15 — private, granted,
5-- and a read-only link — and the ruling's other half is why there is no role
6-- column here. Every write has a named actor; a link holds no name, so a link
7-- can only ever read. A `role` column would be a place for somebody to later
8-- write "writer" into, and the answer to "should an anonymous link be able to
9-- edit" is already no (Paul's own Sheets ruling 12). The column that cannot
10-- exist is the cheapest way to keep that decision.
11--
12-- `token_hash` is a SHA-256 of the capability, never the capability itself
13-- (suite doctrine ruling 16, following Sheets spec §8.3). The token is 128
14-- bits from crypto/rand, base64url, never derived from doc_id, sqid, title or
15-- clock — a guessable token is the whole feature failing silently, and a
16-- derived one is guessable by anybody who reads this file.
17--
18-- Stored as a hash because a token is a STANDING KEY where a document is a
19-- snapshot. Leaked content is what it was on the day it leaked; a leaked token
20-- keeps opening the live document, every later revision, until somebody
21-- notices. Hashing turns a backup or a replica from an ongoing capability into
22-- a one-time disclosure. That argument holds here even though this app stores
23-- its content in plaintext, which is the tempting reason to skip it.
24--
25-- The consequence is that the app cannot show the URL twice. It is shown once,
26-- in the response to the publish that minted it, and after that the share page
27-- can say only whether a link exists. An owner who loses it replaces it.
28--
29-- Unsalted, deliberately: a salt defends a small input space and there is not
30-- one here — the token's own 128 bits are the salt. SHA-256 rather than a
31-- password hash for the same reason, and because a public page load should not
32-- do key-stretching work.
33--
34-- UNIQUE(doc_id): one live link per document, so "the link" is never an
35-- ambiguous phrase in the UI or in an incident. Turning publishing off DELETEs
36-- the row, and turning it back on mints a fresh token — which makes "off" a
37-- revocation rather than a pause. Somebody who kept a URL from last March does
38-- not get back in when the owner republishes for a different audience.
39--
40-- No expiry column, deliberately: an unexpiring link the owner can see and
41-- switch off is honest, while an expiry nobody is reminded of is a promise the
42-- app makes on the owner's behalf and then keeps only by accident.
43CREATE TABLE IF NOT EXISTS links (
44 id INTEGER PRIMARY KEY AUTOINCREMENT,
45 doc_id INTEGER NOT NULL,
46 token_hash TEXT NOT NULL,
47 created_at DATETIME
48);
49
50CREATE UNIQUE INDEX IF NOT EXISTS idx_links_doc ON links (doc_id);
51
52-- The public read path has only a token to go on, so the lookup that has to be
53-- fast — and single-row — leads on its hash. Byte-equality on an indexed
54-- column is not a timing concern at 128 bits of entropy.
55CREATE UNIQUE INDEX IF NOT EXISTS idx_links_token_hash ON links (token_hash);
56