user(); $links = SocialAccount::query() ->where('user_id', $user?->getKey()) ->get() ->keyBy(fn (SocialAccount $link): string => $link->provider->value); $providers = []; foreach (SocialSettings::allProviders() as $key => $settings) { if (! $settings->usable()) { continue; } $link = $links->get($key); $providers[] = [ 'provider' => $key, 'label' => $settings->provider->label(), 'connected' => $link !== null, 'email' => $link?->email, 'connected_at' => $link?->created_at?->toIso8601String(), ]; } return Inertia::render('settings/connected-accounts', [ 'providers' => $providers, // Why a disconnect may be refused, said before it is tried // rather than after. 'has_local_password' => $user?->auth_source === AuthSource::Local, ]); } public function destroy(Request $request, string $provider): RedirectResponse { $case = SocialProvider::tryFrom($provider) ?? throw new NotFoundHttpException; $user = $request->user(); if ($user === null) { return redirect()->route('login'); } $link = SocialAccount::query() ->where('user_id', $user->getKey()) ->where('provider', $case->value) ->first(); if ($link === null) { return back(); } // The mirror image of AccountConversion::requiresNewPassword(): // an account created by a provider holds a random password nobody // has ever seen, so the provider is the only way in. Removing the // last one locks the person out of their own files. $remaining = SocialAccount::query() ->where('user_id', $user->getKey()) ->where('provider', '!=', $case->value) ->count(); if ($remaining === 0 && $user->auth_source !== AuthSource::Local) { throw ValidationException::withMessages([ 'provider' => __('This is the only way you can sign in. Set a password first, then disconnect :provider.', [ 'provider' => $case->label(), ]), ]); } $link->delete(); $this->activity->log(Action::SocialAccountUnlinked, $user, $user, ['provider' => $case->label()]); return back()->with('success', __(':provider disconnected.', ['provider' => $case->label()])); } }