- 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
55 lines
1.8 KiB
Go
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
|
|
}
|