diff --git a/INSTALL.md b/INSTALL.md index a48804c..0d8fd0c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -131,6 +131,32 @@ Apprise sends a different JSON format (`title`, `body`, `type`, `attachments`) - The webhook endpoint is public (no auth required) but validates the auth token from the URL path - Admin settings endpoints (`/save-config`, `/rooms`) require admin login - Images are stored in the bot user's files and purged after the retention period +- **Debug endpoint disabled by default** — see below - **Known limitations (to be fixed in a future release):** - Auth tokens generated from the settings UI use client-side generation; for higher security, regenerate them via the server API - The webhook endpoint has no rate limiting — consider placing it behind a reverse proxy rate limiter if exposing to untrusted sources + +### Debug endpoint + +The `/apps/ncdiscordhook/debug` endpoint exposes internal configuration, +database schema, and bot credentials. It is **disabled by default**. + +```bash +# Check status +php occ ncdiscordhook:debug:status + +# Enable (WARNING: exposes sensitive data) +php occ ncdiscordhook:debug:enable + +# Disable (default) +php occ ncdiscordhook:debug:disable + +# Toggle current state +php occ ncdiscordhook:debug:toggle +``` + +After troubleshooting, disable it immediately: + +```bash +php occ ncdiscordhook:debug:disable +``` diff --git a/README.md b/README.md index e99650b..d7cb0c0 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,34 @@ The apprise webhook maps `title`, `body`, and `attachments` to the same Talk mes - Bot password is encrypted at rest using Nextcloud's crypto - Image download uses Nextcloud's HTTP client with local address blocking - Rate limiting should be handled at the web server or reverse proxy level +- **Debug endpoint disabled by default** — see below + +### Debug endpoint + +The `/apps/ncdiscordhook/debug` endpoint exposes internal configuration, +database schema, and bot credentials. It is **disabled by default** and must +be explicitly enabled via the CLI: + +```bash +# Check current status +php occ ncdiscordhook:debug:status + +# Enable (WARNING: exposes sensitive data) +php occ ncdiscordhook:debug:enable + +# Disable (default) +php occ ncdiscordhook:debug:disable + +# Toggle current state +php occ ncdiscordhook:debug:toggle +``` + +Never leave the debug endpoint enabled in production. After troubleshooting, +disable it immediately: + +```bash +php occ ncdiscordhook:debug:disable +``` ## Logging diff --git a/appinfo/info.xml b/appinfo/info.xml index 0da1f6f..64f461b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -5,7 +5,7 @@ NCdiscordhook Discord webhook bridge for Nextcloud Talk with image support Accepts Discord webhook-style JSON payloads and posts them into Nextcloud Talk rooms, preserving image attachments. Each room gets its own webhook URL with an auth token for security. - 1.0.0 + 1.1.0 AGPL-3.0-or-later Kyle NCdiscordhook @@ -24,5 +24,9 @@ OCA\NCdiscordhook\Settings\Admin + + OCA\NCdiscordhook\Command\DebugToggle + + https://github.com/Mr-Newlove/nc_bot_webhooks/issues diff --git a/lib/Command/DebugToggle.php b/lib/Command/DebugToggle.php new file mode 100644 index 0000000..1696cb4 --- /dev/null +++ b/lib/Command/DebugToggle.php @@ -0,0 +1,101 @@ +appConfig = $appConfig; + } + + protected function configure(): void { + $this + ->setName('ncdiscordhook:debug:toggle') + ->setDescription('Enable or disable the debug endpoint') + ->addOption('enable', 'e', InputOption::VALUE_NONE, 'Enable the debug endpoint') + ->addOption('disable', 'd', InputOption::VALUE_NONE, 'Disable the debug endpoint') + ->addOption('status', null, InputOption::VALUE_NONE, 'Show current debug endpoint status'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $io = new SymfonyStyle($input, $output); + + if ($input->getOption('status')) { + $enabled = (bool) $this->appConfig->getValueBool(self::APP_ID, self::DEBUG_KEY, false); + $io->success($enabled ? 'Debug endpoint is ENABLED' : 'Debug endpoint is DISABLED (default)'); + return Command::SUCCESS; + } + + if ($input->getOption('enable') && $input->getOption('disable')) { + $io->error('Cannot use both --enable and --disable at the same time.'); + return Command::INVALID; + } + + if ($input->getOption('enable')) { + $this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, true); + $io->warning('Debug endpoint is now ENABLED. Anyone can access /apps/ncdiscordhook/debug.'); + $io->note('To disable later, run: php occ ncdiscordhook:debug:disable'); + return Command::SUCCESS; + } + + if ($input->getOption('disable')) { + $this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, false); + $io->success('Debug endpoint is now DISABLED.'); + return Command::SUCCESS; + } + + // No flag: toggle + $current = (bool) $this->appConfig->getValueBool(self::APP_ID, self::DEBUG_KEY, false); + $this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, !$current); + $next = !$current ? 'enabled' : 'disabled'; + $io->success("Debug endpoint is now $next."); + return Command::SUCCESS; + } +} diff --git a/lib/Controller/WebhookController.php b/lib/Controller/WebhookController.php index ad24c34..6f54bbc 100644 --- a/lib/Controller/WebhookController.php +++ b/lib/Controller/WebhookController.php @@ -361,11 +361,26 @@ class WebhookController extends Controller { * Query params: * - webhook_url: Full webhook URL to diagnose (e.g. /apps/ncdiscordhook/discord-webhook/{room}/{token}) * - test_post=1: Actually POST a test message to the room + * + * This endpoint is disabled by default. Enable it via: + * php occ ncdiscordhook:debug:enable + * + * SECURITY: Never leave debug enabled in production. It exposes internal + * configuration, database schema, and bot credentials. */ #[PublicPage] #[NoCSRFRequired] #[NoAdminRequired] public function debug(): DataResponse { + // Debug endpoint must be explicitly enabled via OCC command + $debugEnabled = $this->appConfig->getValueBool('ncdiscordhook', 'debug_enabled', false); + if (!$debugEnabled) { + return new DataResponse( + ['error' => 'Debug endpoint is disabled. Enable it with: php occ ncdiscordhook:debug:enable'], + Http::STATUS_FORBIDDEN, + ); + } + $user = $this->userSession->getUser(); $uid = $user !== null ? $user->getUID() : null; $isAdmin = $user !== null && $this->groupManager->isAdmin($uid); @@ -376,6 +391,7 @@ class WebhookController extends Controller { 'user_is_admin' => $isAdmin, 'bot_user' => null, 'has_bot_password' => false, + 'debug_enabled' => true, ]; try { diff --git a/lib/Service/TalkService.php b/lib/Service/TalkService.php index 852cf73..21b5541 100644 --- a/lib/Service/TalkService.php +++ b/lib/Service/TalkService.php @@ -704,6 +704,22 @@ class TalkService { $richObjects[] = $richObj; } } + } elseif (!empty($attachment['base64'] ?? '')) { + // Base64-encoded attachment (Apprise library JSON method) + $fileData = base64_decode($attachment['base64'], true); + if ($fileData === false) { + continue; + } + + $fileName = $attachment['filename'] ?? $attachment['name'] ?? 'attachment'; + $mimeType = $attachment['mimetype'] ?? $attachment['mimeType'] ?? 'application/octet-stream'; + $uploadPath = $this->uploadImage($roomToken, $fileName, $fileData, $mimeType); + if ($uploadPath !== null) { + $richObj = $this->buildRichObject($uploadPath, $mimeType, $roomToken); + if ($richObj !== null) { + $richObjects[] = $richObj; + } + } } } } @@ -877,7 +893,7 @@ class TalkService { // or the message is silently dropped. $botDisplayName = $this->getBotDisplayNameForRoom($roomToken); - // Build message body for Chat API v4 + // Build message body for Chat API v1 $body = [ 'message' => $message, 'actorType' => 'users',