package utils import ( "crypto/rand" "encoding/base64" "strings" "github.com/gin-gonic/gin" ) const cspNonceContextKey = "csp_nonce" // GetCSPNonce returns the CSP nonce generated for this request, if any. func GetCSPNonce(c *gin.Context) string { if v, ok := c.Get(cspNonceContextKey); ok { if s, ok := v.(string); ok { return s } } return "" } // SetCSPNonce stores a per-request CSP nonce so handlers can reference it in // Content-Security-Policy headers they emit themselves. func SetCSPNonce(c *gin.Context, nonce string) { c.Set(cspNonceContextKey, nonce) } func BuildCSP(nonce string) string { return buildCSP(nonce, nil, nil) } // BuildFormPostCSP builds the Content-Security-Policy for an OIDC response_mode=form_post page func BuildFormPostCSP(nonce, redirectURI, scriptHash string) string { return buildCSP(nonce, []string{redirectURI}, []string{scriptHash}) } func buildCSP(nonce string, formActionExtra, scriptSrcExtra []string) string { formAction := "'self'" scriptSrc := "script-src 'self'" if nonce != "" { scriptSrc += " 'nonce-" + nonce + "'" } for _, extra := range scriptSrcExtra { if extra != "" { scriptSrc += " " + extra } } if len(formActionExtra) > 0 { b := strings.Builder{} for _, extra := range formActionExtra { if extra != "" { b.WriteByte(' ') b.WriteString(extra) } } formAction += b.String() } return "default-src 'self'; " + "base-uri 'self'; " + "object-src 'none'; " + "frame-ancestors 'none'; " + "form-action " + formAction + "; " + "img-src * blob:;" + "font-src 'self'; " + "style-src 'self' 'unsafe-inline'; " + scriptSrc } // GenerateCSPNonce returns a random base64 nonce for use in a CSP header. func GenerateCSPNonce() string { b := make([]byte, 16) if _, err := rand.Read(b); err != nil { return "" // if generation fails, return empty; policy will omit nonce } return base64.RawURLEncoding.EncodeToString(b) }