andrewdwallo / filament-companies

A comprehensive Laravel authentication and authorization system designed for Filament, focusing on multi-tenant company management.
MIT License
268 stars 57 forks source link

Cannot assign null to property Wallo\FilamentCompanies\Http\Controllers\OAuthController::$registrationUrl of type string #129

Closed eelco2k closed 6 months ago

eelco2k commented 6 months ago

when using an php artisan console command i'm now gettting this error after upgrading to version v3.2.2

Cannot assign null to property Wallo\FilamentCompanies\Http\Controllers\OAuthController::$registrationUrl of type string

  at vendor/andrewdwallo/filament-companies/src/Http/Controllers/OAuthController.php:58
     54▕         $this->updatesConnectedAccounts = $updatesConnectedAccounts;
     55▕         $this->invalidStateHandler = $invalidStateHandler;
     56▕ 
     57▕         $this->guard = Filament::auth();
  ➜  58▕         $this->registrationUrl = Filament::getRegistrationUrl();
     59▕         $this->loginUrl = Filament::getLoginUrl();
     60▕         $this->userPanel = FilamentCompanies::getUserPanel();
     61▕     }
     62▕ 

When i dd(Filament::getCurrentPanel()); just above $this->guard = Filament::auth(); it returns a panel which does not have filamentCompanies plugin registered / enabled.

changing this line resolves the issue:

$this->registrationUrl = Filament::getRegistrationUrl() ?? '';
VincentLahaye commented 6 months ago

I'm also working with multiple panels, and I may have encountered a similar issue.

I don't know if it's a good practive, but I've enabled login/register only on the main panel. The other panels have a config similar to this one :

class SeondaryPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->id('secondary_panel')
            ->path('secondary_panel')
            ->login(function() { return redirect('/'); }) // Redirects to main_panel which is on '/' path
            ->homeUrl(static fn (): string => url(Pages\Dashboard::getUrl(panel: 'main_panel', tenant: Auth::user()->currentCompany))) // Home button also redirects to main_panel
...
andrewdwallo commented 6 months ago

@eelco2k What artisan command are you speaking of?

andrewdwallo commented 6 months ago

@VincentLahaye If you want only the "Main" panel to have a login/register, then it would be a better practice to do something along the lines of this.

In your "Main" panel, mark it as default like so:

class FilamentCompaniesServiceProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->default() // HERE
            ->id('company')
            ->path('company')
            ->login(Login::class)
            ->registration(Register::class)
            ...
    }
}

For your other panels, create a new class at app/Http/Middleware/Authenticate.php or use the default one provided by the Laravel skeleton, and use this code in it:

namespace App\Http\Middleware;

use Filament\Facades\Filament;
use Filament\Http\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    /**
     * Redirect users who try to access other panel routes before being authenticated.
     */
    protected function redirectTo($request): ?string
    {
        return Filament::getDefaultPanel()->getLoginUrl();
    }
}

Then in the other panels (the non-main ones), replace the default Filament\Http\Middleware\Authenticate middleware provided by Filament with the new middleware from above like so:

namespace App\Providers\Filament;

use App\Http\Middleware\Authenticate;

class AdminPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            // ...
            ->authMiddleware([
                Authenticate::class,
            ]);
    }
}

You can look at my ERPSAAS repository as an example.

eelco2k commented 6 months ago

Any artisan command

andrewdwallo commented 6 months ago

Any artisan command

Okay well your issue title and description doesn't really help me figure out the problem that is causing this at all. Can you provide more information about your application? Can you provide me with the code of your FilamentCompaniesServiceProvider.php file? The upgrade to v3.2.2 is very unlikely to be the cause. What version were you using before the upgrade?

eelco2k commented 6 months ago

I’m not not behind my computer. But I have 5 panels but some of them don’t have your plugin registered. and for some I only have tenant(Company::class) setup. No registration in the panel only in one panel where also you plugin is registered. But for some reason the tenant registration is null. Will have to debug further but can do that in about 2 hours when I’m behind my computer.

eelco2k commented 6 months ago

sorry i mentioned that all artisan commands this error occured but that is not true now i'm debugging it and it occures when i do the following artisan command:

php artisan route:list 
eelco2k commented 6 months ago

From the Filament perspective this $registrationUrl can be null, see return type ?string. vendor/filament/filament/src/Panel/Concerns/HasAuth.php

