Skip to content

Commit 7f4e29c

Browse files
committed
feat: implement user access controls for frontend panel
1 parent 6999906 commit 7f4e29c

13 files changed

Lines changed: 478 additions & 67 deletions

File tree

config/eclipse-frontend.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
return [
4+
5+
/*
6+
|--------------------------------------------------------------------------
7+
| Multi-tenancy config
8+
|--------------------------------------------------------------------------
9+
*/
10+
'tenancy' => [
11+
'enabled' => false,
12+
'model' => null,
13+
'foreign_key' => null,
14+
],
15+
];

config/frontend-panel.php

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace Eclipse\Frontend\Http\Middleware;
4+
5+
use Closure;
6+
use Eclipse\Core\Services\Registry;
7+
use Exception;
8+
use Filament\Facades\Filament;
9+
use Illuminate\Http\Request;
10+
use Illuminate\Support\Facades\DB;
11+
use Symfony\Component\HttpFoundation\Response;
12+
13+
class CheckFrontendFeatures
14+
{
15+
public function handle(Request $request, Closure $next): Response
16+
{
17+
$routeName = $request->route()?->getName();
18+
19+
if (! $routeName) {
20+
return $next($request);
21+
}
22+
23+
$site = Registry::getSite();
24+
25+
if (! $site) {
26+
return $next($request);
27+
}
28+
29+
if ($this->isAuthRoute($routeName) && ! $this->getSetting($site->id, 'enable_logins')) {
30+
abort(404);
31+
}
32+
33+
if ($this->isRegisterRoute($routeName) && ! $this->getSetting($site->id, 'allow_registration')) {
34+
abort(404);
35+
}
36+
37+
if (! $this->isAuthRoute($routeName) && ! $this->isRegisterRoute($routeName)) {
38+
$guestAccess = $this->getSetting($site->id, 'guest_access');
39+
$enableLogins = $this->getSetting($site->id, 'enable_logins');
40+
41+
if (! $guestAccess && ! Filament::auth()->check()) {
42+
if ($enableLogins) {
43+
return redirect()->route('filament.frontend.auth.login');
44+
}
45+
46+
abort(404);
47+
}
48+
}
49+
50+
return $next($request);
51+
}
52+
53+
private function isAuthRoute(string $routeName): bool
54+
{
55+
return str_contains($routeName, '.auth.login')
56+
|| str_contains($routeName, '.auth.password-reset');
57+
}
58+
59+
private function isRegisterRoute(string $routeName): bool
60+
{
61+
return str_contains($routeName, '.auth.register');
62+
}
63+
64+
private function getSetting(int $siteId, string $settingName): bool
65+
{
66+
try {
67+
$value = DB::table('settings')
68+
->where('group', 'frontend')
69+
->where('name', $settingName)
70+
->where('site_id', $siteId)
71+
->value('payload');
72+
73+
return $value !== null ? json_decode($value) : true;
74+
} catch (Exception $e) {
75+
return true;
76+
}
77+
}
78+
}

src/Pages/Auth/Login.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Eclipse\Frontend\Pages\Auth;
4+
5+
use Eclipse\Core\Services\Registry;
6+
use Filament\Facades\Filament;
7+
use Filament\Pages\Auth\Login as BaseLogin;
8+
use Illuminate\Support\Facades\DB;
9+
10+
class Login extends BaseLogin
11+
{
12+
public function mount(): void
13+
{
14+
if (! $this->allowRegistration()) {
15+
Filament::getCurrentPanel()?->registration(false);
16+
}
17+
18+
parent::mount();
19+
}
20+
21+
private function allowRegistration(): bool
22+
{
23+
try {
24+
$site = Registry::getSite();
25+
26+
if (! $site) {
27+
return true;
28+
}
29+
30+
$allowRegistration = DB::table('settings')
31+
->where('group', 'frontend')
32+
->where('name', 'allow_registration')
33+
->where('site_id', $site->id)
34+
->value('payload');
35+
36+
return $allowRegistration !== null ? json_decode($allowRegistration) : true;
37+
} catch (\Exception $e) {
38+
return true;
39+
}
40+
}
41+
}

