Admit paginated HTML and hydrate CSRF tokens outside presentation keys

This commit is contained in:
GPT on behalf of DHH
2026-10-07 20:58:54 +02:00
parent ac73267b07
commit b220486c16
3 changed files with 33 additions and 3 deletions
+3 -2
View File
@@ -57,8 +57,9 @@ module CachedResponses
ActiveSupport::JSON.encode([
controller_path, request.fullpath, request.base_url, request.user_agent,
request.headers["Accept"], request.headers["Turbo-Frame"], I18n.locale,
Current.user.id, Current.session.token, session.to_hash,
request.env["action_controller.csrf_token"],
# Tokens are hydrated per request, including clients that replay an old
# cookie. Their raw CSRF secret does not select a presentation variant.
Current.user.id, Current.session.token, session.to_hash.except("_csrf_token"),
cookies.to_h.except("_campfire_session", "session_token")
])
end
+4 -1
View File
@@ -16,7 +16,10 @@ class MessagesController < ApplicationController
# Creator, body and boost edits can change HTML without touching messages.
# Masked CSRF tokens remain fresh while the presentation validator stays stable.
fresh_when etag: Digest::SHA256.hexdigest(csrf_neutral_body(body, "")), template: false
self.response_body = body unless performed?
unless performed?
response.content_type = "text/html"
self.response_body = body
end
else
head :no_content
end
+26
View File
@@ -6,6 +6,7 @@ class CachedResponsesTest < ActionDispatch::IntegrationTest
setup do
host! "once.campfire.test"
sign_in :david
@login_cookie = cookies["_campfire_session"]
@previous_forgery = ActionController::Base.allow_forgery_protection
ActionController::Base.allow_forgery_protection = true
@previous_cache = Rails.cache
@@ -38,6 +39,31 @@ class CachedResponsesTest < ActionDispatch::IntegrationTest
end
end
test "pagination caches the completed HTML rather than silently bypassing admission" do
get room_messages_url(@room)
assert_response :success
assert_equal "text/html", response.media_type
MessagesController.any_instance.expects(:find_paged_messages).never
get room_messages_url(@room)
assert_response :success
end
test "clients without a persisted CSRF session still reuse token-neutral HTML" do
cookies["_campfire_session"] = @login_cookie
get room_url(@room)
first = css_select('meta[name="csrf-token"]').first["content"]
cookies["_campfire_session"] = @login_cookie
ResponseCache.instance.expects(:write).never
get room_url(@room)
second = css_select('meta[name="csrf-token"]').first["content"]
assert_response :success
assert_not_equal first, second
assert_no_match /campfire-csrf-/, response.body
post room_messages_url(@room, format: :turbo_stream), params: {
authenticity_token: second, message: { body: "fresh replay token works", client_message_id: "cache-replay-token" } }
assert_response :success
end
test "cached tokens stay fresh and literal token-like text survives" do
get room_url(@room)
literal = css_select('meta[name="csrf-token"]').first["content"]