/**
     * @param  array<mixed>  $parameters
     */
    public function getRegistrationUrl(array $parameters = []): ?string

So when you don't use the $panel->tenantRegistration(); on a specific panel and also don't set filament-companies plugin to that panel. it's correct that this getRegistrationUrl() returns NULL.

eelco2k commented 6 months ago

when i add this to that OAuthController.php just before $this->registrationUrl = ....

 if(empty(Filament::getRegistrationUrl())) {
            dump([
                Filament::getCurrentPanel()->getId(),
                Filament::getCurrentPanel()->hasRegistration()
            ]);
        }

i'm getting:

array:2 [
  0 => "central-admin"
  1 => false
]
eelco2k commented 6 months ago

And also as mentioned in the docs: https://filamentphp.com/docs/3.x/panels/users#authentication-features

I dont have $panel->registration()

mine looks like this:

 return $panel
            ->default()
            ->id('central-admin')
            ->path('')
            ->login()
            ->passwordReset()
            ->topNavigation()
            ->spa()
            ->resources(
                FilamentAuthentication::resources(),
            )
            ->brandName('CENTRAL PORTAL')

etc...

eelco2k commented 6 months ago

so for now i have changed it like this:


class OAuthController extends Controller
{
    protected ?string $registrationUrl;

...

added the questionmark before string to make it nullable, following filaments convention, there the variable is also nullable;

andrewdwallo commented 6 months ago

Do you have socialite registered in the Panel in which you have registration registered for Filament?

eelco2k commented 6 months ago

the $panel->registration(Register::class) is only there in my CompaniesPanelProvider.php

it looks like this:

return $panel
            ->id('company')
            ->path('company')
            ->default()
            ->login(Login::class)
            ->passwordReset()
            ->homeUrl(static fn (): string => url(Pages\Dashboard::getUrl(panel: 'company', tenant: Auth::user()?->personalCompany())))
            ->registration(Register::class)
            ->colors([
                'primary' => Color::Amber,
            ])
            ->resources(
                FilamentAuthentication::resources()
            )
            ->viteTheme('resources/css/filament/company/theme.css')
            ->tenant(Company::class)
            ->tenantProfile(CompanySettings::class)
            ->tenantRegistration(CreateCompany::class)
            ->tenantBillingProvider(new BillingProvider('example-1'))

