auth public function getAuthProperty() { // Use the active guard from session first, then fallback to checking all guards $activeGuard = session('active_guard', 'web'); $user = Auth::guard($activeGuard)->user(); if ($user) { return $user; } // Fallback to checking all guards if active guard doesn't have a user return Auth::guard('admin')->user() ?: Auth::guard('bank')->user() ?: Auth::guard('organization')->user() ?: Auth::guard('web')->user(); } /** * Helper to check authorization and show error toast if unauthorized */ private function checkAuthorization(): bool { $profile = getActiveProfile(); if (!$profile) { $this->dispatch('wirechat-toast', type: 'error', message: __('Unauthorized access')); return false; } try { \App\Helpers\ProfileAuthorizationHelper::authorize($profile); return true; } catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) { $this->dispatch('wirechat-toast', type: 'error', message: __('Unauthorized access')); return false; } } /** * Override sendMessage to use the correct sendable_id. */ public function sendMessage() { abort_unless($this->auth(), 401); // CRITICAL SECURITY: Validate authorization before sending message $profile = getActiveProfile(); if (!$profile) { abort(403, 'No active profile'); } \App\Helpers\ProfileAuthorizationHelper::authorize($profile); // rate limit $this->rateLimit(); $attachments = array_merge($this->media, $this->files); if (empty($attachments)) { $this->validate(['body' => 'required|string']); } if (count($attachments) != 0) { $maxUploads = config('wirechat.attachments.max_uploads'); $fileMimes = implode(',', config('wirechat.attachments.file_mimes')); $fileMaxUploadSize = (int) config('wirechat.attachments.file_max_upload_size'); $mediaMimes = implode(',', config('wirechat.attachments.media_mimes')); $mediaMaxUploadSize = (int) config('wirechat.attachments.media_max_upload_size'); try { $this->validate([ 'files' => "array|max:$maxUploads|nullable", 'files.*' => "max:$fileMaxUploadSize|mimes:$fileMimes", 'media' => "array|max:$maxUploads|nullable", 'media.*' => "max:$mediaMaxUploadSize|mimes:$mediaMimes", ]); } catch (\Illuminate\Validation\ValidationException $th) { $errors = $th->errors(); foreach ($errors as $field => $messages) { $this->addError($field, $messages[0]); } return $this->dispatch('wirechat-toast', type: 'warning', message: $th->getMessage()); } $createdMessages = []; foreach ($attachments as $key => $attachment) { $path = $attachment->store( \Namu\WireChat\Facades\WireChat::storageFolder(), \Namu\WireChat\Facades\WireChat::storageDisk() ); $replyId = ($key === 0 && $this->replyMessage) ? $this->replyMessage->id : null; $message = \Namu\WireChat\Models\Message::create([ 'reply_id' => $replyId, 'conversation_id' => $this->conversation->id, 'sendable_type' => $this->auth->getMorphClass(), // Polymorphic sender type 'sendable_id' => $this->auth->id, // Polymorphic sender ID 'type' => \Namu\WireChat\Enums\MessageType::ATTACHMENT, ]); $attachment = $message->attachment()->create([ 'file_path' => $path, 'file_name' => basename($path), 'original_name' => $attachment->getClientOriginalName(), 'mime_type' => $attachment->getMimeType(), 'url' => \Storage::disk(\Namu\WireChat\Facades\WireChat::storageDisk())->url($path), ]); $createdMessages[] = $message; $this->conversation->updated_at = now(); $this->conversation->save(); $this->dispatch('refresh')->to(Chats::class); $this->dispatch('refresh')->to(\App\Http\Livewire\UnreadIndicator::class); $this->dispatchMessageCreatedEvent($message); } foreach ($createdMessages as $key => $message) { $this->pushMessage($message); } $this->dispatch('scroll-bottom'); } if ($this->body != null) { $createdMessage = \Namu\WireChat\Models\Message::create([ 'reply_id' => $this->replyMessage?->id, 'conversation_id' => $this->conversation->id, 'sendable_type' => $this->auth->getMorphClass(), // Polymorphic sender type 'sendable_id' => $this->auth->id, // Polymorphic sender ID 'body' => $this->body, 'type' => \Namu\WireChat\Enums\MessageType::TEXT, ]); $this->pushMessage($createdMessage); $this->conversation->touch(); $this->dispatchMessageCreatedEvent($createdMessage); $this->dispatch('refresh')->to(Chats::class); $this->dispatch('refreshList')->to(Chats::class); // Also dispatch to UnreadIndicator to update the red dot $this->dispatch('refresh')->to(\App\Http\Livewire\UnreadIndicator::class); } $this->reset('media', 'files', 'body'); $this->dispatch('scroll-bottom'); $this->removeReply(); } private function pushMessage(\Namu\WireChat\Models\Message $message) { $groupKey = $this->messageGroupKey($message); // Ensure loadedMessages is a Collection $this->loadedMessages = collect($this->loadedMessages); // Use tap to create a new group if it doesn’t exist, then push the message $this->loadedMessages->put($groupKey, $this->loadedMessages->get($groupKey, collect())->push($message)); } private function messageGroupKey(\Namu\WireChat\Models\Message $message): string { $messageDate = $message->created_at; $groupKey = ''; if ($messageDate->isToday()) { $groupKey = __('wirechat::chat.message_groups.today'); } elseif ($messageDate->isYesterday()) { $groupKey = __('wirechat::chat.message_groups.yesterday'); } elseif ($messageDate->greaterThanOrEqualTo(now()->subDays(7))) { $groupKey = $messageDate->format('l'); // Day name } else { $groupKey = $messageDate->format('d/m/Y'); // Older than 7 days, dd/mm/yyyy } return $groupKey; } /** * Keep a message from auto-deletion (mark as kept) */ public function keepMessage(string $id): void { if (!$this->checkAuthorization()) { return; } if (!$this->auth()) { $this->dispatch('wirechat-toast', type: 'error', message: __('Unauthorized access')); return; } $id = decrypt($id); $message = \Namu\WireChat\Models\Message::findOrFail($id); // Verify user belongs to the conversation if (!$this->auth->belongsToConversation($message->conversation)) { $this->dispatch('wirechat-toast', type: 'error', message: __('Unauthorized access')); return; } // Check if disappearing messages is enabled and allow_users_to_keep is true if (!timebank_config('wirechat.disappearing_messages.enabled', true) || !timebank_config('wirechat.disappearing_messages.allow_users_to_keep', true)) { return; } // Toggle kept status if ($message->kept_at) { // Unkeep the message $message->kept_at = null; } else { // Keep the message $message->kept_at = now(); } $message->save(); // Update the message in loadedMessages collection to trigger Livewire re-render if ($this->loadedMessages) { $this->loadedMessages = collect($this->loadedMessages)->map(function ($messageGroup) use ($message) { return $messageGroup->map(function ($loadedMessage) use ($message) { if ($loadedMessage->id === $message->id) { $loadedMessage->kept_at = $message->kept_at; } return $loadedMessage; }); }); } } /** * Delete conversation (override with authorization) */ public function deleteConversation() { if (!$this->checkAuthorization()) { return; } return parent::deleteConversation(); } /** * Clear conversation history (override with authorization) */ public function clearConversation() { if (!$this->checkAuthorization()) { return; } return parent::clearConversation(); } /** * Exit conversation (override with authorization) */ public function exitConversation() { if (!$this->checkAuthorization()) { return; } return parent::exitConversation(); } /** * Delete message for me (override with authorization) */ public function deleteForMe(string $id): void { if (!$this->checkAuthorization()) { return; } parent::deleteForMe($id); } /** * Delete message for everyone (override with authorization) */ public function deleteForEveryone(string $id): void { if (!$this->checkAuthorization()) { return; } parent::deleteForEveryone($id); } /** * Send like (override with authorization) */ public function sendLike() { if (!$this->checkAuthorization()) { return; } return parent::sendLike(); } /** * Set reply (override with authorization) */ public function setReply(string $id): void { if (!$this->checkAuthorization()) { return; } parent::setReply($id); } /** * Mount component (override with authorization) */ public function mount($conversation = null) { // CRITICAL SECURITY: Validate authorization on mount $profile = getActiveProfile(); if (!$profile) { abort(403, 'No active profile'); } \App\Helpers\ProfileAuthorizationHelper::authorize($profile); return parent::mount($conversation); } public function render() { // CRITICAL SECURITY: Re-validate authorization on every render $profile = getActiveProfile(); if (!$profile) { return view('errors.unauthorized-component'); } try { \App\Helpers\ProfileAuthorizationHelper::authorize($profile); } catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) { return view('errors.unauthorized-component'); } return parent::render(); } }