package main import ( "crypto/rand" "fmt" "math/big" "net/http" "os" "log/slog" "github.com/a-h/templ" ) func main() { log := slog.New(slog.NewJSONHandler(os.Stderr, nil)) // Create HTTP routes. mux := http.NewServeMux() mux.Handle("/", templ.Handler(template())) // Wrap the router with CSP middleware to apply the CSP nonce to templ scripts. withCSPMiddleware := NewCSPMiddleware(log, mux) log.Info("Listening...", slog.String("addr", "127.0.0.1:7001")) if err := http.ListenAndServe("127.0.0.1:7001", withCSPMiddleware); err != nil { log.Error("failed to start server", slog.Any("error", err)) } } func NewCSPMiddleware(log *slog.Logger, next http.Handler) *CSPMiddleware { return &CSPMiddleware{ Log: log, Next: next, Size: 28, } } type CSPMiddleware struct { Log *slog.Logger Next http.Handler Size int } func (m *CSPMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { nonce, err := m.generateNonce() if err != nil { m.Log.Error("failed to generate nonce", slog.Any("error", err)) http.Error(w, "Internal server error", http.StatusInternalServerError) return } ctx := templ.WithNonce(r.Context(), nonce) w.Header().Add("Content-Security-Policy", fmt.Sprintf("script-src 'nonce-%s'", nonce)) m.Next.ServeHTTP(w, r.WithContext(ctx)) } func (m *CSPMiddleware) generateNonce() (string, error) { const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" ret := make([]byte, m.Size) for i := 0; i < m.Size; i++ { num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) if err != nil { return "", err } ret[i] = letters[num.Int64()] } return string(ret), nil }