diff --git a/app/Http/Controllers/App/Settings/AuthenticationController.php b/app/Http/Controllers/App/Settings/AuthenticationController.php index e093d9a4..cb7d29be 100644 --- a/app/Http/Controllers/App/Settings/AuthenticationController.php +++ b/app/Http/Controllers/App/Settings/AuthenticationController.php @@ -14,6 +14,7 @@ use Illuminate\Validation\Rule; use Inertia\Inertia; use Inertia\Response; +use Laravel\Socialite\Facades\Socialite; class AuthenticationController extends Controller { @@ -63,6 +64,16 @@ public function destroyOtherSessions(Request $request): RedirectResponse return back()->with('flash.success', __('settings.authentication.sessions.flash_logged_out')); } + public function connectProvider(string $provider): RedirectResponse + { + abort_unless(in_array($provider, self::PROVIDERS, true), 404); + + return match ($provider) { + 'google' => Socialite::driver('google-auth')->redirect(), + 'github' => Socialite::driver('github')->scopes(['read:user', 'user:email'])->redirect(), + }; + } + public function disconnectProvider(Request $request, string $provider): RedirectResponse { abort_unless(in_array($provider, self::PROVIDERS, true), 404); diff --git a/app/Http/Controllers/Auth/GitHubController.php b/app/Http/Controllers/Auth/GitHubController.php index 05be8a6b..3b3f09ba 100644 --- a/app/Http/Controllers/Auth/GitHubController.php +++ b/app/Http/Controllers/Auth/GitHubController.php @@ -35,6 +35,13 @@ public function callback(): RedirectResponse return redirect()->route('login'); } + // The signup/login redirect is gated by the `guest` middleware and + // the connect-from-settings redirect by `auth`, so this is a safe + // signal for which flow we came from. + if (Auth::check()) { + return $this->connectToCurrentUser(Auth::user(), (string) $githubUser->getId()); + } + $user = User::where('github_id', (string) $githubUser->getId()) ->when($githubUser->getEmail(), fn ($query, $email) => $query->orWhere('email', $email)) ->first(); @@ -52,6 +59,25 @@ public function callback(): RedirectResponse return $this->registerNewUser($githubUser); } + private function connectToCurrentUser(User $user, string $githubId): RedirectResponse + { + $existing = User::where('github_id', $githubId) + ->where('id', '!=', $user->id) + ->first(); + + if ($existing) { + return redirect()->route('app.authentication.edit') + ->with('flash.error', __('settings.authentication.providers.flash_already_linked', ['provider' => 'GitHub'])); + } + + if ($user->github_id !== $githubId) { + $user->update(['github_id' => $githubId]); + } + + return redirect()->route('app.authentication.edit') + ->with('flash.success', __('settings.authentication.providers.flash_connected', ['provider' => 'GitHub'])); + } + private function loginExistingUser(User $user, string $githubId): RedirectResponse { if (! $user->github_id) { diff --git a/app/Http/Controllers/Auth/GoogleController.php b/app/Http/Controllers/Auth/GoogleController.php index 822b7a8e..f7e1b5da 100644 --- a/app/Http/Controllers/Auth/GoogleController.php +++ b/app/Http/Controllers/Auth/GoogleController.php @@ -33,6 +33,13 @@ public function callback(): RedirectResponse return redirect()->route('login'); } + // The signup/login redirect is gated by the `guest` middleware and + // the connect-from-settings redirect by `auth`, so this is a safe + // signal for which flow we came from. + if (Auth::check()) { + return $this->connectToCurrentUser(Auth::user(), $googleUser->getId()); + } + $user = User::where('google_id', $googleUser->getId()) ->orWhere('email', $googleUser->getEmail()) ->first(); @@ -44,6 +51,25 @@ public function callback(): RedirectResponse return $this->registerNewUser($googleUser); } + private function connectToCurrentUser(User $user, string $googleId): RedirectResponse + { + $existing = User::where('google_id', $googleId) + ->where('id', '!=', $user->id) + ->first(); + + if ($existing) { + return redirect()->route('app.authentication.edit') + ->with('flash.error', __('settings.authentication.providers.flash_already_linked', ['provider' => 'Google'])); + } + + if ($user->google_id !== $googleId) { + $user->update(['google_id' => $googleId]); + } + + return redirect()->route('app.authentication.edit') + ->with('flash.success', __('settings.authentication.providers.flash_connected', ['provider' => 'Google'])); + } + private function loginExistingUser(User $user, string $googleId): RedirectResponse { if (! $user->google_id) { diff --git a/lang/en/settings.php b/lang/en/settings.php index b7996938..d9de6064 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -100,6 +100,8 @@ 'connect' => 'Connect', 'disconnect' => 'Disconnect', 'flash_disconnected' => ':provider disconnected successfully.', + 'flash_connected' => ':provider connected successfully.', + 'flash_already_linked' => 'That :provider account is already linked to another user.', 'flash_cannot_disconnect' => 'You cannot disconnect your only sign-in method. Set a password or connect another provider first.', ], ], diff --git a/lang/es/settings.php b/lang/es/settings.php index 3de7dc26..86a1d58c 100644 --- a/lang/es/settings.php +++ b/lang/es/settings.php @@ -100,6 +100,8 @@ 'connect' => 'Conectar', 'disconnect' => 'Desconectar', 'flash_disconnected' => ':provider desconectada correctamente.', + 'flash_connected' => ':provider conectada correctamente.', + 'flash_already_linked' => 'Esa cuenta de :provider ya está vinculada a otro usuario.', 'flash_cannot_disconnect' => 'No puedes desconectar tu único método de inicio de sesión. Define una contraseña o conecta otro proveedor primero.', ], ], diff --git a/lang/pt-BR/settings.php b/lang/pt-BR/settings.php index db2421cf..d735b1ae 100644 --- a/lang/pt-BR/settings.php +++ b/lang/pt-BR/settings.php @@ -100,6 +100,8 @@ 'connect' => 'Conectar', 'disconnect' => 'Desconectar', 'flash_disconnected' => ':provider desconectada com sucesso.', + 'flash_connected' => ':provider conectada com sucesso.', + 'flash_already_linked' => 'Essa conta do :provider já está vinculada a outro usuário.', 'flash_cannot_disconnect' => 'Você não pode desconectar seu único método de login. Defina uma senha ou conecte outro provedor primeiro.', ], ], diff --git a/resources/js/pages/settings/profile/Authentication.vue b/resources/js/pages/settings/profile/Authentication.vue index a75f3822..430e3a49 100644 --- a/resources/js/pages/settings/profile/Authentication.vue +++ b/resources/js/pages/settings/profile/Authentication.vue @@ -1,5 +1,5 @@