learnlytics-go/templ/examples/content-security-policy/main.go

69 lines
1.6 KiB
Go
Raw Normal View History

2025-03-20 12:35:13 +01:00
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
}