diff --git a/app/Services/Social/ConnectionVerifier.php b/app/Services/Social/ConnectionVerifier.php index 54ed6ca77..e7a273721 100644 --- a/app/Services/Social/ConnectionVerifier.php +++ b/app/Services/Social/ConnectionVerifier.php @@ -340,8 +340,11 @@ private function refreshBlueskyToken(SocialAccount $account): void $client = TokenRefreshClient::for(Platform::Bluesky); try { + // refreshSession must be POSTed with NO body: `post()` without data + // still sends an empty JSON body, and bsky.social now rejects it + // with "A request body was provided when none was expected". $response = $client->send(fn () => $this->refreshHttp()->withToken($account->refresh_token) - ->post("{$service}/xrpc/".BlueskyLexicon::REFRESH_SESSION)); + ->send('POST', "{$service}/xrpc/".BlueskyLexicon::REFRESH_SESSION)); $this->updateBlueskySession($account, $response); diff --git a/tests/Feature/Services/Social/BlueskyRefreshSessionTest.php b/tests/Feature/Services/Social/BlueskyRefreshSessionTest.php new file mode 100644 index 000000000..0de0b9665 --- /dev/null +++ b/tests/Feature/Services/Social/BlueskyRefreshSessionTest.php @@ -0,0 +1,41 @@ +create(); + $workspace = Workspace::factory()->create(['user_id' => $user->id]); + $account = SocialAccount::factory()->bluesky()->create([ + 'workspace_id' => $workspace->id, + 'platform_user_id' => 'did:plc:refresh123', + 'refresh_token' => 'refresh-jwt', + ]); + + $service = config('trypost.platforms.bluesky.default_service'); + + Http::fake([ + "{$service}/xrpc/com.atproto.server.refreshSession" => Http::response([ + 'did' => 'did:plc:refresh123', + 'handle' => 'testuser.bsky.social', + 'accessJwt' => 'new-access', + 'refreshJwt' => 'new-refresh', + ], 200), + ]); + + app(ConnectionVerifier::class)->refreshToken($account); + + // bsky.social rejects refreshSession when any body is present — even the + // empty JSON object/array a data-less post() would send. + Http::assertSent(function ($request) { + return str_contains($request->url(), 'refreshSession') + && $request->body() === ''; + }); + + expect($account->fresh()->access_token)->toBe('new-access'); +});