diff --git a/app/Extensions/OAuth/OAuthSchemaInterface.php b/app/Extensions/OAuth/OAuthSchemaInterface.php index 837705888..6d7610677 100644 --- a/app/Extensions/OAuth/OAuthSchemaInterface.php +++ b/app/Extensions/OAuth/OAuthSchemaInterface.php @@ -32,4 +32,6 @@ interface OAuthSchemaInterface public function getHexColor(): ?string; public function isEnabled(): bool; + + public function shouldCreateMissingUsers(): bool; } diff --git a/app/Extensions/OAuth/Schemas/OAuthSchema.php b/app/Extensions/OAuth/Schemas/OAuthSchema.php index 0f5873d41..250dba5da 100644 --- a/app/Extensions/OAuth/Schemas/OAuthSchema.php +++ b/app/Extensions/OAuth/Schemas/OAuthSchema.php @@ -5,7 +5,9 @@ namespace App\Extensions\OAuth\Schemas; use App\Extensions\OAuth\OAuthSchemaInterface; use Filament\Forms\Components\Component; use Filament\Forms\Components\TextInput; +use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Wizard\Step; +use Filament\Forms\Set; use Illuminate\Support\Str; abstract class OAuthSchema implements OAuthSchemaInterface @@ -53,6 +55,17 @@ abstract class OAuthSchema implements OAuthSchemaInterface ->revealable() ->autocomplete(false) ->default(env("OAUTH_{$id}_CLIENT_SECRET")), + Toggle::make("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS") + ->label(trans('admin/setting.oauth.create_missing_users')) + ->columnSpanFull() + ->inline(false) + ->onIcon('tabler-check') + ->offIcon('tabler-x') + ->onColor('success') + ->offColor('danger') + ->formatStateUsing(fn ($state): bool => (bool) $state) + ->afterStateUpdated(fn ($state, Set $set) => $set("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", (bool) $state)) + ->default(env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS")), ]; } @@ -96,4 +109,11 @@ abstract class OAuthSchema implements OAuthSchemaInterface return env("OAUTH_{$id}_ENABLED", false); } + + public function shouldCreateMissingUsers(): bool + { + $id = Str::upper($this->getId()); + + return env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", false); + } } diff --git a/app/Http/Controllers/Auth/OAuthController.php b/app/Http/Controllers/Auth/OAuthController.php index 97e6a4f15..31de3cb9d 100644 --- a/app/Http/Controllers/Auth/OAuthController.php +++ b/app/Http/Controllers/Auth/OAuthController.php @@ -6,8 +6,8 @@ use App\Extensions\OAuth\OAuthService; use App\Filament\Pages\Auth\EditProfile; use App\Http\Controllers\Controller; use App\Models\User; +use App\Services\Users\UserCreationService; use App\Services\Users\UserUpdateService; -use Exception; use Filament\Notifications\Notification; use Illuminate\Auth\AuthManager; use Illuminate\Http\RedirectResponse; @@ -18,8 +18,9 @@ class OAuthController extends Controller { public function __construct( private readonly AuthManager $auth, + private UserCreationService $userCreation, private readonly UserUpdateService $updateService, - private readonly OAuthService $oauthService + private readonly OAuthService $oauthService, ) {} /** @@ -40,8 +41,10 @@ class OAuthController extends Controller */ public function callback(Request $request, string $driver): RedirectResponse { - // Driver is disabled - redirect to normal login - if (!$this->oauthService->get($driver)?->isEnabled()) { + $driver = $this->oauthService->get($driver); + + // Unknown driver or driver is disabled - redirect to normal login + if (!$driver || !$driver->isEnabled()) { return redirect()->route('auth.login'); } @@ -59,33 +62,57 @@ class OAuthController extends Controller return redirect()->route('auth.login'); } - $oauthUser = Socialite::driver($driver)->user(); + $oauthUser = Socialite::driver($driver->getId())->user(); // User is already logged in and wants to link a new OAuth Provider if ($request->user()) { $oauth = $request->user()->oauth; - $oauth[$driver] = $oauthUser->getId(); + $oauth[$driver->getId()] = $oauthUser->getId(); $this->updateService->handle($request->user(), ['oauth' => $oauth]); return redirect(EditProfile::getUrl(['tab' => '-oauth-tab'], panel: 'app')); } - try { - $user = User::query()->whereJsonContains('oauth->'. $driver, $oauthUser->getId())->firstOrFail(); + $user = User::whereJsonContains('oauth->'. $driver->getId(), $oauthUser->getId())->first(); - $this->auth->guard()->login($user, true); - } catch (Exception) { - // No user found - redirect to normal login - Notification::make() - ->title('No linked User found') - ->danger() - ->persistent() - ->send(); + if (!$user) { + // No user found and auto creation is disabled - redirect to normal login + if (!$driver->shouldCreateMissingUsers()) { + Notification::make() + ->title('No linked User found') + ->danger() + ->persistent() + ->send(); - return redirect()->route('auth.login'); + return redirect()->route('auth.login'); + } + + $username = $oauthUser->getNickname(); + $email = $oauthUser->getEmail(); + + // Incomplete data, can't create user - redirect to normal login + if (!$email) { + Notification::make() + ->title('No linked User found') + ->danger() + ->persistent() + ->send(); + + return redirect()->route('auth.login'); + } + + $user = $this->userCreation->handle([ + 'username' => $username, + 'email' => $email, + 'oauth' => [ + $driver->getId() => $oauthUser->getId(), + ], + ]); } + $this->auth->guard()->login($user, true); + return redirect('/'); } } diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php index 4fe6e5093..cafb22873 100644 --- a/app/Services/Subusers/SubuserCreationService.php +++ b/app/Services/Subusers/SubuserCreationService.php @@ -4,7 +4,6 @@ namespace App\Services\Subusers; use App\Events\Server\SubUserAdded; use App\Models\User; -use Illuminate\Support\Str; use App\Models\Server; use App\Models\Subuser; use Illuminate\Database\ConnectionInterface; @@ -40,14 +39,8 @@ class SubuserCreationService return $this->connection->transaction(function () use ($server, $email, $permissions) { $user = User::query()->where('email', $email)->first(); if (!$user) { - // Just cap the username generated at 64 characters at most and then append a random string - // to the end to make it "unique"... - [$beforeDomain] = explode('@', $email, 1); - $username = substr(preg_replace('/([^\w.-]+)/', '', $beforeDomain), 0, 64) . Str::random(3); - $user = $this->userCreationService->handle([ 'email' => $email, - 'username' => $username, 'root_admin' => false, ]); } diff --git a/app/Services/Users/UserCreationService.php b/app/Services/Users/UserCreationService.php index 589b7e45e..644dac7f5 100644 --- a/app/Services/Users/UserCreationService.php +++ b/app/Services/Users/UserCreationService.php @@ -3,6 +3,7 @@ namespace App\Services\Users; use App\Models\Role; +use Illuminate\Support\Str; use Ramsey\Uuid\Uuid; use App\Models\User; use Illuminate\Contracts\Hashing\Hasher; @@ -42,6 +43,16 @@ class UserCreationService $isRootAdmin = array_key_exists('root_admin', $data) && $data['root_admin']; unset($data['root_admin']); + if (empty($data['username'])) { + $data['username'] = str($data['email'])->before('@')->toString() . Str::random(3); + } + + $data['username'] = str($data['username']) + ->replace(['.', '-'], '') + ->ascii() + ->substr(0, 64) + ->toString(); + /** @var User $user */ $user = User::query()->forceCreate(array_merge($data, [ 'uuid' => Uuid::uuid4()->toString(), diff --git a/lang/en/admin/setting.php b/lang/en/admin/setting.php index 9f0ee01be..718e9511f 100644 --- a/lang/en/admin/setting.php +++ b/lang/en/admin/setting.php @@ -97,6 +97,7 @@ return [ 'base_url' => 'Base URL', 'display_name' => 'Display Name', 'auth_url' => 'Authorization callback URL', + 'create_missing_users' => 'Auto Create Missing Users?', ], 'misc' => [ 'auto_allocation' => [