69 lines
1.6 KiB
Go
69 lines
1.6 KiB
Go
|
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
|
||
|
}
|