Files
member-console/internal/server/README.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

161 lines
4.9 KiB
Markdown

# Server Package
This package implements the HTTP server for the member-console application, including HTMX partial handlers.
## Overview
The server uses Go's standard `net/http` package with:
- Bootstrap 5 for styling
- HTMX 2.0 for dynamic content updates
- gorilla/csrf for CSRF protection
- scs (`alexedwards/scs`) for server-side session management (Valkey/Redis-backed)
## Architecture
The HTTP server handles several types of requests:
- **Static Files** (`/static/*`) - CSS, JS, images served from embedded assets
- **Page Handlers** (`/`) - Full HTML page rendering
- **Partial Handlers** (`/partials/*`) - HTMX partial HTML responses
- **Auth Handlers** (`/login`, `/logout`, `/callback`) - OAuth2/OIDC authentication
Partial handlers interact with Temporal workflows for async operations like site provisioning.
## Files
- `server.go` - Main server setup, middleware chain, route registration
- `member_products.go`, `member_invoices.go`, `workspace_partials.go`, `operator*.go` - HTMX partial handlers for this package's own features
Integration-owned handlers (e.g. FedWiki's member-facing API and partials)
live in their own tree under `internal/integrations/<key>/web` instead of
here — see `docs/building-an-integration.md` and
`internal/integrations/fedwiki/web` for the worked example. The patterns
below apply equally to handlers in either location.
## HTMX Integration
### Partial Handler Pattern
Partial handlers return HTML fragments (not full pages) that HTMX swaps into the DOM:
```go
// Handler returns HTML fragment
func (h *Handler) GetItems(w http.ResponseWriter, r *http.Request) {
// ... fetch data ...
h.Templates.Render(w, "items.html", data)
}
```
```html
<!-- Template returns partial HTML -->
<div class="table-responsive">
<table class="table">
{{ range .Sites }}
<tr>...</tr>
{{ end }}
</table>
</div>
```
### HTMX Triggers
Use `HX-Trigger` header to trigger events that refresh other parts of the page:
```go
// After successful operation, trigger refresh
w.Header().Set("HX-Trigger", `{"refreshSites": true}`)
```
```html
<!-- Element listens for the trigger -->
<div hx-get="/partials/items"
hx-trigger="load, refreshItems from:body">
</div>
```
### Cross-origin protection with HTMX
There is no CSRF token. `internal/middleware/csrf.go` wraps net/http's
`CrossOriginProtection`, which refuses a non-safe request whose
`Sec-Fetch-Site` (or, failing that, `Origin`) says it came from another
site. An htmx request from one of the console's own pages is same-origin
and passes with nothing added; a refusal is a 403 whose body names the
CSRF check, which `error-handler.js` reads to keep the person's input.
## Synchronous Workflow Execution
Unlike fire-and-forget patterns, this server waits for Temporal workflows to complete:
```go
func (h *Handler) CreateSite(w http.ResponseWriter, r *http.Request) {
// 1. Start the workflow
we, err := h.TemporalClient.ExecuteWorkflow(ctx, options, workflow, input)
// 2. Wait for completion (BLOCKING)
var result WorkflowOutput
err = we.Get(ctx, &result)
// 3. Render result based on workflow outcome
if !result.Success {
h.renderError(w, result.ErrorMessage)
return
}
h.renderSuccess(w, result)
}
```
**Benefits:**
- User sees accurate success/failure immediately
- Spinner shows during actual operation
- Error messages come from workflow
**Trade-offs:**
- Request blocks until workflow completes
- Long operations may timeout (mitigated by workflow retry handling)
## Route Organization
Routes are organized by feature using handler structs:
```go
// ItemPartialsHandler groups related routes
type ItemPartialsHandler struct { ... }
func (h *ItemPartialsHandler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /partials/items", h.GetItems)
mux.HandleFunc("POST /partials/items", h.CreateItem)
mux.HandleFunc("DELETE /partials/items/{id}", h.DeleteItem)
// ...
}
```
## Error Rendering
Errors are rendered back into the same partial/modal that initiated the request:
```go
func (h *Handler) renderCreateFormError(w http.ResponseWriter, userID int64, errMsg string) {
// Re-fetch form data
data := h.getCreateFormData(userID)
data.Error = errMsg
// Re-render the form partial with error displayed
h.Templates.Render(w, "create_form.html", data)
}
```
This keeps the user in context - they see the error in the same modal/form they were using.
## Middleware Stack
```go
handler = middleware.Logging(logger)(handler) // Request logging
handler = middleware.Recover(logger)(handler) // Panic recovery
handler = middleware.Compress()(handler) // Gzip compression
handler = middleware.Decompress()(handler) // Gzip decompression
handler = middleware.SecurityHeaders()(handler) // Security headers
handler = middleware.RequestID()(handler) // Request ID generation
handler = csrf.Protect(key, opts...)(handler) // CSRF protection
```