Initial commit
This commit is contained in:
168
app/Providers/AppServiceProvider.php
Normal file
168
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Listeners\SendEmailNewMessage;
|
||||
use App\Models\Bank;
|
||||
use App\Models\Call;
|
||||
use App\Models\Post;
|
||||
use App\Models\Transaction;
|
||||
use App\Observers\BankObserver;
|
||||
use App\Observers\CallObserver;
|
||||
use App\Observers\PostObserver;
|
||||
use App\Observers\TransactionObserver;
|
||||
use App\Services\PresenceService;
|
||||
use App\View\Components\GuestLayout;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Vite;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Scout\EngineManager;
|
||||
use Livewire\Livewire;
|
||||
use Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->bind(
|
||||
'Matchish\ScoutElasticSearch\ElasticSearch\EloquentHitsIteratorAggregate',
|
||||
'app\Overrides\Matchish\ScoutElasticSearch\ElasticSearch\EloquentHitsIteratorAggregate'
|
||||
);
|
||||
|
||||
|
||||
|
||||
// Register custom Blade directive to conditionally check on activeProfile type (User, Organization, Bank, Admin)
|
||||
Blade::directive('profile', function ($expression) {
|
||||
return "<?php if (strtolower(getActiveProfileType()) === strtolower($expression)): ?>";
|
||||
});
|
||||
|
||||
Blade::directive('endprofile', function () {
|
||||
return "<?php endif; ?>";
|
||||
});
|
||||
|
||||
// Register PresenceService
|
||||
$this->app->singleton(PresenceService::class);
|
||||
|
||||
// Register custom SocialShare service to support custom icons
|
||||
$this->app->bind(\Enflow\SocialShare\SocialShare::class, \App\Services\CustomSocialShare::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
// Fix manifest not found error: enforce the correct location of the manifest.json file
|
||||
Vite::useManifestFilename('.vite/manifest.json');
|
||||
|
||||
// Fix 'Specified key was too long error' when storing emoji's as string in DB.
|
||||
Schema::defaultStringLength(191);
|
||||
|
||||
|
||||
if (!Collection::hasMacro('paginate')) {
|
||||
|
||||
Collection::macro(
|
||||
'paginate',
|
||||
function ($perPage = 15, $page = null, $options = []) {
|
||||
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
|
||||
return (new LengthAwarePaginator(
|
||||
$this->forPage($page, $perPage),
|
||||
$this->count(),
|
||||
$perPage,
|
||||
$page,
|
||||
$options
|
||||
))
|
||||
->withPath('');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Manual Elasticsearch driver registration
|
||||
resolve(EngineManager::class)->extend('matchish-elasticsearch', function ($app) {
|
||||
return resolve(ElasticSearchEngine::class);
|
||||
});
|
||||
|
||||
// Register Guest Layout
|
||||
Blade::component('guest-layout', GuestLayout::class);
|
||||
|
||||
|
||||
// Register lay-outs depending ob authorization
|
||||
Blade::directive('layout', function () {
|
||||
return Auth::check() ? 'app-layout' : 'guest-layout';
|
||||
|
||||
});
|
||||
|
||||
|
||||
Blade::if('usercan', function ($permission) {
|
||||
// Get the web user (all permissions are assigned to Users, not to Organizations/Banks/Admins)
|
||||
$webUser = Auth::guard('web')->user();
|
||||
if (!$webUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use request-level cache to prevent recursion during view rendering
|
||||
static $permissionCache = [];
|
||||
$cacheKey = $webUser->id . '_' . $permission;
|
||||
|
||||
if (isset($permissionCache[$cacheKey])) {
|
||||
return $permissionCache[$cacheKey];
|
||||
}
|
||||
|
||||
// Permissions are always checked on the web user (guard 'web')
|
||||
// Organizations/Banks/Admins don't have their own permissions
|
||||
try {
|
||||
// Use can() method instead of hasPermissionTo to avoid loading all permissions
|
||||
$result = $webUser->can($permission);
|
||||
$permissionCache[$cacheKey] = $result;
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
// Any error - return false and cache it
|
||||
$permissionCache[$cacheKey] = false;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Override the vendor Wirechat components with custom extended ones
|
||||
Livewire::component('wirechat.chat', \App\Http\Livewire\WireChat\Chat\Chat::class);
|
||||
Livewire::component('wirechat.chats', \App\Http\Livewire\WireChat\Chats\Chats::class);
|
||||
Livewire::component('wirechat.new.chat', \App\Http\Livewire\WireChat\New\Chat::class);
|
||||
|
||||
// Register WireChat event listener
|
||||
Event::listen(
|
||||
\Namu\WireChat\Events\MessageCreated::class,
|
||||
SendEmailNewMessage::class,
|
||||
);
|
||||
|
||||
Livewire::component('wire-chat.typing-indicator', \App\Http\Livewire\WireChat\TypingIndicator::class);
|
||||
|
||||
// Register Bank observer to auto-register new banks as love reacters/reactables
|
||||
Bank::observe(BankObserver::class);
|
||||
|
||||
// Register Post observer to auto-register new posts as love reactables
|
||||
Post::observe(PostObserver::class);
|
||||
|
||||
// Register Call observer to auto-register new calls as love reactables
|
||||
Call::observe(CallObserver::class);
|
||||
|
||||
// Pause calls when spending profile runs out of credits after a transaction
|
||||
Transaction::observe(TransactionObserver::class);
|
||||
|
||||
// Register Conversation observer to auto-set disappearing_started_at
|
||||
\Namu\WireChat\Models\Conversation::observe(\App\Observers\ConversationObserver::class);
|
||||
}
|
||||
}
|
||||
109
app/Providers/AuthServiceProvider.php
Normal file
109
app/Providers/AuthServiceProvider.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Auth\DockerSessionGuard;
|
||||
use App\Models\Bank;
|
||||
use App\Policies\BankPolicy;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any authentication / authorization services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
// Set remember me duration from platform configuration
|
||||
$rememberMeDays = timebank_config('auth.remember_me_days', 90);
|
||||
$rememberMeMinutes = $rememberMeDays * 24 * 60; // Convert days to minutes
|
||||
|
||||
// Use custom guard in Docker that doesn't migrate sessions
|
||||
if (env('IS_DOCKER', false)) {
|
||||
Auth::extend('session', function ($app, $name, array $config) use ($rememberMeMinutes) {
|
||||
$provider = Auth::createUserProvider($config['provider']);
|
||||
$guard = new DockerSessionGuard($name, $provider, $app['session.store']);
|
||||
|
||||
// Set the cookie jar on the guard
|
||||
$guard->setCookieJar($app['cookie']);
|
||||
|
||||
// If a request is available, set it on the guard
|
||||
if (method_exists($guard, 'setRequest')) {
|
||||
$guard->setRequest($app->refresh('request', $guard, 'setRequest'));
|
||||
}
|
||||
|
||||
// Set remember me duration
|
||||
$guard->setRememberDuration($rememberMeMinutes);
|
||||
|
||||
return $guard;
|
||||
});
|
||||
}
|
||||
|
||||
// Set remember me duration for all standard guards
|
||||
foreach (['web', 'organization', 'bank', 'admin'] as $guardName) {
|
||||
$guard = Auth::guard($guardName);
|
||||
if (method_exists($guard, 'setRememberDuration')) {
|
||||
$guard->setRememberDuration($rememberMeMinutes);
|
||||
}
|
||||
}
|
||||
|
||||
// Spatie Laravel-Permissions:
|
||||
// Implicitly grant "Super-Admin" role all permission checks using can()
|
||||
Gate::before(function ($user, $ability) {
|
||||
if ($user->hasRole('Super-Admin')) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Add these explicit gate definitions:
|
||||
// These gates check permissions directly via Spatie's permission system
|
||||
Gate::define('manage banks', function ($user) {
|
||||
try {
|
||||
// Always check on web guard since that's where permissions are stored
|
||||
if ($user instanceof \App\Models\User) {
|
||||
return $user->hasPermissionTo('manage banks', 'web');
|
||||
}
|
||||
return false;
|
||||
} catch (\Spatie\Permission\Exceptions\PermissionDoesNotExist $e) {
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
Gate::define('manage organizations', function ($user) {
|
||||
try {
|
||||
// Always check on web guard since that's where permissions are stored
|
||||
if ($user instanceof \App\Models\User) {
|
||||
return $user->hasPermissionTo('manage organizations', 'web');
|
||||
}
|
||||
return false;
|
||||
} catch (\Spatie\Permission\Exceptions\PermissionDoesNotExist $e) {
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
Gate::define('manage admins', function ($user) {
|
||||
try {
|
||||
// Always check on web guard since that's where permissions are stored
|
||||
if ($user instanceof \App\Models\User) {
|
||||
return $user->hasPermissionTo('manage admins', 'web');
|
||||
}
|
||||
return false;
|
||||
} catch (\Spatie\Permission\Exceptions\PermissionDoesNotExist $e) {
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
26
app/Providers/BroadcastServiceProvider.php
Normal file
26
app/Providers/BroadcastServiceProvider.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class BroadcastServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
// Broadcasting routes are now registered in routes/web.php within the localized route group
|
||||
|
||||
require base_path('routes/channels.php');
|
||||
|
||||
Broadcast::channel('switch-profile', function ($user) {
|
||||
return $user;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
77
app/Providers/EventServiceProvider.php
Normal file
77
app/Providers/EventServiceProvider.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Events\Auth\RegisteredByAdmin;
|
||||
use App\Events\ProfileSwitchEvent;
|
||||
use App\Events\ProfileVerified;
|
||||
use App\Listeners\Auth\SendAdminCreatedVerificationNotification;
|
||||
use App\Listeners\LogProfileSwitch;
|
||||
use App\Listeners\LoginSuccessful;
|
||||
use App\Listeners\ReactionCreatedListener;
|
||||
use App\Listeners\ReactionRemovedListener;
|
||||
use App\Listeners\SendEmailNewMessage;
|
||||
use App\Listeners\UpdateProfileEmailVerifiedAt;
|
||||
use Cog\Laravel\Love\Reaction\Events\ReactionHasBeenAdded;
|
||||
use Cog\Laravel\Love\Reaction\Events\ReactionHasBeenRemoved;
|
||||
use Illuminate\Auth\Events\Login;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The event listener mappings for the application.
|
||||
*
|
||||
* @var array<class-string, array<int, class-string>>
|
||||
*/
|
||||
protected $listen = [
|
||||
Registered::class => [
|
||||
SendEmailVerificationNotification::class,
|
||||
],
|
||||
|
||||
RegisteredByAdmin::class => [
|
||||
SendAdminCreatedVerificationNotification::class,
|
||||
],
|
||||
|
||||
Login::class => [
|
||||
LoginSuccessful::class
|
||||
],
|
||||
|
||||
ReactionHasBeenAdded::class => [
|
||||
ReactionCreatedListener::class
|
||||
],
|
||||
|
||||
ReactionHasBeenRemoved::class => [
|
||||
ReactionRemovedListener::class
|
||||
],
|
||||
|
||||
'Illuminate\Auth\Events\Logout' => [
|
||||
'App\Listeners\HandleUserLogout',
|
||||
],
|
||||
|
||||
// Log the date and ip of the profile that is switched to:
|
||||
// When the ProfileSwitchEvent triggers the LogProfileSwitch listens
|
||||
ProfileSwitchEvent::class => [
|
||||
LogProfileSwitch::class,
|
||||
],
|
||||
|
||||
// Email verification of profile models (organization's, banks, admins etc)
|
||||
ProfileVerified::class => [
|
||||
UpdateProfileEmailVerifiedAt::class,
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Register any events for your application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
147
app/Providers/FortifyServiceProvider.php
Normal file
147
app/Providers/FortifyServiceProvider.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Actions\Fortify\CreateNewUser;
|
||||
use App\Actions\Fortify\ResetUserPassword;
|
||||
use App\Actions\Fortify\UpdateUserPassword;
|
||||
use App\Actions\Fortify\UpdateUserProfileInformation;
|
||||
use App\Models\User;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Fortify\Fortify;
|
||||
use Laravel\Fortify\Http\Controllers\VerifyEmailController;
|
||||
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
|
||||
|
||||
class FortifyServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
// Disable Fortify route registration, a selection of the initial fortify routes are moved ot /routes/fortify.php.
|
||||
// Custom routes with the same name however are registered in web.php. This is done because Fortify does not allow to register routes with the same name.
|
||||
Fortify::ignoreRoutes();
|
||||
|
||||
Fortify::createUsersUsing(CreateNewUser::class);
|
||||
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
|
||||
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
|
||||
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
|
||||
|
||||
Fortify::verifyEmailView(fn () => view('auth.verify-email'));
|
||||
|
||||
RateLimiter::for('login', function (Request $request) {
|
||||
$loginIdentifier = (string) $request->input('name'); // Changed from $request->email to $request->input('name')
|
||||
return Limit::perMinute(10)->by($loginIdentifier.$request->ip());
|
||||
});
|
||||
|
||||
RateLimiter::for('two-factor', function (Request $request) {
|
||||
return Limit::perMinute(5)->by($request->session()->get('login.id'));
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Rehash Cyclos salted sha256 passwords on first login
|
||||
Fortify::authenticateUsing(function (Request $request) {
|
||||
// Attempt to find the user by email or name.
|
||||
$user = User::where('email', $request->name)
|
||||
->orWhere('name', $request->name)
|
||||
->first();
|
||||
|
||||
if (!$user) {
|
||||
info('Auth failed: User not identified with login identifier: ' . $request->auth);
|
||||
return null;
|
||||
}
|
||||
if ($user->isRemoved()) {
|
||||
info('Auth failed: User ' . $user->name . ' is marked as removed (soft-deleted).');
|
||||
return null;
|
||||
}
|
||||
|
||||
// If maintenance mode is enabled, check if user has admin relationship
|
||||
if (isMaintenanceMode()) {
|
||||
$hasAdminRelation = $user->admins()->exists();
|
||||
if (!$hasAdminRelation) {
|
||||
info('Auth failed: Maintenance mode is active and user ' . $user->name . ' does not have admin relationship.');
|
||||
session()->flash('maintenance_mode_active', true);
|
||||
throw ValidationException::withMessages([
|
||||
'name' => __('Login is currently disabled due to site maintenance. Only administrator accounts can access the site at this time. Please try again later.'),
|
||||
]);
|
||||
}
|
||||
info('Auth allowed during maintenance: User ' . $user->name . ' has admin relationship.');
|
||||
}
|
||||
|
||||
info('User identified: ' . $user->name . ' (ID: ' . $user->id . ') attempting login with identifier: ' . $request->auth);
|
||||
$passwordVerified = false;
|
||||
|
||||
// Check for Cyclos password first if salt exists
|
||||
if (!empty($user->cyclos_salt)) {
|
||||
info('Attempting Cyclos password verification for user: ' . $user->name);
|
||||
$concatenated = $user->cyclos_salt . $request->password;
|
||||
$hashedInputPassword = hash("sha256", $concatenated);
|
||||
|
||||
if (strtolower($hashedInputPassword) === strtolower($user->password)) {
|
||||
info('Cyclos password verified for user: ' . $user->name);
|
||||
// Prepare user model for update: rehash password and clear salt
|
||||
$user->password = Hash::make($request->password);
|
||||
$user->cyclos_salt = null;
|
||||
$passwordVerified = true;
|
||||
info('Cyclos password prepared for rehashing for user: ' . $user->name);
|
||||
} else {
|
||||
info('Cyclos password verification failed for user: ' . $user->name . '. Will attempt standard Laravel hash check.');
|
||||
}
|
||||
}
|
||||
|
||||
// If not verified by Cyclos, or if cyclos_salt was empty, try Laravel's default hash check
|
||||
if (!$passwordVerified && Hash::check($request->password, $user->password)) {
|
||||
info('Laravel password verified for user: ' . $user->name);
|
||||
$passwordVerified = true;
|
||||
}
|
||||
|
||||
if ($passwordVerified) {
|
||||
info('Auth success for user: ' . $user->name);
|
||||
// Update last login details
|
||||
$now = Carbon::now()->toDateTimeString();
|
||||
$user->last_login_at = $now;
|
||||
$user->last_login_ip = $request->getClientIp();
|
||||
if (timebank_config('profile_inactive.re-activate_at_login')) {
|
||||
if (!$user->isActive()) {
|
||||
$user->inactive_at = null;
|
||||
$activated = true;
|
||||
}
|
||||
}
|
||||
// Save all changes (rehashed password, cleared salt, login details)
|
||||
$user->save();
|
||||
|
||||
if (isset($activated) && $activated) {
|
||||
info('User re-activated: ' . $user->name);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
info('Auth failed: Password mismatch for user: ' . $user->name . ' with identifier: ' . $request->auth);
|
||||
return null;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
100
app/Providers/JetstreamServiceProvider.php
Normal file
100
app/Providers/JetstreamServiceProvider.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Actions\Jetstream\DeleteUser;
|
||||
use App\Http\Livewire\ProfileUser\UpdateProfilePersonalForm;
|
||||
use App\Http\Livewire\Profile\DeleteUserForm;
|
||||
use App\Http\Livewire\Profile\TwoFactorAuthenticationForm;
|
||||
use App\Http\Livewire\Profile\UpdatePasswordForm;
|
||||
use App\Http\Livewire\Profile\UpdateProfilePhoneForm;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Fortify\Fortify;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
use Livewire\Livewire;
|
||||
|
||||
|
||||
class JetstreamServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->registerComponent('toaster');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
// Jetstream::ignoreRoutes(); // Completely disable Jetstream routes, stops Jetstream-specific routes (teams, profile, API tokens, etc.).
|
||||
|
||||
$this->configurePermissions();
|
||||
|
||||
Jetstream::deleteUsersUsing(DeleteUser::class);
|
||||
//TODO: fix this registration. Why is it registered here and like this?
|
||||
Livewire::component('profile-user.update-profile-personal-form', UpdateProfilePersonalForm::class);
|
||||
Livewire::component('profile-user.update-profile-phone-form', UpdateProfilePhoneForm::class);
|
||||
|
||||
// Register customized Jetstream DeleteUserForm
|
||||
Livewire::component('profile.delete-user-form', DeleteUserForm::class);
|
||||
|
||||
// CRITICAL SECURITY: Register custom TwoFactorAuthenticationForm with authorization checks
|
||||
// This overrides the vendor Jetstream component to prevent IDOR attacks
|
||||
Livewire::component('profile.two-factor-authentication-form', TwoFactorAuthenticationForm::class);
|
||||
|
||||
// CRITICAL SECURITY: Register custom UpdatePasswordForm with authorization checks
|
||||
// This overrides the vendor Jetstream component to prevent unauthorized password changes
|
||||
Livewire::component('profile.update-password-form', UpdatePasswordForm::class);
|
||||
|
||||
|
||||
// Register LoginResponse for conditional redirects in Http/Responses/LoginResponse.php
|
||||
// This is used to load language preference after login
|
||||
$this->app->singleton(
|
||||
\Laravel\Fortify\Contracts\LoginResponse::class,
|
||||
\App\Http\Responses\LoginResponse::class
|
||||
);
|
||||
|
||||
// Register TwofactorLoginResponse for conditional redirects in Http/Responses/LoginResponse.php
|
||||
// This is used to load language preference after login
|
||||
$this->app->singleton(
|
||||
\Laravel\Fortify\Contracts\TwoFactorLoginResponse::class,
|
||||
\App\Http\Responses\LoginResponse::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the permissions that are available within the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configurePermissions()
|
||||
{
|
||||
Jetstream::defaultApiTokenPermissions(['read']);
|
||||
|
||||
Jetstream::permissions([
|
||||
'create',
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function registerComponent(string $component)
|
||||
{
|
||||
Blade::component('components.' . $component, $component);
|
||||
}
|
||||
|
||||
}
|
||||
32
app/Providers/MailBounceServiceProvider.php
Normal file
32
app/Providers/MailBounceServiceProvider.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Mail\MailpitCopyListener;
|
||||
use App\Mail\UniversalBounceHandler;
|
||||
use Illuminate\Mail\Events\MessageSending;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class MailBounceServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(UniversalBounceHandler::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
// Copy to Mailpit first (before bounce handler which may return false and halt propagation)
|
||||
Event::listen(MessageSending::class, [MailpitCopyListener::class, 'handle']);
|
||||
|
||||
// Register the universal bounce handler for all outgoing emails
|
||||
Event::listen(MessageSending::class, [UniversalBounceHandler::class, 'handle']);
|
||||
}
|
||||
}
|
||||
88
app/Providers/RouteServiceProvider.php
Normal file
88
app/Providers/RouteServiceProvider.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
use \Mcamara\LaravelLocalization\Traits\LoadsTranslatedCachedRoutes;
|
||||
|
||||
/**
|
||||
* The path to the "home" route for your application.
|
||||
*
|
||||
* This is used by Laravel authentication to redirect users after login.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
|
||||
public const HOME = '/main-page'; // Not used because of route localization
|
||||
|
||||
/**
|
||||
* Get the localized home route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function localizedHome()
|
||||
{
|
||||
return \Mcamara\LaravelLocalization\Facades\LaravelLocalization::localizeURL(self::HOME);
|
||||
}
|
||||
|
||||
/**
|
||||
* The controller namespace for the application.
|
||||
*
|
||||
* When present, controller route declarations will automatically be prefixed with this namespace.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
// protected $namespace = 'App\\Http\\Controllers';
|
||||
|
||||
/**
|
||||
* Define your route model bindings, pattern filters, etc.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->configureRateLimiting();
|
||||
|
||||
$this->routes(function () {
|
||||
Route::prefix('api')
|
||||
->middleware('api')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/api.php'));
|
||||
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(function () {
|
||||
require base_path('routes/web.php');
|
||||
require base_path('routes/fortify.php'); // <-- Fortify routes, with some routes disabled as they are customized and therefore in web.php
|
||||
require base_path('routes/test.php');
|
||||
});
|
||||
});
|
||||
|
||||
// Route parameter constraints:
|
||||
Route::pattern('id', '[0-9]+');
|
||||
|
||||
Broadcast::routes([
|
||||
'middleware' => ['auth.any:admin,bank,organization,web'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the rate limiters for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configureRateLimiting()
|
||||
{
|
||||
RateLimiter::for('api', function (Request $request) {
|
||||
return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
|
||||
});
|
||||
}
|
||||
}
|
||||
106
app/Providers/ScoutObserverServiceProvider.php
Normal file
106
app/Providers/ScoutObserverServiceProvider.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
// Create this file: app/Providers/ScoutObserverServiceProvider.php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Bank;
|
||||
use App\Models\Organization;
|
||||
use App\Models\Post;
|
||||
use App\Models\PostTranslation;
|
||||
use App\Models\User;
|
||||
use App\Observers\ScoutReindexObserver;
|
||||
use Elastic\Elasticsearch\ClientBuilder;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ScoutObserverServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register observers for Scout models
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
// Create a single observer instance to share across models
|
||||
$scoutObserver = new ScoutReindexObserver();
|
||||
|
||||
// Register observer for User model
|
||||
User::observe($scoutObserver);
|
||||
|
||||
// Register observer for Organization model
|
||||
Organization::observe($scoutObserver);
|
||||
|
||||
// Register observer for Bank model
|
||||
Bank::observe($scoutObserver);
|
||||
|
||||
// Register observer for Post model
|
||||
Post::observe($scoutObserver);
|
||||
|
||||
// TODO NEXT: why not re-indexing with frsh data?
|
||||
|
||||
// Add PostTranslation observer with custom logic
|
||||
PostTranslation::saved(function (PostTranslation $translation) {
|
||||
if ($translation->post_id) {
|
||||
$post = Post::find($translation->post_id);
|
||||
|
||||
\Log::info('Before searchable:', [
|
||||
'post_id' => $post->id,
|
||||
'translations_loaded' => $post->relationLoaded('translations'),
|
||||
'translation_count' => $post->translations->count(),
|
||||
'searchable_array' => $post->toSearchableArray()
|
||||
]);
|
||||
|
||||
$post->searchable();
|
||||
|
||||
// Wait a moment for Elasticsearch
|
||||
sleep(1);
|
||||
|
||||
// Correct way to get Elasticsearch client for Matchish package
|
||||
$hosts = config('elastic.client.default.hosts', ['localhost:9200']);
|
||||
|
||||
// Ensure hosts is always an array
|
||||
if (!is_array($hosts)) {
|
||||
$hosts = [$hosts];
|
||||
}
|
||||
|
||||
$client = ClientBuilder::create()
|
||||
->setHosts($hosts)
|
||||
->build();
|
||||
|
||||
try {
|
||||
$exists = $client->exists([
|
||||
'index' => $post->searchableAs(),
|
||||
'id' => $post->getScoutKey()
|
||||
]);
|
||||
|
||||
$indexedData = null;
|
||||
if ($exists) {
|
||||
$response = $client->get([
|
||||
'index' => $post->searchableAs(),
|
||||
'id' => $post->getScoutKey()
|
||||
]);
|
||||
$indexedData = $response['_source'] ?? null;
|
||||
}
|
||||
|
||||
\Log::info('After searchable:', [
|
||||
'exists_in_index' => $exists,
|
||||
'index_name' => $post->searchableAs(),
|
||||
'scout_key' => $post->getScoutKey(),
|
||||
'indexed_till_en' => $indexedData['post_translations']['till_en'] ?? 'not found'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Elasticsearch check failed', [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
PostTranslation::deleted(function (PostTranslation $translation) {
|
||||
if ($translation->post) {
|
||||
$translation->post->unsetRelation('translations');
|
||||
$translation->post->searchable();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
63
app/Providers/ThemeServiceProvider.php
Normal file
63
app/Providers/ThemeServiceProvider.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ThemeServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
// Register @theme Blade directive
|
||||
Blade::directive('theme', function ($expression) {
|
||||
return "<?php echo theme({$expression}); ?>";
|
||||
});
|
||||
|
||||
// Register @themeId Blade directive
|
||||
Blade::directive('themeId', function () {
|
||||
return "<?php echo theme_id(); ?>";
|
||||
});
|
||||
|
||||
// Register @themeName Blade directive
|
||||
Blade::directive('themeName', function () {
|
||||
return "<?php echo theme_name(); ?>";
|
||||
});
|
||||
|
||||
// Register @themeColor Blade directive
|
||||
Blade::directive('themeColor', function ($expression) {
|
||||
return "<?php echo theme_color({$expression}); ?>";
|
||||
});
|
||||
|
||||
// Register @themeFont Blade directive
|
||||
Blade::directive('themeFont', function ($expression) {
|
||||
return "<?php echo theme_font({$expression}); ?>";
|
||||
});
|
||||
|
||||
// Register @isTheme Blade directive
|
||||
Blade::directive('isTheme', function ($expression) {
|
||||
return "<?php if (is_theme({$expression})): ?>";
|
||||
});
|
||||
|
||||
// Register @endIsTheme Blade directive
|
||||
Blade::directive('endIsTheme', function () {
|
||||
return "<?php endif; ?>";
|
||||
});
|
||||
|
||||
// Register @themeCssVars Blade directive
|
||||
Blade::directive('themeCssVars', function () {
|
||||
return "<?php echo theme_css_vars(); ?>";
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user