feat: replace Bootstrap with custom CSS and add profile page
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
- Add self-hosted style.css replacing Bootstrap CDN dependency - Add profile.html with tabbed view (account info, emails/aliases, API token) - Refactor login handler to pass structured data to template instead of building HTML strings - Add brand-name and brand-logo flags/env vars for UI customization - Update CSP to allow brand logo domain and remove CDN references - Update all templates to pass template vars to header/footer and use new CSS classes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
910dd7b47a
commit
99def55e80
12 changed files with 796 additions and 107 deletions
32
static.go
32
static.go
|
|
@ -5,6 +5,8 @@ import (
|
|||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func securityHeaders(next http.Handler) http.Handler {
|
||||
|
|
@ -12,7 +14,16 @@ func securityHeaders(next http.Handler) http.Handler {
|
|||
w.Header().Set("X-Frame-Options", "DENY")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' https://stackpath.bootstrapcdn.com; style-src 'self' 'sha256-W6z8OR2iqpPyNGe72eRXH58H75H3UVJDuwHoKA6pX98=' https://stackpath.bootstrapcdn.com; img-src 'self'; font-src https://stackpath.bootstrapcdn.com; worker-src blob:")
|
||||
|
||||
imgSrc := "'self' data:"
|
||||
if strings.HasPrefix(brandLogo, "http://") || strings.HasPrefix(brandLogo, "https://") {
|
||||
if u, err := url.Parse(brandLogo); err == nil {
|
||||
imgSrc += " " + u.Scheme + "://" + u.Host
|
||||
}
|
||||
}
|
||||
csp := "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline'; style-src 'self' 'sha256-W6z8OR2iqpPyNGe72eRXH58H75H3UVJDuwHoKA6pX98='; img-src " + imgSrc + "; worker-src blob:"
|
||||
w.Header().Set("Content-Security-Policy", csp)
|
||||
|
||||
if !devMode {
|
||||
w.Header().Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
|
||||
}
|
||||
|
|
@ -23,7 +34,26 @@ func securityHeaders(next http.Handler) http.Handler {
|
|||
//go:embed all:static
|
||||
var assets embed.FS
|
||||
|
||||
func serveStyleCSS(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := assets.ReadFile("static/style.css")
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/css; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func displayTmpl(w http.ResponseWriter, page string, vars map[string]any) {
|
||||
if vars == nil {
|
||||
vars = map[string]any{}
|
||||
}
|
||||
vars["brand_name"] = brandName
|
||||
if brandLogo != "" {
|
||||
vars["brand_logo"] = brandLogo
|
||||
}
|
||||
|
||||
data, err := assets.ReadFile("static/" + page)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to find %q: %s", page, err.Error())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue