- form-action 'self': injected markup cannot post a native form off-origin - frame-src 'none': no framed content; a sandboxed srcdoc preview is not fetched and still renders - header tests, README and admin-spa docs name the directives
75 lines
4.3 KiB
Markdown
75 lines
4.3 KiB
Markdown
# boardwalk
|
|
|
|
HTTP handler that serves the embedded admin SPA build under a configurable path prefix.
|
|
|
|
`import "git.golem15.com/golem15/summercms/modules/boardwalk"`
|
|
|
|
**Name:** The walkway along the beach front; it is the path admins take into the admin SPA.
|
|
|
|
## Overview
|
|
|
|
`boardwalk` embeds the compiled admin SPA (`dist/`, produced by `npm --prefix admin run build`) into the binary and serves it. The build is path-agnostic: `index.html` carries a placeholder token that the handler replaces once, at construction, with the prefix the admin is mounted under, so one build works at any backend URI. [cabana](../cabana/README.md) mounts it when it activates the admin routes. It stands in for the server-rendered backend layouts of WinterCMS, which the Go port replaces with a single-page app.
|
|
|
|
## Role in the framework
|
|
|
|
A small leaf with no framework dependencies; `cabana` is its only importer. It exists on its own because it owns the generated admin SPA bundle in dist/, which is rebuilt and drift-checked separately from Go code, and because its handler is easy to test against an in-memory file system. Application code normally does not import it: `cabana` mounts it under the backend prefix.
|
|
|
|
Use cases:
|
|
|
|
- Serve the admin SPA from the application binary with no separate frontend deployment.
|
|
- Move the admin to another path by changing the backend URI; the bundle is rewritten for the prefix at boot.
|
|
- Serve as the pattern to copy when a plugin embeds its own static site (the embedding and cache headers, not the admin-only security headers).
|
|
|
|
## Features
|
|
|
|
- Serves the embedded build under any prefix, rewriting relative asset URLs and the admin base meta in `index.html` for that prefix (`boardwalk.RewriteIndex`).
|
|
- Fails at boot when `index.html` lacks the `boardwalk.BaseToken` placeholder, which catches a stale or hand-edited build.
|
|
- Falls back to `index.html` for client-side routes and directories; missing files with an extension get a plain 404.
|
|
- Hands every request whose path under the prefix is `api` or starts with `api/` to a caller-supplied handler, so admin API misses stay JSON instead of returning the SPA.
|
|
- Long-lived immutable caching for hashed files under `assets/`, `no-cache` for other files and `no-store` for `index.html`.
|
|
- Security headers on every response: a restrictive Content-Security-Policy (`frame-ancestors 'none'`, `base-uri 'none'`, `object-src 'none'`, `script-src 'self'`, `form-action 'self'`, `frame-src 'none'`), frame denial, `nosniff`, a same-origin referrer policy and `noindex, nofollow`.
|
|
- Explicit content types for scripts, styles, fonts, SVG and JSON, with a MIME lookup fallback.
|
|
|
|
## Usage
|
|
|
|
```go
|
|
notFoundAPI := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusNotFound)
|
|
_, _ = w.Write([]byte(`{"error":"not found"}`))
|
|
})
|
|
|
|
spa, err := boardwalk.Handler("/backend", notFoundAPI)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mux.Handle("/backend/", spa)
|
|
```
|
|
|
|
## API reference
|
|
|
|
| Identifier | Description |
|
|
|------------|-------------|
|
|
| `boardwalk.Handler` | Builds the SPA handler for a prefix; the second argument answers unmatched `api/` paths. |
|
|
| `boardwalk.Dist` | Returns the embedded build as an `fs.FS` rooted at `dist/`. |
|
|
| `boardwalk.RewriteIndex` | Rewrites raw `index.html` bytes for a prefix; errors when the placeholder token is missing. |
|
|
| `boardwalk.BaseToken` | The placeholder in `dist/index.html` that is replaced by the prefix. |
|
|
| `boardwalk.ContentType` | The Content-Type served for a file name, with explicit UTF-8 JavaScript and CSS types. Shared with the plugin asset route in cabana. |
|
|
| `boardwalk.SetSecurityHeaders` | Sets the admin security headers (nosniff, referrer policy, no framing, the CSP with `script-src 'self'`, `form-action 'self'` and `frame-src 'none'`, noindex) on a response. |
|
|
|
|
## Dependencies
|
|
|
|
- SummerCMS modules: none.
|
|
- Third-party: none.
|
|
- Standard library: `bytes`, `embed`, `errors`, `fmt`, `html`, `io/fs`, `mime`, `net/http`, `path`, `strings`, `time`.
|
|
|
|
The embedded `dist/` tree is generated from the `admin/` Vite project, whose build writes to `modules/boardwalk/dist`.
|
|
|
|
## Testing
|
|
|
|
```sh
|
|
go test ./modules/boardwalk/...
|
|
```
|
|
|
|
The tests run the handler through `net/http/httptest` against the embedded build and in-memory file systems; they need no external services.
|