-
-
Notifications
You must be signed in to change notification settings - Fork 312
Feat/add komga latest books homepage #2056
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v2-master
Are you sure you want to change the base?
Changes from all commits
16f480e
092b931
28548b0
4a2e90d
c1d04b0
966a2ea
6c23dd2
b040bc0
c869c15
2478ceb
5d91191
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| RewriteEngine On | ||
| RewriteBase /api/v2 | ||
| RewriteCond %{REQUEST_FILENAME} !-f | ||
| RewriteCond %{REQUEST_FILENAME} !-d | ||
| RewriteRule ^ /api/v2/index.php [QSA,L] | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,58 @@ | ||||||
| <?php | ||||||
|
|
||||||
| trait KomgaHomepageItem | ||||||
| { | ||||||
| public function komgaSettingsArray($infoOnly = false) | ||||||
| { | ||||||
| $homepageInformation = [ | ||||||
| 'name' => 'Komga', | ||||||
| 'enabled' => true, | ||||||
| 'image' => 'plugins/images/komga.svg', | ||||||
| 'category' => 'Entertainment', | ||||||
| 'settingsArray' => __FUNCTION__ | ||||||
| ]; | ||||||
| if ($infoOnly) { | ||||||
| return $homepageInformation; | ||||||
| } | ||||||
| $homepageSettings = [ | ||||||
| 'debug' => true, | ||||||
| 'settings' => [ | ||||||
| 'Enable' => [ | ||||||
| $this->settingsOption('enable', 'homepageKomgaEnabled', ['label' => 'Activate Komga', 'help' => 'Display the Komga module on the home page']), | ||||||
| $this->settingsOption('auth', 'homepageKomgaAuth', ['label' => 'Authentification']), | ||||||
|
||||||
| $this->settingsOption('auth', 'homepageKomgaAuth', ['label' => 'Authentification']), | |
| $this->settingsOption('auth', 'homepageKomgaAuth', ['label' => 'Authentication']), |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,109 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $app->get('/plugins/komga/books/latest', function ($request, $response, $args) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $komgaPlugin = new KomgaPlugin(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $GLOBALS['api']['response']['data'] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($komgaPlugin->checkRoute($request)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($komgaPlugin->qualifyRequest($komgaPlugin->config['KOMGA-minAuth'], true)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $url = $komgaPlugin->config['KOMGA-url'] ?? ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $apiKey = $komgaPlugin->config['KOMGA-apikey'] ?? ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check for group override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $groupId = $komgaPlugin->user['groupID'] ?? null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $libraries = $komgaPlugin->config['KOMGA-libraries'] ?? 'all'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($groupId !== null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $groupOverride = $komgaPlugin->config['KOMGA-library-group-' . $groupId] ?? 'default'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($groupOverride !== 'default') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $libraries = $groupOverride; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($url && $apiKey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $endpoint = '/api/v1/books?sort=createdDate,desc&size=20'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($libraries && $libraries !== 'all') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $endpoint .= '&library_id=' . $libraries; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $endpoint .= '&library_id=' . $libraries; | |
| $endpoint .= '&library_id=' . rawurlencode($libraries); |
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cURL request for latest books has no connect/overall timeout, so a slow/unreachable Komga server can hang the API request and degrade homepage load. Set reasonable CURLOPT_CONNECTTIMEOUT/CURLOPT_TIMEOUT (similar to the 2s timeout used in settings) and handle curl_error/HTTP status codes to return a clean error response.
| $res = curl_exec($ch); | |
| curl_close($ch); | |
| if ($res) { | |
| $data = json_decode($res, true); | |
| // Provide settings to JS via our API response | |
| $GLOBALS['api']['response']['data'] = [ | |
| 'title' => $komgaPlugin->config['KOMGA-title'] ?? 'Recently added books', | |
| 'baseUrl' => rtrim($url, '/') . '/book/', | |
| 'books' => (isset($data['content']) ? $data['content'] : $data), | |
| 'tabName' => $komgaPlugin->config['KOMGA-tab-name'] ?? 'Komga' | |
| ]; | |
| // Prevent hanging requests to Komga | |
| curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2); | |
| curl_setopt($ch, CURLOPT_TIMEOUT, 2); | |
| $res = curl_exec($ch); | |
| if ($res === false) { | |
| $error = curl_error($ch); | |
| curl_close($ch); | |
| $GLOBALS['responseCode'] = 502; | |
| $GLOBALS['api']['response']['error'] = 'Unable to fetch latest Komga books.'; | |
| $GLOBALS['api']['response']['errorDetails'] = $error; | |
| } else { | |
| $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
| curl_close($ch); | |
| if ($httpCode >= 200 && $httpCode < 300) { | |
| $data = json_decode($res, true); | |
| // Provide settings to JS via our API response | |
| $GLOBALS['api']['response']['data'] = [ | |
| 'title' => $komgaPlugin->config['KOMGA-title'] ?? 'Recently added books', | |
| 'baseUrl' => rtrim($url, '/') . '/book/', | |
| 'books' => (isset($data['content']) ? $data['content'] : $data), | |
| 'tabName' => $komgaPlugin->config['KOMGA-tab-name'] ?? 'Komga' | |
| ]; | |
| } else { | |
| $GLOBALS['responseCode'] = 502; | |
| $GLOBALS['api']['response']['error'] = 'Komga responded with an unexpected status code.'; | |
| $GLOBALS['api']['response']['statusCode'] = $httpCode; | |
| } |
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/plugins/komga/image accepts an arbitrary url query param and, when absolute URLs are allowed, will forward the Komga X-API-Key header to that host. This is an SSRF vector and can leak the Komga API key to attacker-controlled domains; additionally, the route is currently accessible to guests (qualifyRequest(999)). Restrict access to at least the plugin’s configured auth level, and validate that the requested URL is only within the configured Komga base URL (e.g., same scheme/host/port and expected path prefix) before proxying.
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The image proxy cURL request also lacks connect/overall timeouts and doesn’t check for cURL errors or non-2xx HTTP status codes. Add timeouts and validate the upstream response code to avoid tying up PHP workers and to prevent caching/serving error pages as images.
| $res = curl_exec($ch); | |
| $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); | |
| curl_close($ch); | |
| if ($res) { | |
| // Set reasonable timeouts to avoid tying up PHP workers | |
| curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); | |
| curl_setopt($ch, CURLOPT_TIMEOUT, 10); | |
| $res = curl_exec($ch); | |
| $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
| $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); | |
| $curlError = curl_errno($ch); | |
| curl_close($ch); | |
| if ($res !== false && $curlError === 0 && $httpCode >= 200 && $httpCode < 300) { |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,12 @@ | ||||||||
| <?php | ||||||||
| /* | ||||||||
| * Default configuration for Komga Plugin | ||||||||
| */ | ||||||||
| return array( | ||||||||
| 'KOMGA-enabled' => false, | ||||||||
| 'KOMGA-minAuth' => 1, | ||||||||
| 'KOMGA-url' => '', | ||||||||
| 'KOMGA-apikey' => '', | ||||||||
| 'KOMGA-libraries' => 'all', | ||||||||
| 'KOMGA-title' => 'Recently added books' | ||||||||
|
||||||||
| 'KOMGA-title' => 'Recently added books' | |
| 'KOMGA-title' => 'Recently added books', | |
| 'KOMGA-tab-name' => '' |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,125 @@ | ||||||||||||||||||||||||||||||
| let KOMGA_TAB_NAME = 'Komga-(Livres)'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| window.komgaFetchAuthImage = async function (url, elementId) { | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| const proxyUrl = `api/v2/plugins/komga/image?url=${encodeURIComponent(url)}`; | ||||||||||||||||||||||||||||||
| const res = await fetch(proxyUrl); | ||||||||||||||||||||||||||||||
| if (!res.ok) throw new Error(); | ||||||||||||||||||||||||||||||
| const blob = await res.blob(); | ||||||||||||||||||||||||||||||
| const objectURL = URL.createObjectURL(blob); | ||||||||||||||||||||||||||||||
| const el = document.getElementById(elementId); | ||||||||||||||||||||||||||||||
| if (el) el.style.backgroundImage = `url('${objectURL}')`; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| if (el) el.style.backgroundImage = `url('${objectURL}')`; | |
| if (el) { | |
| el.style.backgroundImage = `url('${objectURL}')`; | |
| // Revoke the object URL after a short delay to avoid memory leaks. | |
| setTimeout(() => { | |
| URL.revokeObjectURL(objectURL); | |
| }, 1000); | |
| } |
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sideBarLinks.forEach(...) can click multiple matching links because there’s no way to break out early once found is true. This can cause unexpected navigation if multiple items contain “Komga”. Use a for...of loop (or Array.prototype.find) so you can stop after the first match, and match the tab more precisely (e.g., exact match on the configured tab name).
| sideBarLinks.forEach(link => { | |
| if (link.textContent.trim() === KOMGA_TAB_NAME || link.textContent.includes('Komga')) { | |
| link.click(); | |
| found = true; | |
| } | |
| }); | |
| for (const link of sideBarLinks) { | |
| const text = (link.textContent || '').trim(); | |
| if (text && text.toLowerCase() === KOMGA_TAB_NAME.toLowerCase()) { | |
| link.click(); | |
| found = true; | |
| break; | |
| } | |
| } |
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code builds HTML with template literals containing titleDisplay, title, series, etc. from Komga and then assigns it via innerHTML. If any of those fields contain HTML (or quotes), this becomes an XSS vector in Organizr. Prefer constructing DOM nodes and assigning user-controlled text via textContent (or sanitize/escape all interpolated values) before inserting into the document.
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onclick="komgaOpenBook('${komgaBaseUrl}', '${book.id}')" injects dynamic values into an inline handler without escaping. If either value contains quotes it can break the attribute and enable script injection. Attach click listeners with addEventListener (and keep data in closures / data-* attributes) rather than building inline JS in HTML strings.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "Komga": { | ||
| "repo": "https://github.com/JamesDAdams/organizrv2-plugin-komga", | ||
| "author": "JamesAdams", | ||
| "category": "Entertainment", | ||
| "description": "Add the latest Komga books to the homepage", | ||
| "icon": "plugins/images/komga.svg", | ||
| "version": "1.0.9", | ||
| "minimum_organizr_version": "2.1.0", | ||
| "github_folder": "komga", | ||
| "project_folder": "komga", | ||
| "license": "personal" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This rewrite rule will route all requests (including the UI, static assets, etc.) to
/api/v2/index.phpbecause it matches^at the repo root. That will likely break normal page loads and asset serving. Restrict this rewrite to only/api/v2/*paths, or move this file intoapi/v2/.htaccessand rewrite to that directory’sindex.phpwith an appropriateRewriteBase.