This document captures the project requirements and design goals gathered during the Development session. It can be used to recreate the ftd FastCGI form collector and admin dashboard.
- Provide a FastCGI listener for HTML form submissions and admin traffic forwarded by a front-end web server (e.g., nginx or OpenBSD httpd), defaulting to a Unix socket but allowing an optional TCP port via
-tcp <port>. Default FastCGI paths are/formfor submissions and/form/adminfor admin; both are configurable viaFORM_PATHandADMIN_PREFIX. - Persist each submission to PostgreSQL with:
- Request metadata: timestamp, client IP, user agent, referrer.
- JSONB payload containing all form fields (dynamic schema) plus upload metadata when present (
_upload_original_filenameand_upload_stored_filename).
- Offer an admin dashboard (also served via FastCGI) with authentication, pagination, and status workflow:
- Status values:
new,in_progress,complete,archived. - Main dashboard hides
archivedentries; a dedicated Archived view lists them with pagination. - The Active dashboard provides a bulk "Archive completed" control to sweep all rows currently marked
completeintoarchived. - Each submission includes a reviewer comment field that can be edited alongside status updates and renders in a muted/read-only style when archived.
- Admin login is password protected; password hashes are stored in the database (bcrypt).
- The schema seeds a default admin (
admin/change-me); the dashboard shows a red warning while the default hash is in use and exposes a CSRF-protected password change form so operators can rotate credentials without dropping their current session. - Sessions are signed and refreshed on activity (to avoid logging out open tabs); if
SESSION_SECRETis unset, an ephemeral random key is generated per process (sessions invalidate on restart). Cookies default toSecure,HttpOnly, andSameSite=StrictunlessSESSION_COOKIE_INSECUREis set for local HTTP testing. - Admin POSTs (login and status updates) require CSRF tokens validated against secure cookies.
- Status values:
- Include a sample HTML submitter form and a backend admin HTML experience that formats JSON payloads into readable field/value pairs plus prettified JSON.
- Assume form inputs are hostile: use prepared statements/parameterized queries for all SQL operations and validate status values before mutating rows; clamp comment length to avoid oversized updates.
- Validate pagination/status query parameters; reject invalid statuses.
- Render user content through Go html/template to ensure escaping; JSON is prettified for readability.
- Capture and log errors without leaking sensitive detail to clients.
- Send defensive admin response headers (CSP with self-only sources, frame denial, no sniffing, strict referrer/permissions policies, cache disabling) to reduce injection and clickjacking exposure.
- Front-end web server should terminate TLS and forward
X-Forwarded-Forso real client IPs are recorded. - Throttle hostile traffic: block any IP that submits more than 4 forms within a one-minute window for 24 hours, and pause all submissions for 5 minutes when a sudden burst of 30+ distinct IPs appears in that same window. Return appropriate HTTP status codes (429 for IP blocks, 503 for global pauses) with
Retry-Afterwhere applicable. - Cap FastCGI submission size to 64KB (plus any configured upload allowance) and reject requests with more than 200 fields to mitigate data flooding; uploads are rejected entirely unless explicitly enabled.
- Allow at most one uploaded file per submission when enabled; enforce the configured byte cap, write it inside the chroot with a unique name
ftd.TIMESTAMP,<8-hex>, and record the stored name and any client-supplied original name in the JSON payload under_upload_stored_filenameand_upload_original_filename. Persist the stored path (orFailed Upload (<status code>)) infile_pathfor dashboard display.
- If the process starts as
root, open the PostgreSQL socket and FastCGI socket first, then chroot to the_ftduser’s home and drop privileges to that UID/GID. - On OpenBSD, apply pledge(2): initial broad promise, tighten after DB connection and listener setup, then run with a reduced pledge set.
- Keep a single persistent DB connection so it survives the chrooted environment.
- Initialize the PostgreSQL schema (from
schema.sql) before the first run; the service does not apply migrations at runtime an d will exit on startup if required tables are missing. - Tables:
submissions:id(PK),submitted_at(defaultNOW()),ip_address,user_agent,referer,status(check constraint on allowed values),file_path(optional stored upload path or failure label),comment(optional reviewer notes),form_data(JSONB with field data and upload metadata when a file is present).admin_users:username(PK),password_hash,created_at.submission_blocks:scope(iporglobal),identifier,blocked_untilto track per-IP and global throttling windows.
- Indexes: at least on
submissions.statusandsubmissions.submitted_at DESCto speed filtering and pagination.
- Dashboard presents two tabs (Active, Archived) with modern styling.
- Cards show submission metadata, extracted field list, reviewer comment textarea (muted/read-only when archived), prettified JSON, and status dropdown for updates.
- Active tab includes a bulk action to archive all
completesubmissions. - Filtering on Active view by status (
new,in_progress,complete). - Logout endpoint clears the session.
- Environment variables:
DATABASE_URL(required)FASTCGI_SOCKET(default/var/www/run/ftd.sock)FORM_PATH(default/form)ADMIN_PREFIX(default/form/admin)SESSION_SECRET(optional; auto-generated if omitted)SESSION_COOKIE_INSECURE(optional; disableSecurecookies for local HTTP testing only)MAX_UPLOAD_MB(default0; set to allow one uploaded file per submission up to the given megabytes)
- An OpenBSD
rc.dhelper (rc.d/ftd) is provided; it sources/etc/ftd.envfor environment variables, backgrounds underrcctl, and runs the daemon in the foreground so rc.subr can supervise it. - Place the FastCGI socket where the web server can reach it and restrict permissions accordingly. Create it before chroot/drop-privilege when starting as
root. If running with-tcp, configure your front-end to FastCGI proxy to the chosen port instead of the Unix socket. - Example front-ends: nginx or OpenBSD httpd using
fastcgi_pass unix:/var/www/run/ftd.sock;for both/formand/form/admin/. - Initialize the PostgreSQL schema (from
schema.sql) before the first run; the service does not apply migrations at runtime.
go build ./...should succeed (requires access togithub.com/lib/pqandgolang.org/x/crypto).- Admin/UI HTML and CSS should render without external assets beyond system fonts.