← back to Inline Editor
README.md
124 lines
# @dw/inline-editor
Drop-in inline page editor for the DW sister-site fleet (~46 sites). Admins
authenticated via **auth.agentabrams.com** (role=admin) can click any text
block, image, link, or HTML region on any sister site and edit it directly
on the page — Wix-style. Edits persist in `dw_unified.page_overrides` (PG)
and the source HTML is left untouched.
## Architecture
```
┌─────────────────────────────────────────┐
│ auth.agentabrams.com (existing widget) │
│ → sets cookie with { email, role } │
└────────────────┬────────────────────────┘
│
┌──────────────────▼──────────────────┐
│ per-site Express app │
│ app.use(inlineEditor({ site })) │
│ ─ middleware: HTML rewriter │
│ ─ routes: /api/_inline/* │
│ ─ static: /_inline/editor.js,css │
└────────────────┬────────────────────┘
│ reads/writes
▼
┌──────────────────────────┐
│ dw_unified PG │
│ · page_overrides │ ← current state per (site,path,selector)
│ · page_edit_log │ ← append-only audit
│ · page_uploads │ ← image uploads metadata
└──────────────────────────┘
```
## Persistence — append-only audit (best-practice mode)
`page_overrides` holds the *current* override for each (site, path, selector).
`page_edit_log` is **append-only** — every save inserts a new row with editor
email + IP + UA. A revert is a new log row that updates `page_overrides`. No
edit is ever silently lost.
```
page_overrides
site, path, selector PRIMARY KEY
kind 'text' | 'html' | 'image' | 'href' | 'image_alt'
value sanitized payload
updated_at, updated_by_email
page_edit_log
id, site, path, selector, kind,
old_value, new_value,
editor_email, editor_ip, editor_ua, ts
```
## Edit surface (v1 — "all like Wix")
| kind | trigger | persists |
|---|---|---|
| `text` | click any text-bearing leaf node | sanitized innerText |
| `html` | double-click a `[data-edit-region]` block | DOMPurify-sanitized innerHTML |
| `image` | click an `<img>` | new src (uploaded to /uploads or pasted URL) |
| `image_alt` | shift-click an `<img>` | alt text |
| `href` | right-click an `<a>` | new href (only http/https/mailto/tel) |
All HTML kinds are sanitized server-side via `sanitize-html` with a strict
allowlist (no `<script>`, no `on*` attrs, no `javascript:` urls). Image uploads
gate on MIME (`image/*`), size (≤5 MB), and re-validate at content sniff.
## Auth (role=admin via auth.agentabrams.com)
Every save endpoint checks the `dw_auth` cookie issued by the auth widget.
The cookie is a signed JWT; we verify against `AUTH_JWKS_URL` and require:
- `role === 'admin'`
- `iss === 'https://auth.agentabrams.com'`
- `exp > now`
Rate-limited to 10 edits / min per editor email.
## Wiring per site (one line)
```js
const inlineEditor = require('@dw/inline-editor');
app.use(inlineEditor({
site: 'corkwallcovering.com',
uploadDir: path.join(__dirname, 'public', 'uploads'),
}));
```
The middleware:
1. Wraps `res.send` for `text/html` responses, walking the DOM and applying
any matching overrides from PG (cached for 30 s per (site, path)).
2. Injects `<script src="/_inline/editor.js" defer></script>` and a tiny
bootstrap `<script>` that reads the auth cookie and decides whether to
activate edit mode.
3. Mounts `/api/_inline/save`, `/state`, `/upload`, `/revert`.
Sites *not* served by Express (static nginx) can opt in by including the
script tag and pointing it at a central edit-API host.
## Security checklist
- [x] Append-only audit log; never delete, only superseding inserts
- [x] HTML sanitized server-side, not client-side
- [x] CSP-friendly: editor uses no `eval`, no inline event handlers (uses delegated listeners + dataset)
- [x] Auth cookie verified per request; admin role required
- [x] Rate limit 10 edits/min/editor; 100/day soft cap with alert
- [x] Image uploads MIME-sniffed (file signature), size-capped, stored outside web root by default
- [x] Selector validated as stable (id, data-edit-id, structural path with index); reject XPath
- [x] No raw HTML kind accepted unless region has `[data-edit-region]` opt-in
- [x] Backups: page_overrides snapshotted to `data/overrides.snapshot.json` hourly
- [x] Revert path from admin.agentabrams.com console
## Pilot
Pilot site: TBD (a single low-traffic sister site, Mac2-local first).
Production deploy to Kamatera fleet only after the pilot is green and
Steve gives explicit "deploy fleet" go-ahead.
## NOT in scope (yet)
- Cross-page reflows (editor doesn't break grids when text expands)
- Layout edits (drag-drop blocks) — that needs a real visual editor
- Multi-language overrides
- Versioning UI (we have the log; UI lands in admin console v2)