diff --git a/app/Http/Controllers/Auth/FacebookController.php b/app/Http/Controllers/Auth/FacebookController.php index 8142f6f8f..a153e81e5 100644 --- a/app/Http/Controllers/Auth/FacebookController.php +++ b/app/Http/Controllers/Auth/FacebookController.php @@ -51,6 +51,7 @@ public function connect(Request $request): Response Socialite::driver($this->driver) ->usingGraphVersion($this->graphVersion()) ->setScopes($this->scopes) + ->reRequest() ->redirect() ->getTargetUrl() ); diff --git a/app/Http/Controllers/Auth/InstagramFacebookController.php b/app/Http/Controllers/Auth/InstagramFacebookController.php index d20d32712..6f2a2af94 100644 --- a/app/Http/Controllers/Auth/InstagramFacebookController.php +++ b/app/Http/Controllers/Auth/InstagramFacebookController.php @@ -65,6 +65,7 @@ public function connect(Request $request): Response ->usingGraphVersion($this->graphVersion()) ->setScopes($this->scopes) ->redirectUrl(route('app.social.instagram-facebook.callback')) + ->reRequest() ->redirect() ->getTargetUrl(); diff --git a/app/Http/Controllers/Auth/SocialController.php b/app/Http/Controllers/Auth/SocialController.php index b63de0409..89fc62b3f 100644 --- a/app/Http/Controllers/Auth/SocialController.php +++ b/app/Http/Controllers/Auth/SocialController.php @@ -206,7 +206,7 @@ protected function filterConnectableIdentities( )->values()->all(); } - protected function redirectToProvider(Request $request, string $driver, array $scopes): SymfonyResponse + protected function redirectToProvider(Request $request, string $driver, array $scopes, array $parameters = []): SymfonyResponse { $workspace = $request->user()->currentWorkspace; @@ -215,6 +215,7 @@ protected function redirectToProvider(Request $request, string $driver, array $s return Inertia::location( Socialite::driver($driver) ->scopes($scopes) + ->with($parameters) ->redirect() ->getTargetUrl() ); diff --git a/app/Http/Controllers/Auth/TikTokController.php b/app/Http/Controllers/Auth/TikTokController.php index 88b678a57..8e39b59ac 100644 --- a/app/Http/Controllers/Auth/TikTokController.php +++ b/app/Http/Controllers/Auth/TikTokController.php @@ -37,7 +37,9 @@ public function connect(Request $request): Response $this->authorize('manageAccounts', $workspace); - return $this->redirectToProvider($request, $this->driver, $this->scopes); + return $this->redirectToProvider($request, $this->driver, $this->scopes, [ + 'disable_auto_auth' => 1, + ]); } public function callback(Request $request): InertiaResponse diff --git a/app/Http/Controllers/Auth/YouTubeController.php b/app/Http/Controllers/Auth/YouTubeController.php index 13a779541..2fcb1ed2f 100644 --- a/app/Http/Controllers/Auth/YouTubeController.php +++ b/app/Http/Controllers/Auth/YouTubeController.php @@ -119,7 +119,7 @@ private function redirectToGoogle(): Response ->scopes($this->scopes) ->with([ 'access_type' => 'offline', - 'prompt' => 'consent', + 'prompt' => 'select_account consent', 'include_granted_scopes' => 'true', ]) ->redirect() diff --git a/app/Socialite/InstagramProvider.php b/app/Socialite/InstagramProvider.php index 93c401ed1..faace2ed5 100644 --- a/app/Socialite/InstagramProvider.php +++ b/app/Socialite/InstagramProvider.php @@ -27,6 +27,7 @@ protected function getAuthUrl($state): string 'response_type' => 'code', 'scope' => implode(',', $this->getScopes()), 'state' => $state, + 'force_reauth' => 'true', ]); } diff --git a/tests/Feature/Social/DiscordControllerTest.php b/tests/Feature/Social/DiscordControllerTest.php index 7d9593c96..4dd0a3fd6 100644 --- a/tests/Feature/Social/DiscordControllerTest.php +++ b/tests/Feature/Social/DiscordControllerTest.php @@ -22,6 +22,7 @@ test('discord connect redirects to the oauth provider', function () { $driverMock = Mockery::mock(); $driverMock->shouldReceive('scopes')->andReturnSelf(); + $driverMock->shouldReceive('with')->with([])->andReturnSelf(); $driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([ 'getTargetUrl' => 'https://discord.com/api/oauth2/authorize?test=1', ])); diff --git a/tests/Feature/Social/FacebookControllerTest.php b/tests/Feature/Social/FacebookControllerTest.php index 8b3e627db..5c30e3122 100644 --- a/tests/Feature/Social/FacebookControllerTest.php +++ b/tests/Feature/Social/FacebookControllerTest.php @@ -27,6 +27,7 @@ $driverMock = Mockery::mock(); $driverMock->shouldReceive('usingGraphVersion')->andReturnSelf(); $driverMock->shouldReceive('setScopes')->andReturnSelf(); + $driverMock->shouldReceive('reRequest')->once()->andReturnSelf(); $driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([ 'getTargetUrl' => 'https://www.facebook.com/v25.0/dialog/oauth?test=1', ])); @@ -646,6 +647,7 @@ $driverMock = Mockery::mock(); $driverMock->shouldReceive('usingGraphVersion')->andReturnSelf(); $driverMock->shouldReceive('setScopes')->andReturnSelf(); + $driverMock->shouldReceive('reRequest')->andReturnSelf(); $driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([ 'getTargetUrl' => 'https://www.facebook.com/v25.0/dialog/oauth?test=1', ])); @@ -673,6 +675,7 @@ $driverMock = Mockery::mock(); $driverMock->shouldReceive('usingGraphVersion')->andReturnSelf(); $driverMock->shouldReceive('setScopes')->andReturnSelf(); + $driverMock->shouldReceive('reRequest')->andReturnSelf(); $driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([ 'getTargetUrl' => 'https://www.facebook.com/v25.0/dialog/oauth?test=1', ])); @@ -699,6 +702,7 @@ $driverMock = Mockery::mock(); $driverMock->shouldReceive('usingGraphVersion')->andReturnSelf(); $driverMock->shouldReceive('setScopes')->andReturnSelf(); + $driverMock->shouldReceive('reRequest')->andReturnSelf(); $driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([ 'getTargetUrl' => 'https://www.facebook.com/v25.0/dialog/oauth?test=1', ])); diff --git a/tests/Feature/Social/InstagramControllerTest.php b/tests/Feature/Social/InstagramControllerTest.php index 3aee79f0f..a2a6d48b2 100644 --- a/tests/Feature/Social/InstagramControllerTest.php +++ b/tests/Feature/Social/InstagramControllerTest.php @@ -21,6 +21,14 @@ $this->workspace->members()->attach($this->user->id, ['role' => Role::Member->value]); }); +test('instagram authorize url forces reauth so a second account is reachable', function () { + $response = $this->actingAs($this->user)->get(route('app.social.instagram.connect')); + + expect($response->headers->get('Location')) + ->toStartWith('https://www.instagram.com/oauth/authorize') + ->toContain('force_reauth=true'); +}); + test('instagram connect redirects to oauth provider', function () { $driverMock = Mockery::mock(); $driverMock->shouldReceive('scopes')->andReturnSelf(); @@ -32,6 +40,8 @@ ->with('instagram') ->andReturn($driverMock); + $this->withoutExceptionHandling(); + $response = $this->actingAs($this->user) ->withHeader('X-Inertia', 'true') ->get(route('app.social.instagram.connect')); diff --git a/tests/Feature/Social/InstagramFacebookControllerTest.php b/tests/Feature/Social/InstagramFacebookControllerTest.php index 56d8414e9..ad11d10b3 100644 --- a/tests/Feature/Social/InstagramFacebookControllerTest.php +++ b/tests/Feature/Social/InstagramFacebookControllerTest.php @@ -22,6 +22,29 @@ $this->workspace->members()->attach($this->user->id, ['role' => Role::Member->value]); }); +test('instagram-facebook connect redirects to oauth provider', function () { + $driverMock = Mockery::mock(); + $driverMock->shouldReceive('usingGraphVersion')->andReturnSelf(); + $driverMock->shouldReceive('setScopes')->andReturnSelf(); + $driverMock->shouldReceive('redirectUrl')->andReturnSelf(); + $driverMock->shouldReceive('reRequest')->once()->andReturnSelf(); + $driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([ + 'getTargetUrl' => 'https://www.facebook.com/v25.0/dialog/oauth?test=1', + ])); + + Socialite::shouldReceive('driver') + ->with('facebook') + ->andReturn($driverMock); + + $response = $this->actingAs($this->user) + ->withHeader('X-Inertia', 'true') + ->get(route('app.social.instagram-facebook.connect')); + + $response->assertStatus(409); // Inertia::location returns 409 with X-Inertia header + + expect(session('social_connect_workspace'))->toBe($this->workspace->id); +}); + test('instagram-facebook callback follows accounts pagination and shows picker', function () { session([ 'social_connect_workspace' => $this->workspace->id, diff --git a/tests/Feature/Social/PinterestControllerTest.php b/tests/Feature/Social/PinterestControllerTest.php index 60e0572fd..7d40926e7 100644 --- a/tests/Feature/Social/PinterestControllerTest.php +++ b/tests/Feature/Social/PinterestControllerTest.php @@ -22,6 +22,7 @@ test('pinterest connect redirects to oauth provider', function () { $driverMock = Mockery::mock(); $driverMock->shouldReceive('scopes')->andReturnSelf(); + $driverMock->shouldReceive('with')->with([])->andReturnSelf(); $driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([ 'getTargetUrl' => 'https://www.pinterest.com/oauth?test=1', ])); diff --git a/tests/Feature/Social/TikTokControllerTest.php b/tests/Feature/Social/TikTokControllerTest.php index c95218502..2e91da566 100644 --- a/tests/Feature/Social/TikTokControllerTest.php +++ b/tests/Feature/Social/TikTokControllerTest.php @@ -22,6 +22,7 @@ test('tiktok connect redirects to oauth provider', function () { $driverMock = Mockery::mock(); $driverMock->shouldReceive('scopes')->andReturnSelf(); + $driverMock->shouldReceive('with')->with(['disable_auto_auth' => 1])->once()->andReturnSelf(); $driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([ 'getTargetUrl' => 'https://www.tiktok.com/v2/auth/authorize?test=1', ])); @@ -193,6 +194,7 @@ $driverMock = Mockery::mock(); $driverMock->shouldReceive('scopes')->andReturnSelf(); + $driverMock->shouldReceive('with')->with(['disable_auto_auth' => 1])->andReturnSelf(); $driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([ 'getTargetUrl' => 'https://www.tiktok.com/v2/auth/authorize?test=1', ])); diff --git a/tests/Feature/Social/XControllerTest.php b/tests/Feature/Social/XControllerTest.php index 80fd09226..2c46fd224 100644 --- a/tests/Feature/Social/XControllerTest.php +++ b/tests/Feature/Social/XControllerTest.php @@ -22,6 +22,7 @@ test('x connect redirects to oauth provider', function () { $driverMock = Mockery::mock(); $driverMock->shouldReceive('scopes')->andReturnSelf(); + $driverMock->shouldReceive('with')->with([])->andReturnSelf(); $driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([ 'getTargetUrl' => 'https://twitter.com/i/oauth2/authorize?test=1', ])); diff --git a/tests/Feature/Social/YouTubeControllerTest.php b/tests/Feature/Social/YouTubeControllerTest.php index 0e7768b32..456ff201c 100644 --- a/tests/Feature/Social/YouTubeControllerTest.php +++ b/tests/Feature/Social/YouTubeControllerTest.php @@ -24,7 +24,11 @@ test('youtube connect redirects to oauth provider', function () { $driverMock = Mockery::mock(); $driverMock->shouldReceive('scopes')->andReturnSelf(); - $driverMock->shouldReceive('with')->andReturnSelf(); + $driverMock->shouldReceive('with')->with([ + 'access_type' => 'offline', + 'prompt' => 'select_account consent', + 'include_granted_scopes' => 'true', + ])->once()->andReturnSelf(); $driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([ 'getTargetUrl' => 'https://accounts.google.com/o/oauth2/v2/auth?test=1', ]));