about summary refs log tree commit diff stats
path: root/frontend
diff options
context:
space:
mode:
authorAlan Pearce2024-05-30 13:54:29 +0200
committerAlan Pearce2024-05-30 13:54:29 +0200
commit4698a97974ae82e7bd8592828c58294b222a58ff (patch)
treefc7d6357534efffc69301fb01c6e04143288dbec /frontend
parentb02076363f979daa6ac313058eb140d1d67ce184 (diff)
downloadsearchix-4698a97974ae82e7bd8592828c58294b222a58ff.tar.lz
searchix-4698a97974ae82e7bd8592828c58294b222a58ff.tar.zst
searchix-4698a97974ae82e7bd8592828c58294b222a58ff.zip
feat: enable sub-resource integrity for assets
Diffstat (limited to 'frontend')
-rw-r--r--frontend/assets.go95
-rw-r--r--frontend/templates/blocks/search.gotmpl8
-rw-r--r--frontend/templates/index.gotmpl9
3 files changed, 109 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 }}