chore(csrf): remove gorilla/csrf BE-12948 (#2618)

This commit is contained in:
andres-portainer
2026-05-11 19:41:26 -03:00
committed by GitHub
parent 540c9ba6d5
commit 2bb2b78e82
14 changed files with 28 additions and 676 deletions
+1 -1
View File
@@ -108,7 +108,7 @@ dev-extension: build-server build-client ## Run the extension in development mod
##@ Docs
.PHONY: docs-build docs-validate docs-clean docs-validate-clean
docs-build: init-dist ## Build docs
go mod download -x
go mod download
cd api && $(SWAG) init -o "../dist/docs" -ot "yaml" -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 -p pascalcase --markdownFiles ./
docs-validate: docs-build ## Validate docs
+1 -125
View File
@@ -1,30 +1,13 @@
package csrf
import (
"crypto/rand"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/pkg/featureflags"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
gcsrf "github.com/gorilla/csrf"
"github.com/rs/zerolog/log"
"github.com/urfave/negroni"
)
const csrfSkipHeader = "X-CSRF-Token-Skip"
// SkipCSRFToken signals that the X-CSRF-Token header should not be sent in the response.
// Deprecated: only meaningful when the "legacy-csrf" feature flag is enabled.
func SkipCSRFToken(w http.ResponseWriter) {
w.Header().Set(csrfSkipHeader, "1")
}
func WithProtect(handler http.Handler, trustedOrigins []string) (http.Handler, error) {
// DOCKER_EXTENSION=1 is set in build/docker-extension/docker-compose.yml
isDockerDesktopExtension := false
@@ -32,10 +15,6 @@ func WithProtect(handler http.Handler, trustedOrigins []string) (http.Handler, e
isDockerDesktopExtension = true
}
if featureflags.IsEnabled("legacy-csrf") {
return withLegacyProtect(handler, trustedOrigins, isDockerDesktopExtension)
}
cop := http.NewCrossOriginProtection()
for _, origin := range trustedOrigins {
if err := cop.AddTrustedOrigin(origin); err != nil {
@@ -58,14 +37,7 @@ func WithProtect(handler http.Handler, trustedOrigins []string) (http.Handler, e
protected := cop.Handler(handler)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
skip, err := security.ShouldSkipCSRFCheck(r, isDockerDesktopExtension)
if err != nil {
httperror.WriteError(w, http.StatusForbidden, err.Error(), err)
return
}
if skip {
if isDockerDesktopExtension {
handler.ServeHTTP(w, r)
return
@@ -74,99 +46,3 @@ func WithProtect(handler http.Handler, trustedOrigins []string) (http.Handler, e
protected.ServeHTTP(w, r)
}), nil
}
// Deprecated: use WithProtect without the "legacy-csrf" feature flag instead.
func withLegacyProtect(handler http.Handler, trustedOrigins []string, isDockerDesktopExtension bool) (http.Handler, error) {
handler = withLegacySendCSRFToken(handler)
token := make([]byte, 32)
if _, err := rand.Read(token); err != nil {
return nil, fmt.Errorf("failed to generate CSRF token: %w", err)
}
// gorilla/csrf compares referer.Host against trusted origin entries, so it
// needs bare host[:port] values rather than full scheme://host[:port] origins.
legacyOrigins := make([]string, len(trustedOrigins))
for i, origin := range trustedOrigins {
parsed, err := url.Parse(origin)
if err != nil {
return nil, fmt.Errorf("failed to parse trusted origin %q: %w", origin, err)
}
legacyOrigins[i] = parsed.Host
}
handler = gcsrf.Protect(
token,
gcsrf.Path("/"),
gcsrf.Secure(false),
gcsrf.TrustedOrigins(legacyOrigins),
gcsrf.ErrorHandler(withLegacyErrorHandler(trustedOrigins)),
)(handler)
return withLegacySkipCSRF(handler, isDockerDesktopExtension), nil
}
// Deprecated: use WithProtect without the "legacy-csrf" feature flag instead.
func withLegacySendCSRFToken(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sw := negroni.NewResponseWriter(w)
sw.Before(func(sw negroni.ResponseWriter) {
if len(sw.Header().Get(csrfSkipHeader)) > 0 {
sw.Header().Del(csrfSkipHeader)
return
}
if statusCode := sw.Status(); statusCode >= 200 && statusCode < 300 {
sw.Header().Set("X-CSRF-Token", gcsrf.Token(r))
}
})
handler.ServeHTTP(sw, r)
})
}
// Deprecated: use WithProtect without the "legacy-csrf" feature flag instead.
func withLegacySkipCSRF(handler http.Handler, isDockerDesktopExtension bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
skip, err := security.ShouldSkipCSRFCheck(r, isDockerDesktopExtension)
if err != nil {
httperror.WriteError(w, http.StatusForbidden, err.Error(), err)
return
}
if skip {
r = gcsrf.UnsafeSkipCheck(r)
}
handler.ServeHTTP(w, r)
})
}
// Deprecated: use WithProtect without the "legacy-csrf" feature flag instead.
func withLegacyErrorHandler(trustedOrigins []string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := gcsrf.FailureReason(r)
if errors.Is(err, gcsrf.ErrBadOrigin) || errors.Is(err, gcsrf.ErrBadReferer) || errors.Is(err, gcsrf.ErrNoReferer) {
log.Error().Err(err).
Str("request_url", r.URL.String()).
Str("host", r.Host).
Str("x_forwarded_proto", r.Header.Get("X-Forwarded-Proto")).
Str("forwarded", r.Header.Get("Forwarded")).
Str("origin", r.Header.Get("Origin")).
Str("referer", r.Header.Get("Referer")).
Strs("trusted_origins", trustedOrigins).
Msg("Failed to validate Origin or Referer")
}
http.Error(
w,
http.StatusText(http.StatusForbidden)+" - "+err.Error(),
http.StatusForbidden,
)
})
}
-130
View File
@@ -154,51 +154,6 @@ func TestWithProtect_allowsPostFromTrustedOrigin(t *testing.T) {
require.Equal(t, http.StatusOK, rr.Code)
}
func TestWithProtect_skipsCsrfForApiKey(t *testing.T) {
t.Parallel()
handler, err := WithProtect(okHandler, nil)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodPost, "/", nil)
req.Header.Set("Sec-Fetch-Site", "cross-site")
req.Header.Set("X-API-KEY", "my-api-key")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
}
func TestWithProtect_skipsCsrfForBearerToken(t *testing.T) {
t.Parallel()
handler, err := WithProtect(okHandler, nil)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodPost, "/", nil)
req.Header.Set("Sec-Fetch-Site", "cross-site")
req.Header.Set("Authorization", "Bearer some-token")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
}
func TestWithProtect_forbidsBothApiKeyAndBearerToken(t *testing.T) {
t.Parallel()
handler, err := WithProtect(okHandler, nil)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodPost, "/", nil)
req.Header.Set("X-API-KEY", "my-api-key")
req.Header.Set("Authorization", "Bearer some-token")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusForbidden, rr.Code)
}
func TestWithProtect_enforcesCsrfForCookieAuth(t *testing.T) {
t.Parallel()
@@ -213,88 +168,3 @@ func TestWithProtect_enforcesCsrfForCookieAuth(t *testing.T) {
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusForbidden, rr.Code)
}
func TestWithLegacyProtect_noError_noOrigins(t *testing.T) {
t.Parallel()
_, err := withLegacyProtect(okHandler, nil, false)
require.NoError(t, err)
}
func TestWithLegacyProtect_noError_schemeHostOrigin(t *testing.T) {
t.Parallel()
_, err := withLegacyProtect(okHandler, []string{"https://example.com"}, false)
require.NoError(t, err)
}
func TestWithLegacyProtect_noError_schemeHostPortOrigin(t *testing.T) {
t.Parallel()
_, err := withLegacyProtect(okHandler, []string{"https://example.com:3000"}, false)
require.NoError(t, err)
}
func TestWithLegacyProtect_noError_multipleOrigins(t *testing.T) {
t.Parallel()
_, err := withLegacyProtect(okHandler, []string{"https://example.com", "http://internal.example.com:8080"}, false)
require.NoError(t, err)
}
func TestWithLegacyProtect_safeMethodsAlwaysAllowed(t *testing.T) {
t.Parallel()
handler, err := withLegacyProtect(okHandler, nil, false)
require.NoError(t, err)
for _, method := range []string{http.MethodGet, http.MethodHead, http.MethodOptions} {
req := httptest.NewRequest(method, "/", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code, "method %s should be allowed", method)
}
}
func TestWithLegacyProtect_blocksPostWithoutToken(t *testing.T) {
t.Parallel()
handler, err := withLegacyProtect(okHandler, nil, false)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodPost, "/", nil)
req.AddCookie(&http.Cookie{Name: portainer.AuthCookieKey, Value: "some-token"})
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusForbidden, rr.Code)
}
func TestWithLegacyProtect_skipsCsrfForApiKey(t *testing.T) {
t.Parallel()
handler, err := withLegacyProtect(okHandler, nil, false)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodPost, "/", nil)
req.Header.Set("X-API-KEY", "my-api-key")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
}
func TestWithLegacyProtect_skipsCsrfForBearerToken(t *testing.T) {
t.Parallel()
handler, err := withLegacyProtect(okHandler, nil, false)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodPost, "/", nil)
req.Header.Set("Authorization", "Bearer some-token")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
}
@@ -1,75 +0,0 @@
package middlewares
import (
"net/http"
"slices"
"strings"
"github.com/gorilla/csrf"
)
var (
// Idempotent (safe) methods as defined by RFC7231 section 4.2.2.
safeMethods = []string{"GET", "HEAD", "OPTIONS", "TRACE"}
)
type plainTextHTTPRequestHandler struct {
next http.Handler
}
// parseForwardedHeaderProto parses the Forwarded header and extracts the protocol.
// The Forwarded header format supports:
// - Single proxy: Forwarded: by=<identifier>;for=<identifier>;host=<host>;proto=<http|https>
// - Multiple proxies: Forwarded: for=192.0.2.43, for=198.51.100.17
// We take the first (leftmost) entry as it represents the original client
func parseForwardedHeaderProto(forwarded string) string {
if forwarded == "" {
return ""
}
// Parse the first part (leftmost proxy, closest to original client)
firstPart, _, _ := strings.Cut(forwarded, ",")
firstPart = strings.TrimSpace(firstPart)
// Split by semicolon to get key-value pairs within this proxy entry
// Format: key=value;key=value;key=value
for pair := range strings.SplitSeq(firstPart, ";") {
// Split by equals sign to separate key and value
key, value, found := strings.Cut(pair, "=")
if !found {
continue
}
if strings.EqualFold(strings.TrimSpace(key), "proto") {
return strings.Trim(strings.TrimSpace(value), `"'`)
}
}
return ""
}
// isHTTPSRequest checks if the original request was made over HTTPS
// by examining both X-Forwarded-Proto and Forwarded headers
func isHTTPSRequest(r *http.Request) bool {
return strings.EqualFold(r.Header.Get("X-Forwarded-Proto"), "https") ||
strings.EqualFold(parseForwardedHeaderProto(r.Header.Get("Forwarded")), "https")
}
func (h *plainTextHTTPRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if slices.Contains(safeMethods, r.Method) {
h.next.ServeHTTP(w, r)
return
}
req := r
// If original request was HTTPS (via proxy), keep CSRF checks.
if !isHTTPSRequest(r) {
req = csrf.PlaintextHTTPRequest(r)
}
h.next.ServeHTTP(w, req)
}
func PlaintextHTTPRequest(next http.Handler) http.Handler {
return &plainTextHTTPRequestHandler{next: next}
}
@@ -1,174 +0,0 @@
package middlewares
import (
"testing"
)
var tests = []struct {
name string
forwarded string
expected string
}{
{
name: "empty header",
forwarded: "",
expected: "",
},
{
name: "single proxy with proto=https",
forwarded: "proto=https",
expected: "https",
},
{
name: "single proxy with proto=http",
forwarded: "proto=http",
expected: "http",
},
{
name: "single proxy with multiple directives",
forwarded: "for=192.0.2.60;proto=https;by=203.0.113.43",
expected: "https",
},
{
name: "single proxy with proto in middle",
forwarded: "for=192.0.2.60;proto=https;host=example.com",
expected: "https",
},
{
name: "single proxy with proto at end",
forwarded: "for=192.0.2.60;host=example.com;proto=https",
expected: "https",
},
{
name: "multiple proxies - takes first",
forwarded: "proto=https, proto=http",
expected: "https",
},
{
name: "multiple proxies with complex format",
forwarded: "for=192.0.2.43;proto=https, for=198.51.100.17;proto=http",
expected: "https",
},
{
name: "multiple proxies with for directive only",
forwarded: "for=192.0.2.43, for=198.51.100.17",
expected: "",
},
{
name: "multiple proxies with proto only in second",
forwarded: "for=192.0.2.43, proto=https",
expected: "",
},
{
name: "multiple proxies with proto only in first",
forwarded: "proto=https, for=198.51.100.17",
expected: "https",
},
{
name: "quoted protocol value",
forwarded: "proto=\"https\"",
expected: "https",
},
{
name: "single quoted protocol value",
forwarded: "proto='https'",
expected: "https",
},
{
name: "mixed case protocol",
forwarded: "proto=HTTPS",
expected: "HTTPS",
},
{
name: "no proto directive",
forwarded: "for=192.0.2.60;by=203.0.113.43",
expected: "",
},
{
name: "empty proto value",
forwarded: "proto=",
expected: "",
},
{
name: "whitespace around values",
forwarded: " proto = https ",
expected: "https",
},
{
name: "whitespace around semicolons",
forwarded: "for=192.0.2.60 ; proto=https ; by=203.0.113.43",
expected: "https",
},
{
name: "whitespace around commas",
forwarded: "proto=https , proto=http",
expected: "https",
},
{
name: "IPv6 address in for directive",
forwarded: "for=\"[2001:db8:cafe::17]:4711\";proto=https",
expected: "https",
},
{
name: "complex multiple proxies with IPv6",
forwarded: "for=192.0.2.43;proto=https, for=\"[2001:db8:cafe::17]\";proto=http",
expected: "https",
},
{
name: "obfuscated identifiers",
forwarded: "for=_mdn;proto=https",
expected: "https",
},
{
name: "unknown identifier",
forwarded: "for=unknown;proto=https",
expected: "https",
},
{
name: "malformed key-value pair",
forwarded: "proto",
expected: "",
},
{
name: "malformed key-value pair with equals",
forwarded: "proto=",
expected: "",
},
{
name: "multiple equals signs",
forwarded: "proto=https=extra",
expected: "https=extra",
},
{
name: "mixed case directive name",
forwarded: "PROTO=https",
expected: "https",
},
{
name: "mixed case directive name with spaces",
forwarded: " Proto = https ",
expected: "https",
},
}
func TestParseForwardedHeaderProto(t *testing.T) {
t.Parallel()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := parseForwardedHeaderProto(tt.forwarded)
if result != tt.expected {
t.Errorf("parseForwardedHeader(%q) = %q, want %q", tt.forwarded, result, tt.expected)
}
})
}
}
func FuzzParseForwardedHeaderProto(f *testing.F) {
for _, t := range tests {
f.Add(t.forwarded)
}
f.Fuzz(func(t *testing.T, forwarded string) {
parseForwardedHeaderProto(forwarded)
})
}
+8 -37
View File
@@ -307,6 +307,14 @@ func (bouncer *RequestBouncer) mwIsTeamLeader(next http.Handler) http.Handler {
// A result of a first succeeded token lookup would be used for the authentication.
func (bouncer *RequestBouncer) mwAuthenticateFirst(tokenLookups []tokenLookup, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, hasAPIKey := extractAPIKey(r)
_, hasBearerToken := extractBearerToken(r)
if hasAPIKey && hasBearerToken {
httperror.WriteError(w, http.StatusUnauthorized, "API key and auth header are not allowed at the same time", httperrors.ErrUnauthorized)
return
}
var token *portainer.TokenData
for _, lookup := range tokenLookups {
@@ -570,40 +578,3 @@ func (bouncer *RequestBouncer) EdgeComputeOperation(next http.Handler) http.Hand
next.ServeHTTP(w, r)
})
}
// ShouldSkipCSRFCheck checks if the CSRF check should be skipped
//
// It returns true if the request has no cookie token and has either (but not both):
// - an api key header
// - an auth header
// if it has both headers, an error is returned
//
// we allow CSRF check to be skipped for the following reasons:
// - public routes
// - kubectl - a bearer token is needed, and no csrf token can be sent
// - api token
// - docker desktop extension
func ShouldSkipCSRFCheck(r *http.Request, isDockerDesktopExtension bool) (bool, error) {
if isDockerDesktopExtension {
return true, nil
}
cookie, _ := r.Cookie(portainer.AuthCookieKey)
hasCookie := cookie != nil && cookie.Value != ""
if hasCookie {
return false, nil
}
apiKey := r.Header.Get(apiKeyHeader)
hasApiKey := apiKey != ""
authHeader := r.Header.Get(jwtTokenHeader)
hasAuthHeader := authHeader != ""
if hasApiKey && hasAuthHeader {
return false, errors.New("api key and auth header are not allowed at the same time")
}
return true, nil
}
+16 -72
View File
@@ -399,81 +399,25 @@ func Test_apiKeyLookup(t *testing.T) {
})
}
func Test_ShouldSkipCSRFCheck(t *testing.T) {
func Test_mwAuthenticateFirst_rejectsBothAPIKeyAndBearerToken(t *testing.T) {
t.Parallel()
tt := []struct {
name string
cookieValue string
apiKey string
authHeader string
isDockerDesktopExtension bool
expectedResult bool
expectedError bool
}{
{
name: "Should return false (not skip) when cookie is present",
cookieValue: "test-cookie",
isDockerDesktopExtension: false,
},
{
name: "Should return true (skip) when cookie is present and docker desktop extension is true",
cookieValue: "test-cookie",
isDockerDesktopExtension: true,
expectedResult: true,
},
{
name: "Should return true (skip) when cookie is not present",
cookieValue: "",
isDockerDesktopExtension: false,
expectedResult: true,
},
{
name: "Should return true (skip) when api key is present",
cookieValue: "",
apiKey: "test-api-key",
isDockerDesktopExtension: false,
expectedResult: true,
},
{
name: "Should return true (skip) when auth header is present",
cookieValue: "",
authHeader: "test-auth-header",
isDockerDesktopExtension: false,
expectedResult: true,
},
{
name: "Should return false (not skip) and error when both api key and auth header are present",
cookieValue: "",
apiKey: "test-api-key",
authHeader: "test-auth-header",
isDockerDesktopExtension: false,
expectedError: true,
},
}
_, store := datastore.MustNewTestStore(t, true, true)
for _, test := range tt {
t.Run(test.name, func(t *testing.T) {
is := assert.New(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
if test.cookieValue != "" {
req.AddCookie(&http.Cookie{Name: portainer.AuthCookieKey, Value: test.cookieValue})
}
if test.apiKey != "" {
req.Header.Set(apiKeyHeader, test.apiKey)
}
if test.authHeader != "" {
req.Header.Set(jwtTokenHeader, test.authHeader)
}
jwtService, err := jwt.NewService("1h", store)
require.NoError(t, err)
result, err := ShouldSkipCSRFCheck(req, test.isDockerDesktopExtension)
is.Equal(test.expectedResult, result)
if test.expectedError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
apiKeyService := apikey.NewAPIKeyService(nil, nil)
bouncer := NewRequestBouncer(t.Context(), store, jwtService, apiKeyService)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set(apiKeyHeader, "test-api-key")
req.Header.Set(jwtTokenHeader, "Bearer test-token")
rr := httptest.NewRecorder()
h := bouncer.mwAuthenticateFirst(nil, testHandler200)
h.ServeHTTP(rr, req)
require.Equal(t, http.StatusUnauthorized, rr.Code)
}
func TestJWTRevocation(t *testing.T) {
+1 -1
View File
@@ -346,7 +346,7 @@ func (server *Server) Start(ctx context.Context) error {
log.Info().Str("bind_address", server.BindAddress).Msg("starting HTTP server")
httpServer := &http.Server{
Addr: server.BindAddress,
Handler: middlewares.PlaintextHTTPRequest(handler),
Handler: handler,
ErrorLog: errorLogger,
}
+1 -1
View File
@@ -1977,7 +1977,7 @@ const (
)
// List of supported features
var SupportedFeatureFlags = []featureflags.Feature{"hsts", "csp", "legacy-csrf"}
var SupportedFeatureFlags = []featureflags.Feature{"hsts", "csp"}
const (
_ AuthenticationMethod = iota
-2
View File
@@ -33,7 +33,5 @@ export function onStartupAngular($rootScope, $state, cfpLoadingBar, $transitions
if (type && hasNoContentType) {
jqXhr.setRequestHeader('Content-Type', 'application/json');
}
const csrfCookie = window.cookieStore.get('_gorilla_csrf');
jqXhr.setRequestHeader('X-CSRF-Token', csrfCookie);
});
}
-6
View File
@@ -1,5 +1,4 @@
import { agentInterceptor } from '@/react/portainer/services/axios/axios';
import { csrfInterceptor, csrfTokenReaderInterceptorAngular } from './portainer/services/csrf';
import { dispatchCacheRefreshEventIfNeeded } from './portainer/services/http-request.helper';
/* @ngInject */
@@ -26,11 +25,6 @@ export function configApp($urlRouterProvider, $httpProvider, localStorageService
request: agentInterceptor,
}));
$httpProvider.interceptors.push(() => ({
response: csrfTokenReaderInterceptorAngular,
request: csrfInterceptor,
}));
$uibTooltipProvider.setTriggers({
mouseenter: 'mouseleave',
click: 'click',
-40
View File
@@ -1,40 +0,0 @@
import { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { CacheAxiosResponse } from 'axios-cache-interceptor';
import { IHttpResponse } from 'angular';
import axios from './axios/axios';
axios.interceptors.response.use(csrfTokenReaderInterceptor);
axios.interceptors.request.use(csrfInterceptor);
let csrfToken: string | null = null;
export function csrfTokenReaderInterceptor(
config: CacheAxiosResponse | AxiosResponse
) {
const csrfTokenHeader = config.headers['x-csrf-token'];
if (csrfTokenHeader) {
csrfToken = csrfTokenHeader;
}
return config;
}
export function csrfTokenReaderInterceptorAngular(
config: IHttpResponse<unknown>
) {
const csrfTokenHeader = config.headers('x-csrf-token');
if (csrfTokenHeader) {
csrfToken = csrfTokenHeader;
}
return config;
}
export function csrfInterceptor(config: InternalAxiosRequestConfig) {
if (!csrfToken) {
return config;
}
const newConfig = { ...config };
newConfig.headers['X-CSRF-Token'] = csrfToken;
return newConfig;
}
-4
View File
@@ -31,7 +31,6 @@ require (
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/gorilla/csrf v1.7.3
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
github.com/hashicorp/golang-lru v0.6.0
@@ -55,7 +54,6 @@ require (
github.com/segmentio/encoding v0.5.3
github.com/sirupsen/logrus v1.9.4
github.com/stretchr/testify v1.11.1
github.com/urfave/negroni v1.0.0
github.com/viney-shih/go-lock v1.1.1
go.etcd.io/bbolt v1.4.3
go.podman.io/image/v5 v5.37.0
@@ -75,8 +73,6 @@ require (
oras.land/oras-go/v2 v2.6.0
)
require github.com/gorilla/securecookie v1.1.2 // indirect
require (
cloud.google.com/go/auth v0.18.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
-8
View File
@@ -462,8 +462,6 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef h1:xpF9fUHpoIrrjX24DURVKiwHcFpw19ndIs+FwTSMbno=
github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
@@ -478,15 +476,11 @@ github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
github.com/gophercloud/gophercloud/v2 v2.10.0 h1:NRadC0aHNvy4iMoFXj5AFiPmut/Sj3hAPAo9B59VMGc=
github.com/gophercloud/gophercloud/v2 v2.10.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk=
github.com/gorilla/csrf v1.7.3 h1:BHWt6FTLZAb2HtWT5KDBf6qgpZzvtbp9QWDRKZMXJC0=
github.com/gorilla/csrf v1.7.3/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
@@ -953,8 +947,6 @@ github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Q
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/viney-shih/go-lock v1.1.1 h1:SwzDPPAiHpcwGCr5k8xD15d2gQSo8d4roRYd7TDV2eI=