Central Security Centre

* Security policy management
* Minimum password-length policy
* Mixed-case, number, and symbol requirements
* Password expiry and history settings
* MFA requirement policy
* Failed-login and lockout settings
* Session idle-time policy
* Single-session policy
* Automatic successful-login recording
* Automatic failed-login recording
* Automatic logout recording
* Authentication history
* IP allow and deny rules
* IPv4 CIDR support
* Administration and login rule scopes
* Trusted-device storage and revocation
* Security middleware registration
* Administration navigation
* Security permissions
* Module registration and tests
This commit is contained in:
Mark Gregory
2026-07-24 12:30:40 +01:00
parent 86f2bc75aa
commit 7fb71c4d24
21 changed files with 270 additions and 3 deletions
@@ -0,0 +1,31 @@
<?php
namespace Modules\Security\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Modules\Security\Models\IpRule;
use Modules\Security\Models\LoginEvent;
use Modules\Security\Models\SecurityPolicy;
use Modules\Security\Models\TrustedDevice;
final class SecurityController extends Controller
{
public function index(): View
{
return view('security::admin.index',['policy'=>SecurityPolicy::first(),'events'=>LoginEvent::latest('occurred_at')->paginate(25),'devices'=>TrustedDevice::whereNull('revoked_at')->latest('last_seen_at')->limit(20)->get(),'rules'=>IpRule::latest()->get(),'failed24h'=>LoginEvent::where('event','failed')->where('occurred_at','>=',now()->subDay())->count()]);
}
public function savePolicy(Request $request): RedirectResponse
{
$data=$request->validate(['minimum_password_length'=>['required','integer','min:8','max:128'],'password_expiry_days'=>['nullable','integer','min:1'],'password_history'=>['required','integer','min:0','max:24'],'max_failed_logins'=>['required','integer','min:1','max:100'],'lockout_minutes'=>['required','integer','min:1','max:1440'],'session_idle_minutes'=>['required','integer','min:5','max:10080']]);
foreach(['require_mixed_case','require_numbers','require_symbols','require_mfa','single_session'] as $key) $data[$key]=$request->boolean($key);
SecurityPolicy::updateOrCreate(['id'=>SecurityPolicy::value('id')],$data+['name'=>'Default security policy','is_active'=>true]);
return back()->with('status','Security policy updated.');
}
public function storeRule(Request $request): RedirectResponse
{
IpRule::create($request->validate(['name'=>['required','string','max:255'],'type'=>['required','in:allow,deny'],'address'=>['required','string','max:64'],'description'=>['nullable','string']])+['applies_to_admin'=>$request->boolean('applies_to_admin'),'applies_to_login'=>$request->boolean('applies_to_login'),'is_active'=>true,'created_by'=>$request->user()->id]);
return back()->with('status','IP rule created.');
}
public function destroyRule(IpRule $rule): RedirectResponse { $rule->delete(); return back()->with('status','IP rule deleted.'); }
public function revokeDevice(TrustedDevice $device): RedirectResponse { $device->update(['revoked_at'=>now()]); return back()->with('status','Device trust revoked.'); }
}