            ->discoverResources(in: app_path('Filament/Company/Resources'), for: 'App\\Filament\\Company\\Resources')
            ->discoverPages(in: app_path('Filament/Company/Pages'), for: 'App\\Filament\\Company\\Pages')
            ->pages([
                Pages\Dashboard::class,
            ])
            ->userMenuItems([
                    // 'profile' => MenuItem::make()
                    //     ->label('Profile')
                    //     ->icon('heroicon-o-user-circle')
                    // ->url(static fn () => route(MyProfilePage::getRouteName(panel: 'company'))),
                    // ->url(static fn () => route(Profile::getRouteName(panel: 'admin'))),
            ])
            ->authGuard('web')
            ->discoverWidgets(in: app_path('Filament/Company/Widgets'), for: 'App\\Filament\\Company\\Widgets')
            ->widgets([
                Widgets\AccountWidget::class,
            ])
            ->middleware([
                EncryptCookies::class,
                AddQueuedCookiesToResponse::class,
                StartSession::class,
                AuthenticateSession::class,
                ShareErrorsFromSession::class,
                VerifyCsrfToken::class,
                SubstituteBindings::class,
                DisableBladeIconComponents::class,
                DispatchServingFilamentEvent::class,
            ])
            ->authMiddleware([
                Authenticate::class,
            ])
            ->plugins([
            FilamentCompanies::make()
                ->userPanel('company')
                ->switchCurrentCompany()
                ->updateProfileInformation()
                ->updatePasswords()
                ->setPasswords()
                ->connectedAccounts()
                ->manageBrowserSessions()
                ->accountDeletion()
                ->profilePhotos()
                ->api()
                ->companies(invitations: true)
                ->termsAndPrivacyPolicy()
                ->notifications()
                ->modals()
                ->socialite(
                    providers: [Providers::github()],
                    features: [Socialite::rememberSession(), Socialite::providerAvatars()]
                ),
            ]);

but on my other panels don't have $panel->registration(Register::class) but i do have this configured:

FilamentCompanies::make()
                        ->userPanel('company')
                        ->companies(invitations: true)
                        ->switchCurrentCompany()
                        ->updateProfileInformation()
                        ->updatePasswords()
                        ->setPasswords()
                        ->connectedAccounts()
                        ->manageBrowserSessions()
                        ->accountDeletion()
                        ->profilePhotos()
                        ->api()
                        ->termsAndPrivacyPolicy()
                        ->notifications()
                        ->modals()
                        ->socialite(
                            providers: [Providers::github()],
                            features: [
                                Socialite::rememberSession(), Socialite::providerAvatars(),
                            ]
                        ),

and

FilamentCompanies::make()
                    ->userPanel('company')
                    ->userPanel('tenant-portal')
                    ->switchCurrentCompany()
                    ->updateProfileInformation()
                    ->updatePasswords()
                    ->setPasswords()
                    ->connectedAccounts()
                    ->manageBrowserSessions()
                    ->accountDeletion()
                    ->profilePhotos()
                    ->api()
                    ->companies(invitations: true)
                    ->termsAndPrivacyPolicy()
                    ->notifications()
                    ->modals()
                    ->socialite(
                        providers: [Providers::github()],
                        features: [Socialite::rememberSession(), Socialite::providerAvatars()]
                    ),

now i noticed that i used twice ->userPanel() i'll just paste it like it was but i've changed that to the same name as the panelId --> $panel->id('tenant-portal'). Not sure whether that ->userPanel name makes a different. i will find out...

andrewdwallo commented 6 months ago

@eelco2k You should only have my Plugin registered in one single Panel.. Which should be the FilamentCompaniesServiceProvider. Also, it isn't recommended to have the ->userPanel() be the same as your company panel. This is why you are probably having issues.

eelco2k commented 6 months ago

I did not have it on an earlier version that was version v3.1.8 and filament version v3.2.9

and in v3.2.2 with filament v3.2.46 the issue occures.

andrewdwallo commented 6 months ago

@eelco2k I'm confused on what you are saying? Do you have my plugin registered in multiple panels? If so, it shouldn't be. The plugin isn't made to be registered in multiple panels. Update your Service Providers and let me know if the issue still occurs then.

eelco2k commented 6 months ago

@andrewdwallo I just removed all plugin registrations from my panels except the CompaniesPanelProvider.php

and when removing the ? from protected ?string $registrationUrl; the problem with php artisan route:list is still there.

this is how my app/Providers/Filament/CompaniesPanelProvider.php looks like:


class CompaniesPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->id('company')
            ->path('company')
            ->default()
            ->login(Login::class)
            ->passwordReset()
            ->homeUrl(static fn (): string => url(Pages\Dashboard::getUrl(panel: 'company', tenant: Auth::user()?->personalCompany())))
            ->registration(Register::class)
            ->colors([
                'primary' => Color::Amber,
            ])
            ->resources(
                FilamentAuthentication::resources()
            )
            ->viteTheme('resources/css/filament/company/theme.css')
            ->tenant(Company::class)
            ->tenantProfile(CompanySettings::class)
            ->tenantRegistration(CreateCompany::class)
            ->tenantBillingProvider(new BillingProvider('example-1'))

