Skip to content

Commit

Permalink
Add Oauth frontend and backend improvements (#718)
Browse files Browse the repository at this point in the history
* better oauth provider loading

* add auth frontend

* add configs for all default providers

* add more default providers

* add env variables to enable oauth providers

* small refactor to link/ unlink routes

* add oauth tab to (admin) profile

* use redirects instead of exceptions

* add notification if no oauth user is found

* use import in config

* remove whmcs provider

* replace hardcoded links with `route`

* redirect to account page on unlink

* remove unnecessary controller and handle linking/ unlinking in action

* only show oauth tab if at least one oauth provider is enabled
  • Loading branch information
Boy132 authored Nov 30, 2024
1 parent 951fc73 commit b208835
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 63 deletions.
23 changes: 23 additions & 0 deletions app/Filament/Pages/Auth/Login.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
namespace App\Filament\Pages\Auth;

use Coderflex\FilamentTurnstile\Forms\Components\Turnstile;
use Filament\Forms\Components\Actions;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\TextInput;
use Filament\Pages\Auth\Login as BaseLogin;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class Login extends BaseLogin
Expand All @@ -19,6 +22,7 @@ protected function getForms(): array
$this->getLoginFormComponent(),
$this->getPasswordFormComponent(),
$this->getRememberFormComponent(),
$this->getOAuthFormComponent(),
Turnstile::make('captcha')
->hidden(!config('turnstile.turnstile_enabled'))
->validationMessages([
Expand Down Expand Up @@ -49,6 +53,25 @@ protected function getLoginFormComponent(): Component
->extraInputAttributes(['tabindex' => 1]);
}

protected function getOAuthFormComponent(): Component
{
$actions = [];

foreach (config('auth.oauth') as $name => $data) {
if (!$data['enabled']) {
continue;
}

$actions[] = Action::make("oauth_$name")
->label(Str::title($name))
->icon($data['icon'])
->color($data['color'])
->url(route('auth.oauth.redirect', ['driver' => $name], false));
}

return Actions::make($actions);
}

protected function getCredentialsFromFormData(array $data): array
{
$loginType = filter_var($data['login'], FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
Expand Down
51 changes: 51 additions & 0 deletions app/Filament/Resources/UserResource/Pages/EditProfile.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
use App\Models\User;
use App\Services\Users\ToggleTwoFactorService;
use App\Services\Users\TwoFactorSetupService;
use App\Services\Users\UserUpdateService;
use chillerlan\QRCode\Common\EccLevel;
use chillerlan\QRCode\Common\Version;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use Closure;
use DateTimeZone;
use Exception;
use Filament\Forms\Components\Actions;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Placeholder;
Expand All @@ -33,7 +35,9 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password;
use Laravel\Socialite\Facades\Socialite;

/**
* @method User getUser()
Expand Down Expand Up @@ -113,6 +117,53 @@ protected function getForms(): array
->options(fn (User $user) => $user->getAvailableLanguages()),
]),

Tab::make('OAuth')
->icon('tabler-brand-oauth')
->visible(function () {
foreach (config('auth.oauth') as $name => $data) {
if ($data['enabled']) {
return true;
}
}

return false;
})
->schema(function () {
$providers = [];

foreach (config('auth.oauth') as $name => $data) {
if (!$data['enabled']) {
continue;
}

$unlink = array_key_exists($name, $this->getUser()->oauth);

$providers[] = Action::make("oauth_$name")
->label(($unlink ? 'Unlink ' : 'Link ') . Str::title($name))
->icon($unlink ? 'tabler-unlink' : 'tabler-link')
->color($data['color'])
->action(function (UserUpdateService $updateService) use ($name, $unlink) {
if ($unlink) {
$oauth = auth()->user()->oauth;
unset($oauth[$name]);

$updateService->handle(auth()->user(), ['oauth' => $oauth]);

$this->fillForm();

Notification::make()
->title("OAuth provider '$name' unlinked")
->success()
->send();
} elseif (config("auth.oauth.$name.enabled")) {
redirect(Socialite::with($name)->redirect()->getTargetUrl());
}
});
}

return [Actions::make($providers)];
}),

Tab::make('2FA')
->icon('tabler-shield-lock')
->schema(function (TwoFactorSetupService $setupService) {
Expand Down
22 changes: 20 additions & 2 deletions app/Http/Controllers/Auth/OAuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace App\Http\Controllers\Auth;

use App\Filament\Resources\UserResource\Pages\EditProfile;
use Filament\Notifications\Notification;
use Illuminate\Auth\AuthManager;
use Illuminate\Http\RedirectResponse;
use Laravel\Socialite\Facades\Socialite;
Expand All @@ -26,6 +28,11 @@ public function __construct(
*/
protected function redirect(string $driver): RedirectResponse
{
// Driver is disabled - redirect to normal login
if (!config("auth.oauth.$driver.enabled")) {
return redirect()->route('auth.login');
}

return Socialite::with($driver)->redirect();
}

Expand All @@ -34,6 +41,11 @@ protected function redirect(string $driver): RedirectResponse
*/
protected function callback(Request $request, string $driver): RedirectResponse
{
// Driver is disabled - redirect to normal login
if (!config("auth.oauth.$driver.enabled")) {
return redirect()->route('auth.login');
}

$oauthUser = Socialite::driver($driver)->user();

// User is already logged in and wants to link a new OAuth Provider
Expand All @@ -43,15 +55,21 @@ protected function callback(Request $request, string $driver): RedirectResponse

$this->updateService->handle($request->user(), ['oauth' => $oauth]);

return redirect()->route('account');
return redirect(EditProfile::getUrl(['tab' => '-oauth-tab']));
}

try {
$user = User::query()->whereJsonContains('oauth->'. $driver, $oauthUser->getId())->firstOrFail();

$this->auth->guard()->login($user, true);
} catch (Exception $e) {
} catch (Exception) {
// No user found - redirect to normal login
Notification::make()
->title('No linked User found')
->danger()
->persistent()
->send();

return redirect()->route('auth.login');
}

Expand Down
43 changes: 0 additions & 43 deletions app/Http/Controllers/Base/OAuthController.php

This file was deleted.

16 changes: 13 additions & 3 deletions app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Laravel\Sanctum\Sanctum;
use SocialiteProviders\Discord\Provider;
use SocialiteProviders\Manager\SocialiteWasCalled;

class AppServiceProvider extends ServiceProvider
Expand Down Expand Up @@ -69,8 +68,19 @@ public function boot(Application $app): void
Scramble::registerApi('client', ['api_path' => 'api/client', 'info' => ['version' => '1.0']])->afterOpenApiGenerated($bearerTokens);
Scramble::registerApi('remote', ['api_path' => 'api/remote', 'info' => ['version' => '1.0']])->afterOpenApiGenerated($bearerTokens);

Event::listen(function (SocialiteWasCalled $event) {
$event->extendSocialite('discord', Provider::class);
$oauthProviders = [];
foreach (config('auth.oauth') as $name => $data) {
config()->set("services.$name", array_merge($data['service'], ['redirect' => "/auth/oauth/callback/$name"]));

if (isset($data['provider'])) {
$oauthProviders[$name] = $data['provider'];
}
}

Event::listen(function (SocialiteWasCalled $event) use ($oauthProviders) {
foreach ($oauthProviders as $name => $provider) {
$event->extendSocialite($name, $provider);
}
});

FilamentColor::register([
Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
"predis/predis": "~2.1.1",
"ryangjchandler/blade-tabler-icons": "^2.3",
"s1lentium/iptools": "~1.2.0",
"socialiteproviders/authentik": "^5.2",
"socialiteproviders/discord": "^4.2",
"socialiteproviders/steam": "^4.2",
"spatie/laravel-fractal": "^6.2",
"spatie/laravel-permission": "^6.9",
"spatie/laravel-query-builder": "^5.8.1",
Expand Down
100 changes: 100 additions & 0 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b208835

Please sign in to comment.