Files
summercms/modules/cabana/plugin_assets.go
Jakub Zych 8b1cb244de feat(10.1-01): serve controller JS/CSS and run registered toolbar actions
- boardwalk exports ContentType and SetSecurityHeaders
- pact.AdminClientAssets files are read and hashed at boot and served by exact
  key under {prefix}/assets/{vendor}/{plugin}/ with nosniff, CSP, CORP,
  no-cache and an ETag; a miss falls through to the SPA
- list and form schemas carry assets URLs with a ?v= hash
- toolbar.buttons resolves create, delete and registered actions after decode;
  toolbarActions is permission-filtered per admin
- POST .../toolbar/{action} behind requireAjax and action permissions
2026-09-28 23:41:17 +02:00

55 lines
1.8 KiB
Go

package cabana
import (
"bytes"
"net/http"
"path"
"time"
"git.golem15.com/golem15/summercms/modules/boardwalk"
)
// pluginAsset serves GET {prefix}/assets/{vendor}/{plugin}/{file...}: a
// controller's declared JS or CSS file, looked up by exact key in the map
// built at boot, so a plugin's embedded tree is never exposed wholesale and
// traversal matches no key. A miss falls through to the SPA handler, which
// serves the embedded dist assets and answers any other name with its 404.
//
// Plugin files are not content-hashed, so they are revalidated on every use
// (no-cache plus a sha256 ETag); the schema URLs carry a ?v= hash instead of
// the long-lived caching the SPA's hashed dist files get.
func (s *service) pluginAsset(w http.ResponseWriter, r *http.Request) {
var asset *pluginAsset
if s != nil && s.reg != nil {
asset = s.reg.assets[r.PathValue("vendor")+"/"+r.PathValue("plugin")+"/"+r.PathValue("file")]
}
if asset == nil {
s.serveSPA(w, r)
return
}
h := w.Header()
boardwalk.SetSecurityHeaders(h)
h.Set("Cross-Origin-Resource-Policy", "same-origin")
h.Set("Content-Type", asset.contentType)
h.Set("Cache-Control", "no-cache")
h.Set("ETag", asset.etag)
http.ServeContent(w, r, path.Base(asset.key), time.Time{}, bytes.NewReader(asset.body))
}
// controllerAssets are the same-origin URLs of a controller's plugin files,
// each with a ?v= content hash so a rebuilt binary never serves stale JS.
func (s *service) controllerAssets(cc *CompiledController) ControllerAssets {
out := ControllerAssets{Scripts: []string{}, Styles: []string{}}
if cc == nil {
return out
}
base := s.adminPrefix() + "/assets/"
for _, file := range cc.scripts {
out.Scripts = append(out.Scripts, base+file.key+"?v="+file.version)
}
for _, file := range cc.styles {
out.Styles = append(out.Styles, base+file.key+"?v="+file.version)
}
return out
}