From 2080e89308be0064ccefc86aa9fa53659b64c767 Mon Sep 17 00:00:00 2001 From: Joel Butcher Date: Tue, 24 Dec 2024 09:42:39 +0000 Subject: [PATCH] fix: confirm account link --- config/socialstream.php | 1 + resources/views/oauth/prompt.blade.php | 47 +++++++++++ src/Actions/AuthenticateOAuthCallback.php | 2 +- src/Filament/web.php | 12 +++ src/HasProfilePhoto.php | 89 -------------------- src/Http/Controllers/OAuthController.php | 61 ++++++++++++++ stubs/jetstream/app/Models/User.php | 2 +- stubs/jetstream/app/Models/UserWithTeams.php | 2 +- 8 files changed, 124 insertions(+), 92 deletions(-) create mode 100644 resources/views/oauth/prompt.blade.php create mode 100644 src/Filament/web.php delete mode 100644 src/HasProfilePhoto.php diff --git a/config/socialstream.php b/config/socialstream.php index 95ae448a..c6277cde 100644 --- a/config/socialstream.php +++ b/config/socialstream.php @@ -6,6 +6,7 @@ return [ 'middleware' => ['web'], 'prompt' => 'Or Login Via', + 'confirmation-prompt' => null, 'providers' => [ // Providers::github(), ], diff --git a/resources/views/oauth/prompt.blade.php b/resources/views/oauth/prompt.blade.php new file mode 100644 index 00000000..89511e1c --- /dev/null +++ b/resources/views/oauth/prompt.blade.php @@ -0,0 +1,47 @@ + + + + + + + Laravel + + + + + + + + + +
+
+

+ Confirm connection of your {{ \JoelButcher\Socialstream\Providers::name($provider) }} account. +

+ +
+ @csrf +

+ @if (config('socialstream.confirmation-prompt')) + {{ config('socialstream.confirmation-prompt') }} + @else + To ensure you are the account owner of this {{ \JoelButcher\Socialstream\Providers::name($provider) }} account, + please confirm or deny the request below to link this provider to your account. + @endif +

+ +
+ + + +
+
+
+
+ + diff --git a/src/Actions/AuthenticateOAuthCallback.php b/src/Actions/AuthenticateOAuthCallback.php index f0831e90..99f36828 100644 --- a/src/Actions/AuthenticateOAuthCallback.php +++ b/src/Actions/AuthenticateOAuthCallback.php @@ -136,7 +136,7 @@ protected function login(Authenticatable $user, mixed $account, string $provider /** * Attempt to link the provider to the authenticated user. */ - private function linkProvider(Authenticatable $user, string $provider, ProviderUser $providerAccount): SocialstreamResponse + public function linkProvider(Authenticatable $user, string $provider, ProviderUser $providerAccount): SocialstreamResponse { $account = $this->findAccount($provider, $providerAccount); diff --git a/src/Filament/web.php b/src/Filament/web.php new file mode 100644 index 00000000..dcc6b596 --- /dev/null +++ b/src/Filament/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/HasProfilePhoto.php b/src/HasProfilePhoto.php deleted file mode 100644 index 8fcc88d3..00000000 --- a/src/HasProfilePhoto.php +++ /dev/null @@ -1,89 +0,0 @@ -profile_photo_path, function ($previous) use ($photo, $storagePath) { - $this->forceFill([ - 'profile_photo_path' => $photo->storePublicly( - $storagePath, ['disk' => $this->profilePhotoDisk()] - ), - ])->save(); - - if ($previous) { - Storage::disk($this->profilePhotoDisk())->delete($previous); - } - }); - } - - /** - * Delete the user's profile photo. - * - * @return void - */ - public function deleteProfilePhoto() - { - if (! Features::managesProfilePhotos()) { - return; - } - - if (is_null($this->profile_photo_path)) { - return; - } - - Storage::disk($this->profilePhotoDisk())->delete($this->profile_photo_path); - - $this->forceFill([ - 'profile_photo_path' => null, - ])->save(); - } - - /** - * Get the URL to the user's profile photo. - */ - public function profilePhotoUrl(): Attribute - { - return Attribute::get(function () { - return $this->profile_photo_path - ? Storage::disk($this->profilePhotoDisk())->url($this->profile_photo_path) - : $this->defaultProfilePhotoUrl(); - }); - } - - /** - * Get the default profile photo URL if no profile photo has been uploaded. - * - * @return string - */ - protected function defaultProfilePhotoUrl() - { - $name = trim(collect(explode(' ', $this->name))->map(function ($segment) { - return mb_substr($segment, 0, 1); - })->join(' ')); - - return 'https://ui-avatars.com/api/?name='.urlencode($name).'&color=7F9CF5&background=EBF4FF'; - } - - /** - * Get the disk that profile photos should be stored on. - * - * @return string - */ - protected function profilePhotoDisk() - { - return isset($_ENV['VAPOR_ARTIFACT_NAME']) ? 's3' : config('jetstream.profile_photo_disk', 'public'); - } -} diff --git a/src/Http/Controllers/OAuthController.php b/src/Http/Controllers/OAuthController.php index 8b2b1f02..17911fed 100644 --- a/src/Http/Controllers/OAuthController.php +++ b/src/Http/Controllers/OAuthController.php @@ -2,16 +2,24 @@ 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\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\OAuthProviderLinkFailedResponse; use JoelButcher\Socialstream\Contracts\ResolvesSocialiteUsers; use JoelButcher\Socialstream\Contracts\SocialstreamResponse; +use JoelButcher\Socialstream\Events\OAuthProviderLinkFailed; +use JoelButcher\Socialstream\Providers; +use Laravel\Jetstream\Jetstream; use Laravel\Socialite\Two\InvalidStateException; use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirectResponse; @@ -58,4 +66,57 @@ 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 the request.', ['provider' => Providers::name($provider)]), + ); + + return app(OAuthProviderLinkFailedResponse::class); + } + + if (!$providerAccount) { + throw new \DomainException( + message: 'Could not retrieve social provider information.' + ); + } + + return $this->authenticator->linkProvider($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/stubs/jetstream/app/Models/User.php b/stubs/jetstream/app/Models/User.php index 4e118150..0222d954 100644 --- a/stubs/jetstream/app/Models/User.php +++ b/stubs/jetstream/app/Models/User.php @@ -68,7 +68,7 @@ class User extends Authenticatable /** * Get the URL to the user's profile photo. */ - public function profilePhotoUrl(): Attribute + protected function profilePhotoUrl(): Attribute { return filter_var($this->profile_photo_path, FILTER_VALIDATE_URL) ? Attribute::get(fn () => $this->profile_photo_path) diff --git a/stubs/jetstream/app/Models/UserWithTeams.php b/stubs/jetstream/app/Models/UserWithTeams.php index 27be9534..760bf8c5 100644 --- a/stubs/jetstream/app/Models/UserWithTeams.php +++ b/stubs/jetstream/app/Models/UserWithTeams.php @@ -70,7 +70,7 @@ class User extends Authenticatable /** * Get the URL to the user's profile photo. */ - public function profilePhotoUrl(): Attribute + protected function profilePhotoUrl(): Attribute { return filter_var($this->profile_photo_path, FILTER_VALIDATE_URL) ? Attribute::get(fn () => $this->profile_photo_path)