mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-05-20 03:49:53 +00:00
remove SetAllowedFormAction and explicitly set csp header
This commit is contained in:
@@ -23,42 +23,35 @@ func GetCSPNonce(c *gin.Context) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetAllowedFormAction sets the allowed form-action for the CSP header.
|
||||
// This is used for OAuth2 response_mode=form_post to allow form submissions to the client's redirect URI.
|
||||
func SetAllowedFormAction(c *gin.Context, uri string) {
|
||||
c.Set("csp_allowed_form_action", uri)
|
||||
}
|
||||
|
||||
func (m *CspMiddleware) Add() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
nonce := generateNonce()
|
||||
c.Set("csp_nonce", nonce)
|
||||
c.Writer.Header().Set("Content-Security-Policy", BuildCSP(nonce))
|
||||
|
||||
// Let the handler run first, then set CSP header with the final context values
|
||||
c.Next()
|
||||
|
||||
// Determine if there is an EXTRA target beyond 'self'
|
||||
// This is set by handlers (e.g., OIDC authorize) after validating the redirect URI
|
||||
var extraAction string
|
||||
if v, ok := c.Get("csp_allowed_form_action"); ok {
|
||||
extraAction, _ = v.(string)
|
||||
}
|
||||
|
||||
// 'self' is kept in the string; extraAction is just appended
|
||||
csp := "default-src 'self'; " +
|
||||
"base-uri 'self'; " +
|
||||
"object-src 'none'; " +
|
||||
"frame-ancestors 'none'; " +
|
||||
"form-action 'self' " + extraAction + "; " +
|
||||
"img-src * blob:;" +
|
||||
"font-src 'self'; " +
|
||||
"style-src 'self' 'unsafe-inline'; " +
|
||||
"script-src 'self' 'nonce-" + nonce + "'"
|
||||
|
||||
c.Writer.Header().Set("Content-Security-Policy", csp)
|
||||
}
|
||||
}
|
||||
|
||||
func BuildCSP(nonce string, formActionExtra ...string) string {
|
||||
formAction := "'self'"
|
||||
for _, extra := range formActionExtra {
|
||||
if extra != "" {
|
||||
formAction += " " + extra
|
||||
}
|
||||
}
|
||||
|
||||
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'; " +
|
||||
"script-src 'self' 'nonce-" + nonce + "'"
|
||||
}
|
||||
|
||||
func generateNonce() string {
|
||||
b := make([]byte, 16)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
|
||||
24
backend/internal/middleware/csp_middleware_test.go
Normal file
24
backend/internal/middleware/csp_middleware_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBuildCSP(t *testing.T) {
|
||||
t.Run("uses self form action by default", func(t *testing.T) {
|
||||
csp := BuildCSP("test-nonce")
|
||||
|
||||
assert.Contains(t, csp, "form-action 'self';")
|
||||
assert.Contains(t, csp, "script-src 'self' 'nonce-test-nonce'")
|
||||
})
|
||||
|
||||
t.Run("adds validated form action targets", func(t *testing.T) {
|
||||
csp := BuildCSP("test-nonce", "https://example.com/callback")
|
||||
|
||||
assert.Contains(t, csp, "form-action 'self' https://example.com/callback;")
|
||||
assert.Equal(t, 1, strings.Count(csp, "form-action"))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user