src/Providers/FrontendPanelProvider.php

Lines changed: 27 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
namespace Eclipse\Frontend\Providers;
44

5-
use BezhanSalleh\FilamentShield\Middleware\SyncShieldTenant;
65
use Eclipse\Common\Providers\GlobalSearchProvider;
7-
use Eclipse\Core\Models\Site;
86
use Eclipse\Core\Services\Registry;
97
use Eclipse\Frontend\Filament\Pages as CustomPages;
10-
use Filament\Http\Middleware\Authenticate;
8+
use Eclipse\Frontend\Http\Middleware\CheckFrontendFeatures;
9+
use Eclipse\Frontend\Pages\Auth\Login;
10+
use Filament\Facades\Filament;
1111
use Filament\Http\Middleware\AuthenticateSession;
1212
use Filament\Http\Middleware\DisableBladeIconComponents;
1313
use Filament\Http\Middleware\DispatchServingFilamentEvent;
14-
use Filament\Pages;
1514
use Filament\Panel;
1615
use Filament\PanelProvider;
1716
use Filament\Support\Colors\Color;
@@ -43,28 +42,23 @@ public function panel(Panel $panel): Panel
4342
SubstituteBindings::class,
4443
DisableBladeIconComponents::class,
4544
DispatchServingFilamentEvent::class,
45+
CheckFrontendFeatures::class,
4646
];
4747

48-
$widgets = [];
49-
$pages = [];
48+
$widgets = [
49+
Widgets\AccountWidget::class,
50+
Widgets\FilamentInfoWidget::class,
51+
];
52+
53+
$pages = [
54+
CustomPages\Home::class,
55+
];
5056

51-
if ($this->allowGuestAccess()) {
52-
$middleware[] = AuthenticateSession::class;
53-
$pages[] = CustomPages\Home::class;
54-
} else {
55-
$widgets = array_merge($widgets, [
56-
Widgets\AccountWidget::class,
57-
Widgets\FilamentInfoWidget::class,
58-
]);
59-
$pages[] = Pages\Dashboard::class;
60-
}
57+
$middleware[] = AuthenticateSession::class;
6158

6259
$panel
6360
->id(self::PANEL_ID)
6461
->path('')
65-
->login()
66-
->passwordReset()
67-
->emailVerification()
6862
->colors([
6963
'primary' => Color::Cyan,
7064
'gray' => Color::Slate,
@@ -83,33 +77,26 @@ public function panel(Panel $panel): Panel
8377
Platform::Mac => '⌘K',
8478
default => null,
8579
})
86-
->tenant(Site::class, slugAttribute: 'domain')
87-
->tenantDomain('{tenant:domain}')
88-
->tenantMiddleware([
89-
SyncShieldTenant::class,
90-
], isPersistent: true)
91-
->tenantMenu(false)
9280
->widgets($widgets)
9381
->middleware($middleware)
9482
->plugins(array_merge([
9583
EnvironmentIndicatorPlugin::make(),
9684
], app(Registry::class)->getPlugins()));
9785

98-
match ($this->allowGuestAccess()) {
99-
true => $panel
100-
->renderHook(
101-
PanelsRenderHook::TOPBAR_END,
102-
fn () => view('frontend-panel::filament.components.theme-switcher')
103-
),
104-
false => $panel
105-
->authMiddleware([
106-
Authenticate::class,
107-
])
108-
->renderHook(
109-
PanelsRenderHook::HEAD_START,
110-
fn (): string => self::getThemeIsolationScript(self::PANEL_ID)
111-
)
112-
};
86+
$panel
87+
->login(Login::class)
88+
->passwordReset()
89+
->registration();
90+
91+
$panel
92+
->renderHook(
93+
PanelsRenderHook::TOPBAR_END,
94+
fn () => ! Filament::auth()->check() ? view('frontend-panel::filament.components.theme-switcher') : ''
95+
)
96+
->renderHook(
97+
PanelsRenderHook::HEAD_START,
98+
fn (): string => self::getThemeIsolationScript(self::PANEL_ID)
99+
);
113100

