Private team editor
The optional editor is a Node service for authenticated writers. It is not required for static readers and is not deployed on this public showcase. A team operator supplies server identity, filesystem permissions, network access, TLS, process supervision and backups. V2 does not provision those services.
Keep one supervised editor process for an installation. Its sessions, rate-limit maps and current publication job are process-local. Cross-process account-file locking does not make the whole editor a replicated/multi-writer service. Do not distribute requests across interchangeable instances and assume cookies/jobs survive.
Separate audiences and storage
| Component | Audience and path |
|---|---|
| Reader | Intended readers; generated output only |
| Editor | Authorized writers; private supervised service |
| Source | Service/operator; saved Markdown and media |
| Runtime | Protected accounts, settings, links, history |
| Backups | Restricted operators; separate storage |
Illustrative layout:
/srv/field-guide/app engine, content, templates, static
/srv/field-guide/data private runtime
/srv/field-guide/app/dist generated reader only
A reader web server needs read/traverse permission to output and its ancestors, not accounts. Editor needs content/runtime writes and output-parent access for staged siblings/renames. A read-only dist permission check alone does not establish publication can work.
Use a dedicated low-privilege service user. Keep private data out of content/static/output/repositories. Back up selected source/runtime consistently. Assigned sections and editor authentication protect writing, not public reader URLs. A private reader needs a separate host/network gate covering index/assets too.
First account and runtime selection
Set MARKSTACK_USERS_FILE to the same protected absolute path for account commands and service. The editor keeps runtime data beside it. A build’s --data or MARKSTACK_DATA_DIR does not relocate editor users.
# Illustrative selected path; inspect ownership/access first.
MARKSTACK_USERS_FILE=/srv/field-guide/data/users.json \
npm run editor:create-admin -- --user admin
Create-admin asks for a hidden password and confirms twice. Do not put it in arguments, repository files or chat. It fails on an existing name; it does not silently reset it. Reset-admin is a separate recovery operation which sets a new password, enables the administrator, clears forced-change state and invalidates sessions on subsequent requests. Use only for the intended account with an actual recovery decision.
Admin sees all editor content and manages accounts/settings/publication. Editor role is limited to assigned top-level physical sections; granted editing access is not a reading-confidentiality model. Test synthetic accounts and server enforcement, not only hidden client buttons.
Service settings
| Purpose | Choice |
|---|---|
| Bind | Loopback 127.0.0.1 behind local proxy |
| Port | Intended editor listener, default 3001 |
| Accounts | Protected selected users file |
| Hosts | Exact accepted browser hostname/port |
| Cookie | Secure mode for actual HTTPS |
| Forwarding | Actual trusted proxy connection IPs |
| Lifetimes | Reviewed idle/absolute session limits |
Exact keys are MARKSTACK_EDITOR_HOST, MARKSTACK_EDITOR_PORT, MARKSTACK_USERS_FILE, MARKSTACK_EDITOR_ALLOWED_HOSTS, MARKSTACK_EDITOR_COOKIE_SECURE, MARKSTACK_TRUSTED_PROXIES, MARKSTACK_SESSION_IDLE_MINUTES and MARKSTACK_SESSION_MAX_MINUTES. Environment variables documents parser/default details. A value in a shell is not automatically a service environment value; restart/reload through the actual supervisor after reviewed changes.
Host entries are normalized lower-case values, not a broad wildcard permission. Preserve correct Host through the proxy; do not rely on X-Forwarded-Host to substitute for it. Secure mode selects the host-prefixed cookie with Secure, Path=/ and no Domain. It requires actual HTTPS at the browser even though proxy upstream is loopback HTTP.
Trusted proxy boundary
| Direct peer | Client-address handling |
|---|---|
| Untrusted | Ignore forwarded address claim |
| Configured trusted peer | Evaluate forwarded chain by implementation rules |
| Multiple proxy hops | Review/sanitize each hop and actual trust set |
| Broad/unverified trust | Unsafe assumption, do not deploy |
loopback expands to loopback addresses; it is appropriate only when the actual trusted proxy connects locally. The pin’s parser is exact addresses/loopback, not a general CIDR trust configuration. Do not paste CDN address ranges into it and assume range matching.
The front proxy must replace or safely process untrusted forwarded headers. A client-supplied spoofed address must not choose its limiter identity. Rate limits are layered and process-local, not a substitute for external reachability controls. Review the actual chain before putting a CDN/extra proxy in front.
Deployment sequence
- Select engine/Node/lockfile and prepare separate low-privilege paths.
- Install/test/build and inspect public output.
- Create intended first admin at selected protected runtime.
- Configure supervisor with actual executable, workdir, environment and write permissions.
- Configure DNS/TLS/private reachability and one chosen reverse proxy.
- Test on staging, including unauthenticated denial and negative permissions.
- Enable the service through the operator’s maintenance process.
- Verify actual reader and private editor after cutover.
See Linux team service, HTTPS proxy and Caddy and Apache examples. Those are external templates, not automated provisioning.
Acceptance matrix
| Area | Pass condition |
|---|---|
| Listener | Expected loopback/private binding, no bypass |
| Host | Intended hostname accepted; unknown value refused |
| HTTPS | Correct TLS/cookie behavior and renewal process |
| Sign-in | Synthetic valid/invalid/disabled/forced-change paths |
| Scope | Assigned-section save/create/delete; outside denied |
| Publication | Admin staged job and actual reader result |
| History | Legitimate records or surfaced storage warning |
| Recovery | Protected backup/restore and account recovery rehearsed |
Do not test destructive actions on valuable live source. Restart ends sessions/current job/log state, not saved accounts/content or separately served static reading. Users may need fresh login. Check disk/permissions without making accounts world-readable.
No production team service, certificate, DNS, proxy chain or private-runtime restore is claimed tested here. Local product regressions and browser checks are documented separately in Validation limits.