Skip to content

Commit 23127fc

Browse files
tonysmtaylorotwell
andcommitted
Passes both first request and first response cookies to internal request
This should handle any encrypted request as well. Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent 13a40c9 commit 23127fc

File tree

2 files changed

+66
-4
lines changed

2 files changed

+66
-4
lines changed

src/Http/Middleware/TurboMiddleware.php

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Illuminate\Support\Facades\Route;
1313
use Illuminate\Support\Str;
1414
use Illuminate\Validation\ValidationException;
15+
use Symfony\Component\HttpFoundation\Cookie;
1516
use Tonysm\TurboLaravel\Facades\Turbo as TurboFacade;
1617
use Tonysm\TurboLaravel\Turbo;
1718

@@ -20,6 +21,13 @@ class TurboMiddleware
2021
/** @var \Tonysm\TurboLaravel\Http\Middleware\RouteRedirectGuesser */
2122
private $redirectGuesser;
2223

24+
/**
25+
* Encrypted cookies to be added to the internal requests following redirects.
26+
*
27+
* @var array
28+
*/
29+
private array $encryptedCookies;
30+
2331
public function __construct(RouteRedirectGuesser $redirectGuesser)
2432
{
2533
$this->redirectGuesser = $redirectGuesser;
@@ -32,6 +40,8 @@ public function __construct(RouteRedirectGuesser $redirectGuesser)
3240
*/
3341
public function handle($request, Closure $next)
3442
{
43+
$this->encryptedCookies = $request->cookies->all();
44+
3545
if ($this->turboNativeVisit($request)) {
3646
TurboFacade::setVisitingFromTurboNative();
3747
}
@@ -63,14 +73,24 @@ private function turboResponse($response, Request $request)
6373
return $response;
6474
}
6575

76+
// We get the response's encrypted cookies and merge them with the
77+
// encrypted cookies of the first request to make sure that are
78+
// sub-sequent request will use the most up-to-date values.
79+
80+
$responseCookies = collect($response->headers->getCookies())
81+
->mapWithKeys(fn (Cookie $cookie) => [$cookie->getName() => $cookie->getValue()])
82+
->all();
83+
84+
$this->encryptedCookies = array_replace_recursive($this->encryptedCookies, $responseCookies);
85+
6686
// When throwing a ValidationException and the app uses named routes convention, we can guess
6787
// the form route for the current endpoint, make an internal request there, and return the
6888
// response body with the form over a 422 status code, which is better for Turbo Native.
6989

7090
if ($response->exception instanceof ValidationException && ($formRedirectUrl = $this->getRedirectUrl($request, $response))) {
7191
$response->setTargetUrl($formRedirectUrl);
7292

73-
return tap($this->handleRedirectInternally($this->kernel(), $request, $response), function () use ($request) {
93+
return tap($this->handleRedirectInternally($request, $response), function () use ($request) {
7494
App::instance('request', $request);
7595
Facade::clearResolvedInstance('request');
7696
});
@@ -94,28 +114,34 @@ private function kernel(): Kernel
94114
}
95115

96116
/**
97-
* @param Kernel $kernel
98117
* @param Request $request
99118
* @param Response $response
100119
*
101120
* @return Response
102121
*/
103-
private function handleRedirectInternally($kernel, $request, $response)
122+
private function handleRedirectInternally($request, $response)
104123
{
124+
$kernel = $this->kernel();
125+
105126
do {
106127
$response = $kernel->handle(
107128
$request = $this->createRequestFrom($response->headers->get('Location'), $request)
108129
);
109130
} while ($response->isRedirect());
110131

111-
return $response->setStatusCode(422);
132+
if ($response->isOk()) {
133+
$response->setStatusCode(422);
134+
}
135+
136+
return $response;
112137
}
113138

114139
private function createRequestFrom(string $url, Request $baseRequest)
115140
{
116141
$request = Request::create($url, 'GET');
117142

118143
$request->headers->replace($baseRequest->headers->all());
144+
$request->cookies->replace($this->encryptedCookies);
119145

120146
return $request;
121147
}

tests/Http/Middleware/TurboMiddlewareTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,4 +217,40 @@ public function only_guess_route_on_resource_routes()
217217
->assertRedirect(route('app.login'))
218218
->assertStatus(303);
219219
}
220+
221+
public function usesRoutesWhichExceptCookies()
222+
{
223+
Route::get('posts/create', function () {
224+
$firstRequestCookie = request()->cookie('my-cookie', 'no-cookie');
225+
226+
$responseCookie = request()->cookie('response-cookie', 'no-cookie');
227+
228+
return response(sprintf('Request Cookie: %s; Response Cookie: %s', $firstRequestCookie, $responseCookie));
229+
})->name('posts.create');
230+
231+
Route::post('posts', function () {
232+
$exception = ValidationException::withMessages([
233+
'title' => ['Title cannot be blank.'],
234+
]);
235+
236+
$exception->response = redirect()->to('/')->withCookie('response-cookie', 'response-cookie-value');
237+
238+
throw $exception;
239+
})->name('posts.store')->middleware(TurboMiddleware::class);
240+
}
241+
242+
/**
243+
* @test
244+
* @define-route usesRoutesWhichExceptCookies
245+
*/
246+
public function passes_the_request_cookies_to_the_internal_request()
247+
{
248+
$this->withHeaders([
249+
'Accept' => sprintf('%s, text/html, application/xhtml+xml', Turbo::TURBO_STREAM_FORMAT),
250+
])
251+
->withUnencryptedCookie('my-cookie', 'test-value')
252+
->post(route('posts.store'))
253+
->assertSee('Request Cookie: test-value; Response Cookie: response-cookie-value')
254+
->assertStatus(422);
255+
}
220256
}

0 commit comments

Comments
 (0)