diff --git a/resources/views/oauth/prompt.blade.php b/resources/views/oauth/prompt.blade.php
new file mode 100644
index 00000000..371e7b28
--- /dev/null
+++ b/resources/views/oauth/prompt.blade.php
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+ Laravel
+
+
+
+
+
+
+
+
+
+
+
+
+ Confirm connection of your {{ \JoelButcher\Socialstream\Providers::name($provider) }} account.
+
+
+
+
+
+
+
diff --git a/routes/web.php b/routes/web.php
new file mode 100644
index 00000000..dcc6b596
--- /dev/null
+++ b/routes/web.php
@@ -0,0 +1,12 @@
+ config('socialstream.middleware', ['web'])], function () {
+ Route::get('/oauth/{provider}/callback/prompt', [OAuthController::class, 'prompt'])->name('oauth.callback.prompt');
+ Route::post('/oauth/{provider}/callback/confirm', [OAuthController::class, 'confirm'])->name(
+ 'oauth.callback.confirm'
+ );
+});
diff --git a/src/Actions/AuthenticateOAuthCallback.php b/src/Actions/AuthenticateOAuthCallback.php
index c874a288..f44cd0c4 100644
--- a/src/Actions/AuthenticateOAuthCallback.php
+++ b/src/Actions/AuthenticateOAuthCallback.php
@@ -65,7 +65,10 @@ public function authenticate(string $provider, ProviderUser $providerAccount): S
{
// If the user is authenticated, link the provider to the authenticated user.
if ($user = auth()->user()) {
- return $this->link($user, $provider, $providerAccount);
+ // cache the provider account for 10 mins whilst the user is redirected to the confirmation screen.
+ cache()->put("socialstream.{$user->id}:$provider.provider", $providerAccount, ttl: new \DateInterval('PT10M'));
+
+ return redirect()->route('oauth.callback.prompt', $provider);
}
// Check if the user has an existing OAuth account.
@@ -198,16 +201,11 @@ function ($request, $next) {
]));
}
- /**
- * Attempt to link the provider to the authenticated user.
- *
- * Attempt to link the provider with the authenticated user.
- */
- private function link(Authenticatable $user, string $provider, ProviderUser $providerAccount): SocialstreamResponse
+ public function link(Authenticatable $user, string $provider, ProviderUser $providerAccount): SocialstreamResponse
{
$account = Socialstream::findConnectedAccountForProviderAndId($provider, $providerAccount->getId());
- if ($account && $user?->id !== $account->user_id) {
+ if ($account && $user->id !== $account->user_id) {
event(new OAuthProviderLinkFailed($user, $provider, $account, $providerAccount));
$this->flashError(
diff --git a/src/Http/Controllers/OAuthController.php b/src/Http/Controllers/OAuthController.php
index 17d5b9ea..8414a78f 100644
--- a/src/Http/Controllers/OAuthController.php
+++ b/src/Http/Controllers/OAuthController.php
@@ -2,17 +2,25 @@
namespace JoelButcher\Socialstream\Http\Controllers;
+use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
+use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Session;
+use Illuminate\Support\MessageBag;
+use Illuminate\Support\ViewErrorBag;
use JoelButcher\Socialstream\Contracts\AuthenticatesOAuthCallback;
use JoelButcher\Socialstream\Contracts\GeneratesProviderRedirect;
use JoelButcher\Socialstream\Contracts\HandlesInvalidState;
use JoelButcher\Socialstream\Contracts\HandlesOAuthCallbackErrors;
use JoelButcher\Socialstream\Contracts\ResolvesSocialiteUsers;
use JoelButcher\Socialstream\Contracts\SocialstreamResponse;
+use JoelButcher\Socialstream\Events\OAuthProviderLinkFailed;
+use JoelButcher\Socialstream\Http\Responses\OAuthProviderLinkFailedResponse;
+use JoelButcher\Socialstream\Providers;
+use Laravel\Jetstream\Jetstream;
use Laravel\Socialite\Two\InvalidStateException;
use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirectResponse;
@@ -59,4 +67,58 @@ public function callback(Request $request, string $provider): SocialstreamRespon
return $this->authenticator->authenticate($provider, $providerAccount);
}
+
+ /**
+ * Show the oauth confirmation page.
+ */
+ public function prompt(string $provider): View
+ {
+ return view('socialstream::oauth.prompt', [
+ 'provider' => $provider,
+ ]);
+ }
+
+ public function confirm(string $provider): SocialstreamResponse|RedirectResponse
+ {
+ $user = auth()->user();
+ $providerAccount = cache()->pull("socialstream.{$user->id}:$provider.provider");
+
+ $result = request()->input('result');
+
+ if ($result === 'deny') {
+ event(new OAuthProviderLinkFailed($user, $provider, null, $providerAccount));
+
+ $this->flashError(
+ __('Failed to link :provider account. User denied request.', ['provider' => Providers::name($provider)]),
+ );
+
+ return app(OAuthProviderLinkFailedResponse::class);
+ }
+
+ if (!$providerAccount) {
+ throw new \DomainException(
+ message: 'Could not retrieve social provider information.'
+ );
+ }
+
+ return $this->authenticator->link($user, $provider, $providerAccount);
+ }
+
+ private function flashError(string $error): void
+ {
+ if (auth()->check()) {
+ if (class_exists(Jetstream::class)) {
+ Session::flash('flash.banner', $error);
+ Session::flash('flash.bannerStyle', 'danger');
+
+ return;
+ }
+ }
+
+ Session::flash('errors', (new ViewErrorBag())->put(
+ 'default',
+ new MessageBag(['socialstream' => $error])
+ ));
+ }
+
}
diff --git a/src/SocialstreamServiceProvider.php b/src/SocialstreamServiceProvider.php
index 1ecb4826..0a9ea12c 100644
--- a/src/SocialstreamServiceProvider.php
+++ b/src/SocialstreamServiceProvider.php
@@ -141,6 +141,7 @@ private function configureRoutes(): void
'domain' => config('socialstream.domain'),
'prefix' => config('socialstream.prefix', config('socialstream.path')),
], function () {
+ $this->loadRoutesFrom(path: __DIR__.'/../routes/web.php');
$this->loadRoutesFrom(path: __DIR__.'/../routes/'.config('jetstream.stack', 'livewire').'.php');
});
}