cms

The cms block is the whole authenticated zone. Its keys:

cms keydescription
pathonly "/cms" (reserved)
authsessions and sign-in — see auth
menuthe admin's own router (below); the item with path "/" is the admin index (required, exactly one)
guardOPTIONAL — hide /cms behind a device cookie (see below). Omit it and /cms is open as usual

The admin skeleton is a partial the admin templates wrap themselves in — this site declares it as _cms. A menu item binds a path to an hbs template with data views, exactly like a public route binds a site page.

menu item keydescription
path"/" for the index, "/services" for a section (one segment); the page opens at /cms + path
titlelabel in the nav and on the index tiles
hbstemplate of the section page
view / viewsdata, like on page routes; the built-in users and media views serve the system tables
formthe edit binding: { "name": "doc_edit", "hbs": "cms/form.hbs" } — which table form edits the section rows and which template renders the row page (/cms/{path}/{id}). No form — a read-only section
permissionsroles allowed in (admin always is; empty — admin only). The users section is admin-only no matter what — the engine ignores wider permissions there

Two sections of this site:

"cms": {
  "path": "/cms",
  "auth": { "session_ttl_minutes": 120, "cookie_name": "cms_session" },
  "menu": [
    { "path": "/", "title": "Dashboard", "hbs": "cms/index.hbs", "permissions": ["user"] },
    { "path": "/docs", "title": "Articles", "hbs": "cms/list.hbs",
      "view": "doc_admin", "permissions": ["user"],
      "form": { "name": "doc_edit", "hbs": "cms/form.hbs" } }
  ]
  // , "guard": []   // optional: enable the /cms gate (see below)
}

(This example site ships without guard — the line is shown commented for placement; it sits right beside menu.)

The nav is built by the layout from the menu itself — {{#each menu}}, already filtered by the visitor's role: an admin-only section simply does not exist for the user role, in the nav or by direct URL. Every admin template is an ordinary site-owned hbs file; only the sign-in pages (cms/login, cms/invite) keep fixed names — they run the OPAQUE flow before any session exists. Details: Admin templates & the WASM client.

Hiding the admin (optional). guard is a key of the cms block (a sibling of path/auth/menu, not a menu item) that makes the whole /cms zone invisible to browsers without an enrolled device: with the gate on, /cms and everything under it answer the same 404 as any unknown path — the login page is not merely locked, it does not appear to exist. This is a curtain, not a replacement for the password: real OPAQUE sign-in still runs behind it. A fresh bootstrap (plain ./cms in an empty folder) ships with the gate ON and prints the one-time enrollment link in the console first-run summary. Three states:

cms.guardmeaning
key absentno gate — /cms is open as usual (this example after def-help)
[]gate ON, zero devices — /cms is hidden from EVERYONE until you add one (issue a device link first, or you lock yourself out)
list of devicesgate ON — only enrolled devices see /cms. Managed by the server, do not hand-edit

The device list is managed from the CLI, and the cookie key is kept in settings.json as a sha3 hash (a different vault from the password keys in the database):

cms guard add laptop --ttl 24h   # prints a one-time link like /Qx7f… ; open it once from that device
cms guard list                    # active / pending / expired devices
cms guard remove laptop           # revoke a device

Opening the link sets a long-lived cms_guard cookie and burns the link. Every guard command takes --site domain to manage a named site of the map; without it the default site is managed. Inviting a new user while the gate is on: send them a guard link first, then the invite link.

← All articles in this group