Skip to content

Commit

Permalink
[6.x] Various updates and fixes (incl. 2FA) (#361)
Browse files Browse the repository at this point in the history
* Respect Fortify 2FA if using Jetstream.
Various fixes for Filament.
Remove PHPStan.

* Fix breeze migrations

* Revert breeze changes to service provider

* try again

* please... work

* fix dumbness

* don't seed users table, remove doc blocks from child install drivers

* MJ: this is it!

* dammit
  • Loading branch information
joelbutcher authored Jul 19, 2024
1 parent f94e4c3 commit 30e056c
Show file tree
Hide file tree
Showing 38 changed files with 441 additions and 418 deletions.
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"orchestra/testbench": "^9.0",
"pestphp/pest": "^2.0",
"pestphp/pest-plugin-laravel": "^2.2",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.5"
},
"autoload": {
Expand Down
3 changes: 2 additions & 1 deletion config/filament.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
'providers' => [
// Providers::github(),
],
'component' => 'socialstream::components.socialstream',
'components' => 'socialstream::components.socialstream',
'filament-route' => 'filament.admin.pages.dashboard',
];

This file was deleted.

51 changes: 0 additions & 51 deletions database/migrations/0001_01_01_000000_create_users_table.php

This file was deleted.

11 changes: 0 additions & 11 deletions phpstan.neon.dist

This file was deleted.

76 changes: 64 additions & 12 deletions src/Actions/AuthenticateOAuthCallback.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
namespace JoelButcher\Socialstream\Actions;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Pipeline;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\MessageBag;
Expand All @@ -29,6 +31,11 @@
use JoelButcher\Socialstream\Features;
use JoelButcher\Socialstream\Providers;
use JoelButcher\Socialstream\Socialstream;
use Laravel\Fortify\Actions\CanonicalizeUsername;
use Laravel\Fortify\Actions\EnsureLoginIsNotThrottled;
use Laravel\Fortify\Actions\PrepareAuthenticatedSession;
use Laravel\Fortify\Features as FortifyFeatures;
use Laravel\Fortify\Fortify;
use Laravel\Jetstream\Jetstream;
use Laravel\Socialite\Contracts\User as ProviderUser;