            ->discoverResources(in: app_path('Filament/Company/Resources'), for: 'App\\Filament\\Company\\Resources')
            ->discoverPages(in: app_path('Filament/Company/Pages'), for: 'App\\Filament\\Company\\Pages')
            ->pages([
                Pages\Dashboard::class,
            ])
            ->userMenuItems([
                    // 'profile' => MenuItem::make()
                    //     ->label('Profile')
                    //     ->icon('heroicon-o-user-circle')
                    // ->url(static fn () => route(MyProfilePage::getRouteName(panel: 'company'))),
                    // ->url(static fn () => route(Profile::getRouteName(panel: 'admin'))),
            ])
            ->authGuard('web')
            ->discoverWidgets(in: app_path('Filament/Company/Widgets'), for: 'App\\Filament\\Company\\Widgets')
            ->widgets([
                Widgets\AccountWidget::class,
            ])
            ->middleware([
                EncryptCookies::class,
                AddQueuedCookiesToResponse::class,
                StartSession::class,
                AuthenticateSession::class,
                ShareErrorsFromSession::class,
                VerifyCsrfToken::class,
                SubstituteBindings::class,
                DisableBladeIconComponents::class,
                DispatchServingFilamentEvent::class,
            ])
            ->authMiddleware([
                Authenticate::class,
            ])
            ->plugins([
            FilamentCompanies::make()
                ->userPanel('company')
                ->switchCurrentCompany()
                ->updateProfileInformation()
                ->updatePasswords()
                ->setPasswords()
                ->connectedAccounts()
                ->manageBrowserSessions()
                ->accountDeletion()
                ->profilePhotos()
                ->api()
                ->companies(invitations: true)
                ->termsAndPrivacyPolicy()
                ->notifications()
                ->modals()
                ->socialite(
                    providers: [Providers::github()],
                    features: [Socialite::rememberSession(), Socialite::providerAvatars()]
                ),
            BreezyCore::make()
                ->enableTwoFactorAuthentication(
                    force: false, // force the user to enable 2FA before they can use the application (default = false)
                    // action: CustomTwoFactorPage::class // optionally, use a custom 2FA page
                )
                ->enableSanctumTokens()
                ->myProfile(
                    shouldRegisterUserMenu: true, // Sets the 'account' link in the panel User Menu (default = true)
                    shouldRegisterNavigation: false, // Adds a main navigation item for the My Profile page (default = false)
                    hasAvatars: true, // Enables the avatar upload form component (default = false)
                    slug: 'my-profile' // Sets the slug for the profile page (default = 'my-profile')
                ),
            FilamentRouteStatisticsPlugin::make(),

            ]);
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configurePermissions();

        FilamentCompanies::createUsersUsing(CreateNewUser::class);
        FilamentCompanies::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
        FilamentCompanies::updateUserPasswordsUsing(UpdateUserPassword::class);

        FilamentCompanies::createCompaniesUsing(CreateCompany::class);
        FilamentCompanies::updateCompanyNamesUsing(UpdateCompanyName::class);
        FilamentCompanies::addCompanyEmployeesUsing(AddCompanyEmployee::class);
        FilamentCompanies::inviteCompanyEmployeesUsing(InviteCompanyEmployee::class);
        FilamentCompanies::removeCompanyEmployeesUsing(RemoveCompanyEmployee::class);
        FilamentCompanies::deleteCompaniesUsing(DeleteCompany::class);
        FilamentCompanies::deleteUsersUsing(DeleteUser::class);

        Socialite::resolvesSocialiteUsersUsing(ResolveSocialiteUser::class);
        Socialite::createUsersFromProviderUsing(CreateUserFromProvider::class);
        Socialite::createConnectedAccountsUsing(CreateConnectedAccount::class);
        Socialite::updateConnectedAccountsUsing(UpdateConnectedAccount::class);
        Socialite::setUserPasswordsUsing(SetUserPassword::class);
        Socialite::handlesInvalidStateUsing(HandleInvalidState::class);
        Socialite::generatesProvidersRedirectsUsing(GenerateRedirectForProvider::class);
    }
}
eelco2k commented 6 months ago

When i'm xdebugging, i notice that these lines:

        dump(
            Filament::getCurrentPanel()->getId(),
            Filament::getCurrentPanel()->getPlugins(),
        );

        $this->registrationUrl = Filament::getRegistrationUrl();

the getId() is another panel than the 'companies' panel ID. it doesn't even get that 'companies' panel. only another panel twice.

after stepping through.. the file before going to the OAuthController.php is

vendor/laravel/framework/src/illuminate/foundation/console/Kernel.php

return $this->getArtisan()->run($input, $output); line.

andrewdwallo commented 6 months ago

Where are you dumping these? Filament::getCurrentPanel() isn't a static value.. It changes based on which Panel the user is currently logged into. Are you modifying this piece of code $this->registrationUrl = Filament::getRegistrationUrl(); directly in the vendor/ directory? If so, you shouldn't do this and expect everything to work correctly as intended. You also can't expect a console command to accurately tell you which Panel is the "current panel". Are there any issues you are having when you run the application and actually login through the UI into the panel?

One thing to note as well, which is mentioned in this packages Documentation here:

In order for this package to work you must also have a "User" panel to contain the Profile page and Personal Access Tokens page.

