diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 831cf42..09b765f 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -3,6 +3,8 @@ namespace App\Http\Controllers; use App\Models\User; +use App\Models\App; +use App\Models\AppRestriction; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -18,14 +20,112 @@ public function index() // Fetch all users to display on the dashboard for administration $users = User::orderBy('created_at', 'desc')->get(); + // Fetch all apps and active restrictions + $apps = App::orderBy('name', 'asc')->get(); + $restrictions = AppRestriction::with('app')->orderBy('created_at', 'desc')->get(); + // Calculate statistics $stats = [ 'total_users' => User::count(), 'admins_count' => User::where('role', 'admin')->count(), - 'microsoft_users' => User::whereNotNull('microsoft_id')->count(), - 'credentials_users' => User::whereNull('microsoft_id')->count(), + 'blocked_count' => User::where('is_blocked', true)->count(), + 'apps_count' => App::count(), ]; - return view('admin.dashboard', compact('admin', 'users', 'stats')); + return view('admin.dashboard', compact('admin', 'users', 'apps', 'restrictions', 'stats')); + } + + /** + * Store a new app / service. + */ + public function storeApp(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:255', + 'url' => 'required|url|max:255', + 'icon_svg' => 'nullable|string', + 'color' => 'nullable|string|max:20', + 'desc' => 'nullable|string', + 'tag' => 'nullable|string|max:255', + ]); + + App::create([ + 'name' => $request->name, + 'url' => $request->url, + 'icon_svg' => $request->icon_svg, + 'color' => $request->color ?? '#4f46e5', + 'desc' => $request->desc, + 'tag' => $request->tag ?? 'General', + ]); + + return redirect()->route('admin.dashboard')->with('success', 'Service app registered successfully.'); + } + + /** + * Delete an app / service. + */ + public function destroyApp($id) + { + $app = App::findOrFail($id); + $app->delete(); + + return redirect()->route('admin.dashboard')->with('success', 'Service app deleted successfully.'); + } + + /** + * Restrict an app for a specific user email. + */ + public function storeRestriction(Request $request) + { + $request->validate([ + 'email' => 'required|email|max:255', + 'app_id' => 'required|exists:apps,id', + ]); + + // Prevent duplicate restrictions + $exists = AppRestriction::where('email', $request->email) + ->where('app_id', $request->app_id) + ->exists(); + + if ($exists) { + return redirect()->route('admin.dashboard')->with('error', 'This user is already restricted from accessing this service.'); + } + + AppRestriction::create([ + 'email' => $request->email, + 'app_id' => $request->app_id, + ]); + + return redirect()->route('admin.dashboard')->with('success', 'Access restriction created successfully.'); + } + + /** + * Remove an app restriction. + */ + public function destroyRestriction($id) + { + $restriction = AppRestriction::findOrFail($id); + $restriction->delete(); + + return redirect()->route('admin.dashboard')->with('success', 'Access restriction removed successfully.'); + } + + /** + * Toggle the blocked status of a user. + */ + public function toggleBlockUser(Request $request, $id) + { + $user = User::findOrFail($id); + + // Security: Prevent blocking oneself + if ($user->id === Auth::id()) { + return redirect()->route('admin.dashboard')->with('error', 'Security violation: You cannot block your own administrator account.'); + } + + $user->is_blocked = !$user->is_blocked; + $user->save(); + + $status = $user->is_blocked ? 'blocked' : 'unblocked'; + return redirect()->route('admin.dashboard')->with('success', "User account has been {$status} successfully."); } } diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index e03d35a..930071c 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -14,33 +14,13 @@ public function index() { $user = Auth::user(); - // Service list with URLs and aesthetic tags - $services = [ - [ - 'name' => 'Outlook', - 'url' => 'https://outlook.office.com/mail/', - 'icon_svg' => 'outlook', - 'color' => '#0078d4', - 'desc' => 'Access your corporate mail, calendar, and contacts instantly.', - 'tag' => 'Microsoft 365' - ], - [ - 'name' => 'Microsoft Teams', - 'url' => 'https://teams.microsoft.com/v2/', - 'icon_svg' => 'teams', - 'color' => '#6264a7', - 'desc' => 'Chat, meet, call, and collaborate with your team.', - 'tag' => 'Microsoft 365' - ], - [ - 'name' => 'Keka HR', - 'url' => 'https://sentientgeeks.keka.com/', - 'icon_svg' => 'keka', - 'color' => '#f27059', - 'desc' => 'Manage timesheets, leaves, payroll, and performance tasks.', - 'tag' => 'HR Portal' - ], - ]; + // Get all apps that are NOT restricted for this user's email + $services = \App\Models\App::whereNotExists(function ($query) use ($user) { + $query->select(\DB::raw(1)) + ->from('app_restrictions') + ->whereColumn('app_restrictions.app_id', 'apps.id') + ->where('app_restrictions.email', $user->email); + })->orderBy('name', 'asc')->get(); return view('dashboard', compact('user', 'services')); } diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php index 3bfea73..bb91cde 100644 --- a/app/Http/Controllers/LoginController.php +++ b/app/Http/Controllers/LoginController.php @@ -22,6 +22,18 @@ public function showLoginForm() return view('auth.login'); } + /** + * Show the administrator login screen. + */ + public function showAdminLoginForm() + { + if (Auth::check()) { + return $this->redirectUserBasedOnRole(Auth::user()); + } + + return view('auth.admin-login'); + } + /** * Handle Email & Password login for Administrators. */ @@ -88,14 +100,20 @@ public function handleMicrosoftCallback(Request $request) return redirect()->route('login')->with('error', 'Microsoft Authentication failed or was cancelled: ' . $e->getMessage()); } + $email = $microsoftUser->getEmail(); + $isSentientGeeks = str_ends_with($email, '@sentientgeeks.com') || str_ends_with($email, '@sentientgeeks.in'); + // Search for user by microsoft_id first, then by email $user = User::where('microsoft_id', $microsoftUser->getId())->first(); if (!$user) { - $user = User::where('email', $microsoftUser->getEmail())->first(); + $user = User::where('email', $email)->first(); } if ($user) { + if ($user->is_blocked) { + return redirect()->route('login')->with('error', 'Your account has been blocked. Please contact the administrator.'); + } // Update existing user with Microsoft data if not already set $user->update([ 'microsoft_id' => $microsoftUser->getId(), @@ -104,10 +122,15 @@ public function handleMicrosoftCallback(Request $request) 'avatar' => $microsoftUser->getAvatar() ?? $user->avatar, ]); } else { + // Block registration if domain is not allowed + if (!$isSentientGeeks) { + return redirect()->route('login')->with('error', 'Access denied. Only @sentientgeeks.com and @sentientgeeks.in users are allowed to register.'); + } + // Create a new User (defaults to 'user' role) $user = User::create([ - 'name' => $microsoftUser->getName() ?? explode('@', $microsoftUser->getEmail())[0], - 'email' => $microsoftUser->getEmail(), + 'name' => $microsoftUser->getName() ?? explode('@', $email)[0], + 'email' => $email, 'microsoft_id' => $microsoftUser->getId(), 'microsoft_token' => $microsoftUser->token, 'microsoft_refresh_token' => $microsoftUser->refreshToken ?? null, diff --git a/app/Http/Middleware/EnsureRole.php b/app/Http/Middleware/EnsureRole.php index ff0015b..0d81c56 100644 --- a/app/Http/Middleware/EnsureRole.php +++ b/app/Http/Middleware/EnsureRole.php @@ -19,6 +19,13 @@ public function handle(Request $request, Closure $next, string $role): Response return redirect()->route('login')->with('error', 'Please log in to access this page.'); } + if (auth()->user()->is_blocked) { + auth()->logout(); + $request->session()->invalidate(); + $request->session()->regenerateToken(); + return redirect()->route('login')->with('error', 'Your account has been blocked. Please contact the administrator.'); + } + if (auth()->user()->role !== $role) { if (auth()->user()->role === 'admin') { return redirect()->route('admin.dashboard'); diff --git a/app/Models/App.php b/app/Models/App.php new file mode 100644 index 0000000..2b85d8e --- /dev/null +++ b/app/Models/App.php @@ -0,0 +1,21 @@ +hasMany(AppRestriction::class, 'app_id'); + } +} diff --git a/app/Models/AppRestriction.php b/app/Models/AppRestriction.php new file mode 100644 index 0000000..a0522ba --- /dev/null +++ b/app/Models/AppRestriction.php @@ -0,0 +1,21 @@ +belongsTo(App::class, 'app_id'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index ea953fd..02ace5d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -10,7 +10,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; -#[Fillable(['name', 'email', 'password', 'role', 'microsoft_id', 'microsoft_token', 'microsoft_refresh_token', 'avatar'])] +#[Fillable(['name', 'email', 'password', 'role', 'microsoft_id', 'microsoft_token', 'microsoft_refresh_token', 'avatar', 'is_blocked'])] #[Hidden(['password', 'remember_token'])] class User extends Authenticatable { diff --git a/database/migrations/2026_06_23_091509_add_is_blocked_to_users_table.php b/database/migrations/2026_06_23_091509_add_is_blocked_to_users_table.php new file mode 100644 index 0000000..e66cf56 --- /dev/null +++ b/database/migrations/2026_06_23_091509_add_is_blocked_to_users_table.php @@ -0,0 +1,28 @@ +boolean('is_blocked')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('is_blocked'); + }); + } +}; diff --git a/database/migrations/2026_06_23_091722_create_apps_table.php b/database/migrations/2026_06_23_091722_create_apps_table.php new file mode 100644 index 0000000..45225a7 --- /dev/null +++ b/database/migrations/2026_06_23_091722_create_apps_table.php @@ -0,0 +1,33 @@ +id(); + $table->string('name'); + $table->string('url'); + $table->longText('icon_svg')->nullable(); + $table->string('color')->default('#4f46e5'); + $table->text('desc')->nullable(); + $table->string('tag')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('apps'); + } +}; diff --git a/database/migrations/2026_06_23_091731_create_app_restrictions_table.php b/database/migrations/2026_06_23_091731_create_app_restrictions_table.php new file mode 100644 index 0000000..e3a3741 --- /dev/null +++ b/database/migrations/2026_06_23_091731_create_app_restrictions_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('email'); + $table->foreignId('app_id')->constrained('apps')->onDelete('cascade'); + $table->unique(['email', 'app_id']); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('app_restrictions'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 17799db..2213a5b 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -36,5 +36,39 @@ public function run(): void 'microsoft_id' => 'mock-microsoft-id-123', ] ); + + // Seed default apps + \App\Models\App::updateOrCreate( + ['name' => 'Outlook'], + [ + 'url' => 'https://outlook.office.com/mail/', + 'icon_svg' => 'outlook', + 'color' => '#0078d4', + 'desc' => 'Access your corporate mail, calendar, and contacts instantly.', + 'tag' => 'Microsoft 365' + ] + ); + + \App\Models\App::updateOrCreate( + ['name' => 'Microsoft Teams'], + [ + 'url' => 'https://teams.microsoft.com/v2/', + 'icon_svg' => 'teams', + 'color' => '#6264a7', + 'desc' => 'Chat, meet, call, and collaborate with your team.', + 'tag' => 'Microsoft 365' + ] + ); + + \App\Models\App::updateOrCreate( + ['name' => 'Keka HR'], + [ + 'url' => 'https://sentientgeeks.keka.com/', + 'icon_svg' => 'keka', + 'color' => '#f27059', + 'desc' => 'Manage timesheets, leaves, payroll, and performance tasks.', + 'tag' => 'HR Portal' + ] + ); } } diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index c783238..19c985e 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -423,27 +423,192 @@
| Service | +Tag | +URL | +Action | +
|---|---|---|---|
|
+
+
+ {{ $app->name }}
+
+ |
+ {{ $app->tag }} | ++ | + + | +
| No registered service apps. | +|||
| User Email | +Restricted App | +Action | +
|---|---|---|
| {{ $res->email }} | ++ @if($res->app) + + + {{ $res->app->name }} + + @else + App Deleted + @endif + | ++ + | +
| No active restrictions. All apps visible to all users. | +||
{{ $error }}
+ @endforeach +