discourse/spec/integrity/middleware_order_spec.rb
Alan Guo Xiang Tan ef90ef7d8d
FIX: Anon pageview session id reused across cached responses (#39879)
The `discourse-track-view-session-id` meta tag is generated inline when HTML is rendered, so when `Middleware::AnonymousCache` serves a cached response every anonymous visitor receives the same session id baked into the cached body. Browser pageview analytics that rely on this value to distinguish visitors collapse all cache-served visits into a single session.

This commit introduces `Middleware::TrackViewSessionIdInjector`, a Rack middleware that performs a late-stage substitution on the outbound response body. The `ApplicationHelper#discourse_pageview_tracking_meta_tags` helper now emits a placeholder string into the meta tag and registers it on a response header. The middleware reads that header, deletes it, and replaces the placeholder in the body with a fresh `SecureRandom.alphanumeric(Middleware::RequestTracker::MAX_SESSION_ID_LENGTH)` per request, so each visitor gets a unique session id even when the underlying HTML came from cache.
2026-05-12 10:13:05 +08:00

59 lines
2 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe "Middleware order" do
let(:expected_middlewares) do
[
BlockRequestsMiddleware,
TestMultisiteMiddleware,
Middleware::ProcessingRequest,
Middleware::OverloadProtections,
ActionDispatch::RemoteIp,
Middleware::RequestTracker,
MessageBus::Rack::Middleware,
Rack::Sendfile,
ActionDispatch::Static,
Propshaft::Server,
ActionDispatch::Executor,
Rack::MethodOverride,
Middleware::EnforceHostname,
ActionDispatch::RequestId,
SilenceLogger,
Middleware::DefaultHeaders,
ActionDispatch::ShowExceptions,
ActionDispatch::DebugExceptions,
ActionDispatch::Callbacks,
ActionDispatch::Cookies,
ActionDispatch::Session::DiscourseCookieStore,
Discourse::Cors,
ActionDispatch::Flash,
RspecErrorTracker,
Middleware::TrackViewSessionIdInjector,
Middleware::CspScriptNonceInjector,
Middleware::AnonymousCache,
ContentSecurityPolicy::Middleware,
ActionDispatch::PermissionsPolicy::Middleware,
Rack::Head,
Rack::ConditionalGet,
Rack::TempfileReaper,
Middleware::CrawlerHooks,
Middleware::OmniauthBypassMiddleware,
]
end
let(:actual_middlewares) { Rails.configuration.middleware.middlewares }
let(:remote_ip_index) { actual_middlewares.index(ActionDispatch::RemoteIp) }
let(:request_tracker_index) { actual_middlewares.index(Middleware::RequestTracker) }
it "has the correct order of middlewares" do
expect(actual_middlewares).to eq(expected_middlewares)
end
it "ensures that ActionDispatch::RemoteIp comes before Middleware::RequestTracker" do
expect(remote_ip_index).to be < request_tracker_index
end
it "ensures that Middleware::DefaultHeaders comes before ActionDispatch::ShowExceptions" do
default_headers_index = actual_middlewares.index(Middleware::DefaultHeaders)
show_exceptions_index = actual_middlewares.index(ActionDispatch::ShowExceptions)
expect(default_headers_index).to be < show_exceptions_index
end
end