114101
return $panel;
115102
}
@@ -133,9 +120,4 @@ private static function getThemeIsolationScript(string $panelId): string
133120
});
134121
</script>";
135122
}
136-
137-
private function allowGuestAccess(): bool
138-
{
139-
return config('frontend-panel.guest_access');
140-
}
141123
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
use Illuminate\Support\Facades\DB;
4+
5+
describe('Settings Management', function () {
6+
it('stores settings by site_id', function () {
7+
$siteModel = config('eclipse-frontend.tenancy.model');
8+
$site1 = $siteModel::factory()->create(['domain' => 'site1.test']);
9+
$site2 = $siteModel::factory()->create(['domain' => 'site2.test']);
10+
11+
setSetting($site1->id, 'guest_access', false);
12+
setSetting($site2->id, 'guest_access', true);
13+
14+
$setting1 = DB::table('settings')
15+
->where('group', 'frontend')
16+
->where('name', 'guest_access')
17+
->where('site_id', $site1->id)
18+
->value('payload');
19+
20+
$setting2 = DB::table('settings')
21+
->where('group', 'frontend')
22+
->where('name', 'guest_access')
23+
->where('site_id', $site2->id)
24+
->value('payload');
25+
26+
expect(json_decode($setting1))->toBeFalse()
27+
->and(json_decode($setting2))->toBeTrue();
28+
});
29+
30+
it('allows different sites to have different access control settings', function () {
31+
$siteModel = config('eclipse-frontend.tenancy.model');
32+
$site1 = $siteModel::where('domain', 'singa.lndo.site')->first();
33+
$site2 = $siteModel::factory()->create(['domain' => 'site2.test']);
34+
35+
setSetting($site1->id, 'enable_logins', true);
36+
setSetting($site1->id, 'allow_registration', false);
37+
38+
setSetting($site2->id, 'enable_logins', false);
39+
setSetting($site2->id, 'allow_registration', true);
40+
41+
$logins1 = json_decode(DB::table('settings')
42+
->where('site_id', $site1->id)
43+
->where('name', 'enable_logins')
44+
->value('payload'));
45+
46+
$registration1 = json_decode(DB::table('settings')
47+
->where('site_id', $site1->id)
48+
->where('name', 'allow_registration')
49+
->value('payload'));
50+
51+
$logins2 = json_decode(DB::table('settings')
52+
->where('site_id', $site2->id)
53+
->where('name', 'enable_logins')
54+
->value('payload'));
55+
56+
$registration2 = json_decode(DB::table('settings')
57+
->where('site_id', $site2->id)
58+
->where('name', 'allow_registration')
59+
->value('payload'));
60+
61+
expect($logins1)->toBeTrue()
62+
->and($registration1)->toBeFalse()
63+
->and($logins2)->toBeFalse()
64+
->and($registration2)->toBeTrue();
65+
});
66+
67+
it('updates existing settings', function () {
68+
$siteModel = config('eclipse-frontend.tenancy.model');
69+
$site = $siteModel::where('domain', 'singa.lndo.site')->first();
70+
71+
setSetting($site->id, 'guest_access', true);
72+
$first = json_decode(DB::table('settings')
73+
->where('site_id', $site->id)
74+
->where('name', 'guest_access')
75+
->value('payload'));
76+
77+
setSetting($site->id, 'guest_access', false);
78+
$second = json_decode(DB::table('settings')
79+
->where('site_id', $site->id)
80+
->where('name', 'guest_access')
81+
->value('payload'));
82+
83+
expect($first)->toBeTrue()
84+
->and($second)->toBeFalse();
85+
});
86+
});

0 commit comments

Comments
 (0)