Expand All @@ -41,7 +48,7 @@ class AuthenticateOAuthCallback implements AuthenticatesOAuthCallback
* Create a new controller instance.
*/
public function __construct(
protected Guard $guard,
protected StatefulGuard $guard,
protected CreatesUserFromProvider $createsUser,
protected CreatesConnectedAccounts $createsConnectedAccounts,
protected UpdatesConnectedAccounts $updatesConnectedAccounts
Expand Down Expand Up @@ -108,29 +115,74 @@ public function authenticate(string $provider, ProviderUser $providerAccount): S
/**
* Handle the registration of a new user.
*/
protected function register(string $provider, ProviderUser $providerAccount): SocialstreamResponse
protected function register(string $provider, ProviderUser $providerAccount): SocialstreamResponse|RedirectResponse
{
$user = $this->createsUser->create($provider, $providerAccount);

$this->guard->login($user, Socialstream::hasRememberSessionFeatures());
return tap(
(new Pipeline(app()))->send(request())->through(array_filter([
function ($request, $next) use ($user) {
$this->guard->login($user, Socialstream::hasRememberSessionFeatures());

event(new NewOAuthRegistration($user, $provider, $providerAccount));

return app(OAuthRegisterResponse::class);
return $next($request);
},
]))->then(fn () => app(OAuthRegisterResponse::class)),
fn () => event(new NewOAuthRegistration($user, $provider, $providerAccount))
);
}

/**
* Authenticate the given user and return a login response.
*/
protected function login(Authenticatable $user, mixed $account, string $provider, ProviderUser $providerAccount): SocialstreamResponse
protected function login(Authenticatable $user, mixed $account, string $provider, ProviderUser $providerAccount): SocialstreamResponse|RedirectResponse
{
$this->updatesConnectedAccounts->update($user, $account, $provider, $providerAccount);

$this->guard->login($user, Socialstream::hasRememberSessionFeatures());
return tap(
$this->loginPipeline(request(), $user)->then(fn () => app(OAuthLoginResponse::class)),
fn () => event(new OAuthLogin($user, $provider, $account, $providerAccount)),
);
}

protected function loginPipeline(Request $request, Authenticatable $user): Pipeline
{
if (! class_exists(Fortify::class)) {
return (new Pipeline(app()))->send($request)->through(array_filter([
function ($request, $next) use ($user) {
$this->guard->login($user, Socialstream::hasRememberSessionFeatures());

if ($request->hasSession()) {
$request->session()->regenerate();
}

return $next($request);
},
]));
}

event(new OAuthLogin($user, $provider, $account, $providerAccount));
if (Fortify::$authenticateThroughCallback) {
return (new Pipeline(app()))->send($request)->through(array_filter(
call_user_func(Fortify::$authenticateThroughCallback, $request)
));
}

if (is_array(config('fortify.pipelines.login'))) {
return (new Pipeline(app()))->send($request)->through(array_filter(
config('fortify.pipelines.login')
));
}

return app(OAuthLoginResponse::class);
return (new Pipeline(app()))->send($request)->through(array_filter([
config('fortify.limiters.login') ? null : EnsureLoginIsNotThrottled::class,
config('fortify.lowercase_usernames') ? CanonicalizeUsername::class : null,
FortifyFeatures::enabled(FortifyFeatures::twoFactorAuthentication()) ? RedirectIfTwoFactorAuthenticatable::class : null,
function ($request, $next) use ($user) {
$this->guard->login($user, Socialstream::hasRememberSessionFeatures());

return $next($request);
},
PrepareAuthenticatedSession::class,
]));
}

/**
Expand Down Expand Up @@ -205,7 +257,7 @@ private function flashError(string $error): void
*/
private function canRegister(mixed $user, mixed $account): bool
{
if (! is_null($user) || !is_null($account)) {
if (! is_null($user) || ! is_null($account)) {
return false;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Actions/CreateUserWithTeamsFromProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace JoelButcher\Socialstream\Actions;

use App\Models\Team;
use app\Models\User;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use JoelButcher\Socialstream\Contracts\CreatesConnectedAccounts;
use JoelButcher\Socialstream\Contracts\CreatesUserFromProvider;
Expand Down
37 changes: 37 additions & 0 deletions src/Actions/RedirectIfTwoFactorAuthenticatable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace JoelButcher\Socialstream\Actions;

use JoelButcher\Socialstream\Contracts\ResolvesSocialiteUsers;
use JoelButcher\Socialstream\Socialstream;
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable as BaseAction;
use Laravel\Fortify\Fortify;

class RedirectIfTwoFactorAuthenticatable extends BaseAction
{
protected function validateCredentials($request)
{
if (Fortify::$authenticateUsingCallback) {
return tap(call_user_func(Fortify::$authenticateUsingCallback, $request), function ($user) use ($request) {
if (! $user) {
$this->fireFailedEvent($request);

$this->throwFailedAuthenticationException($request);
}
});
}

$socialUser = app(ResolvesSocialiteUsers::class)
->resolve($request->route('provider'));

return tap(Socialstream::$userModel::where('email', $socialUser->getEmail())->first(), function ($user) use ($request, $socialUser) {
if (! $user || ! Socialstream::$connectedAccountModel::where('email', $socialUser->getEmail())->first()) {
$this->fireFailedEvent($request, $user);

$this->throwFailedAuthenticationException($request);
}
});
}
}
9 changes: 9 additions & 0 deletions src/Concerns/InteractsWithComposer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace JoelButcher\Socialstream\Concerns;

use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;

trait InteractsWithComposer
Expand Down Expand Up @@ -82,4 +83,12 @@ protected function buildBaseComposerCommand(string $command, array $packages, st
$packages
);
}

/**
* Get the path to the appropriate PHP binary.
*/
protected function phpBinary(): string
{
return (new PhpExecutableFinder())->find(false) ?: 'php';
}
}
36 changes: 26 additions & 10 deletions src/Console/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Console\Command;
use Illuminate\Contracts\Console\PromptsForMissingInput;
use Illuminate\Support\ServiceProvider;
use JoelButcher\Socialstream\Concerns\InteractsWithComposer;
use JoelButcher\Socialstream\Concerns\InteractsWithNode;
use JoelButcher\Socialstream\Installer\Enums\BreezeInstallStack;
Expand All @@ -12,9 +13,11 @@
use JoelButcher\Socialstream\Installer\Enums\JetstreamInstallStack;
use JoelButcher\Socialstream\Installer\InstallManager;
use Laravel\Fortify\Features as FortifyFeatures;
use Laravel\Fortify\FortifyServiceProvider;
use Laravel\Jetstream\Jetstream;
use Pest\TestSuite;
use RuntimeException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
Expand All @@ -26,6 +29,7 @@
use function Laravel\Prompts\select;
use function Laravel\Prompts\warning;

#[AsCommand(name: 'socialstream:install')]
class InstallCommand extends Command implements PromptsForMissingInput
{
use InteractsWithComposer;
Expand Down Expand Up @@ -84,6 +88,18 @@ public function handle(InstallManager $installManager): ?int
return self::SUCCESS;
}

/**
* Register the Fortify service provider in the application configuration file.
*/
protected function registerFortifyServiceProvider(): void
{
if (! method_exists(ServiceProvider::class, 'addProviderToBootstrapFile')) {
return;
}

ServiceProvider::addProviderToBootstrapFile(\App\Providers\FortifyServiceProvider::class);
}

/**
* Prompt for missing input arguments using the returned questions.
*/
Expand Down Expand Up @@ -168,7 +184,7 @@ protected function promptForMissingArgumentsUsing(): array
protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output): void
{
if ($this->isUsingFilament()) {
$input->setOption('pest', select(
$input->setOption('pest', $this->option('pest') || select(
label: 'Which testing framework do you prefer?',
options: ['PHPUnit', 'Pest'],
default: $this->isUsingPest() ? 'Pest' : 'PHPUnit'
Expand Down Expand Up @@ -218,17 +234,17 @@ protected function afterPromptingForMissingArguments(InputInterface $input, Outp
]
))->each(fn ($option) => $input->setOption($option, true));
} else {
$input->setOption('dark', confirm(
label: 'Would you like dark mode support?',
default: false
));
$input->setOption('dark', $this->option('dark') || confirm(
label: 'Would you like dark mode support?',
default: false
));
}

$input->setOption('pest', select(
label: 'Which testing framework do you prefer?',
options: ['PHPUnit', 'Pest'],
default: $this->isUsingPest() ? 'pest' : 'phpunit'
) === 'Pest');
$input->setOption('pest', $this->option('pest') || select(
label: 'Which testing framework do you prefer?',
options: ['PHPUnit', 'Pest'],
default: $this->isUsingPest() ? 'pest' : 'phpunit'
) === 'Pest');
}
}

Expand Down
Loading

0 comments on commit 30e056c

Please sign in to comment.