Based on your FilamentCompaniesServiceProvider.php file, you have not created a "User" panel as mentioned in the documentation.

Make sure to follow all directions in the documentation for this package. After creating a "User" panel, you must also configure this setting in the FilamentCompaniesServiceProvider. Here is the snippet from the documentation:

You may change the value used for the User Panel using the id of the panel:

use Filament\Panel;
use Wallo\FilamentCompanies\FilamentCompanies;

class FilamentCompaniesServiceProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            // ...
            ->plugin(
                FilamentCompanies::make()
                    ->userPanel('user')
            )
    }
}

In your FilamentCompaniesServiceProvider you registered the company panel as the User panel. You cannot do this. Your User panel must be separate from the Company panel.

eelco2k commented 6 months ago

@andrewdwallo i'm editing this file only for finding the bug purposes.... the file i mean is: vendor/andrewdwallo/filament-companies/src/Http/Controllers/OAuthController.php

there i temporary added these lines just before the $this->registrationUrl = Filament::getRegistrationUrl();

and runned the php artisan route:list command with php Xdebug and set some breakpoints. I just want to figure out why $this->registrationUrl = Filament::getRegistrationUrl(); stays NULL.

and what i'm trying to explain is that i only get 2 times with the breakpoints in this file and the Filament::getCurrenPanel() is NOT one of the filament company panel names.

So i tried putting breakpoints earlier in the dispatcher and i ended up here right before the code execution went to the OAuthController.php. Which is here:

vendor/laravel/framework/src/illuminate/foundation/console/Kernel.php
return $this->getArtisan()->run($input, $output); line.

The code execution goes two times through the __construct() of the OAuthController where both the panel was my central-admin panelId name.

And yes that panel does not hold any registration() in that panel. in the company panel there is a ->registration(Register::class) but like i said the __construct does get executed twice but no Filament::getCurrentPanel() == 'company'.

What i think is that via the console command this OAuthController.php gets instantiated but this does not need to get instanciated... or at least coupled to the wrong panel... not sure what's going on...

eelco2k commented 6 months ago

and even when using the plugin for all the other panels with only:

TenantPortalPanelProvider.php

 public function panel(Panel $panel): Panel
    {
        return $panel
            ->id('tenant-portal')
            ->plugins([
                  FilamentCompanies::make()->userPanel('tenant-portal'),
                 ]);
    }
TenantAdminPanelProvider.php

 public function panel(Panel $panel): Panel
    {
        return $panel
            ->id('tenant-admin')
            ->plugins([
                  FilamentCompanies::make()->userPanel('tenant-admin'),
                 ]);
    }

and the CompaniesPanelProvider.php

class CompaniesPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->id('company')
            ->path('company')
            ->default()
            ->login(Login::class)
            ->passwordReset()
            ->registration(Register::class)
            ->plugins([
            FilamentCompanies::make()
                ->switchCurrentCompany()
                ->updateProfileInformation()
                ->updatePasswords()
                ->setPasswords()
                ->connectedAccounts()
                ->manageBrowserSessions()
                ->accountDeletion()
                ->profilePhotos()
                ->api()
                ->companies(invitations: true)
                ->termsAndPrivacyPolicy()
                ->notifications()
                ->modals()
                ->socialite(
                    providers: [Providers::github()],
                    features: [Socialite::rememberSession(), Socialite::providerAvatars()]
                ),
       ]);
}

Same problem with the console command. logging normally in every portal gives me no errors... works flawlessly.

eelco2k commented 6 months ago

is it then such big of an issue to change the $registrationUrl; in OAuthController.php to a nullable parameter?

like so:

class OAuthController extends Controller
{
    protected ?string $registrationUrl;

instead of

class OAuthController extends Controller
{
    protected string $registrationUrl;
andrewdwallo commented 6 months ago

No I suppose its not a big deal to change it, but there is a reason why it is not working for you supposedly. Because you aren't using the plugin as intended. There is a reason why there has not been a similar issue posted in 1 1/2 years until now.

You can't register the plugin in multiple panels. Period.

eelco2k commented 6 months ago

okay got the point. Thanks. will close issue.

andrewdwallo commented 6 months ago

@eelco2k I will make it nullable in the next release, but I still suggest that you only register it in one panel.