diff options
author | Alan Pearce | 2024-05-30 13:54:29 +0200 |
---|---|---|
committer | Alan Pearce | 2024-05-30 13:54:29 +0200 |
commit | 4698a97974ae82e7bd8592828c58294b222a58ff (patch) | |
tree | fc7d6357534efffc69301fb01c6e04143288dbec | |
parent | b02076363f979daa6ac313058eb140d1d67ce184 (diff) | |
download | searchix-4698a97974ae82e7bd8592828c58294b222a58ff.tar.lz searchix-4698a97974ae82e7bd8592828c58294b222a58ff.tar.zst searchix-4698a97974ae82e7bd8592828c58294b222a58ff.zip |
feat: enable sub-resource integrity for assets
-rw-r--r-- | frontend/assets.go | 95 | ||||
-rw-r--r-- | frontend/templates/blocks/search.gotmpl | 8 | ||||
-rw-r--r-- | frontend/templates/index.gotmpl | 9 | ||||
-rw-r--r-- | internal/server/mux.go | 4 |
4 files changed, 113 insertions, 3 deletions
diff --git a/frontend/assets.go b/frontend/assets.go new file mode 100644 index 0000000..a6a5e79 --- /dev/null +++ b/frontend/assets.go @@ -0,0 +1,95 @@ +package frontend + +import ( + "crypto/sha256" + "encoding/base64" + "io" + "io/fs" + "net/url" + + "github.com/pkg/errors" +) + +var Assets = AssetCollection{ + Scripts: make(map[string]Asset), + Stylesheets: make(map[string]Asset), +} + +type Asset struct { + URL string + Base64SHA256 string +} + +type AssetCollection struct { + Scripts map[string]Asset + Stylesheets map[string]Asset +} + +func hashFile(filename string) ([]byte, error) { + file, err := Files.Open(filename) + if err != nil { + return nil, errors.WithMessagef(err, "could not open file %s", filename) + } + sum := sha256.New() + defer file.Close() + if _, err := io.Copy(sum, file); err != nil { + return nil, errors.WithMessagef(err, "could not hash file %s", filename) + } + + return sum.Sum(nil), nil +} + +func newAsset(filename string, hash []byte) Asset { + u, err := url.JoinPath("/", filename) + if err != nil { + panic(err) + } + + return Asset{ + URL: u, + Base64SHA256: base64.StdEncoding.EncodeToString(hash), + } +} + +func hashScripts() error { + scripts, err := fs.Glob(Files, "static/**.js") + if err != nil { + return errors.WithMessage(err, "could not glob files") + } + for _, filename := range scripts { + hash, err := hashFile(filename) + if err != nil { + return err + } + Assets.Scripts[filename] = newAsset(filename, hash) + } + + return nil +} + +func hashStyles() error { + styles, err := fs.Glob(Files, "static/**.css") + if err != nil { + return errors.WithMessage(err, "could not glob files") + } + for _, filename := range styles { + hash, err := hashFile(filename) + if err != nil { + return err + } + Assets.Stylesheets[filename] = newAsset(filename, hash) + } + + return nil +} + +func init() { + err := hashScripts() + if err != nil { + panic(err) + } + err = hashStyles() + if err != nil { + panic(err) + } +} diff --git a/frontend/templates/blocks/search.gotmpl b/frontend/templates/blocks/search.gotmpl index 1be001a..9320376 100644 --- a/frontend/templates/blocks/search.gotmpl +++ b/frontend/templates/blocks/search.gotmpl @@ -22,5 +22,11 @@ {{- end }} {{- define "head" }} - <script src="/static/search.js" defer></script> + {{- with (index .Assets.Scripts "static/search.js") }} + <script + src="{{ .URL }}" + defer + integrity="sha256-{{ .Base64SHA256 }}" + ></script> + {{- end }} {{- end }} diff --git a/frontend/templates/index.gotmpl b/frontend/templates/index.gotmpl index ee37c0f..7732dc8 100644 --- a/frontend/templates/index.gotmpl +++ b/frontend/templates/index.gotmpl @@ -4,8 +4,13 @@ <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Searchix</title> - <link href="/static/base.css" rel="stylesheet" /> - <link href="/static/style.css" rel="stylesheet" /> + {{- range .Assets.Stylesheets }} + <link + href="{{ .URL }}" + rel="stylesheet" + integrity="sha256-{{ .Base64SHA256 }}" + /> + {{- end }} {{ block "head" . }} {{ end }} {{ .ExtraHeadHTML }} diff --git a/internal/server/mux.go b/internal/server/mux.go index ea4b70c..87e878a 100644 --- a/internal/server/mux.go +++ b/internal/server/mux.go @@ -43,6 +43,7 @@ type TemplateData struct { ExtraHeadHTML template.HTML Code int Message string + Assets frontend.AssetCollection } type ResultData struct { @@ -94,6 +95,7 @@ func NewMux( indexData := TemplateData{ ExtraHeadHTML: cfg.Web.ExtraHeadHTML, Sources: cfg.Importer.Sources, + Assets: frontend.Assets, } w.Header().Add("Cache-Control", "max-age=86400") err := templates["index"].Execute(w, indexData) @@ -142,6 +144,7 @@ func NewMux( ExtraHeadHTML: cfg.Web.ExtraHeadHTML, Source: *source, Sources: cfg.Importer.Sources, + Assets: frontend.Assets, }, ResultsPerPage: search.ResultsPerPage, Query: qs, @@ -205,6 +208,7 @@ func NewMux( Sources: cfg.Importer.Sources, Source: *source, SourceResult: sourceResult, + Assets: frontend.Assets, }) if err != nil { errorHandler(w, r, err.Error(), http.StatusInternalServerError) |