diff options
-rw-r--r-- | README.md | 30 | ||||
-rw-r--r-- | actions/DisplayAction.php | 1 | ||||
-rw-r--r-- | actions/FrontpageAction.php | 7 | ||||
-rw-r--r-- | actions/SetBridgeCacheAction.php | 68 | ||||
-rw-r--r-- | config.default.ini.php | 14 | ||||
-rw-r--r-- | lib/BridgeCard.php | 39 | ||||
-rw-r--r-- | lib/RssBridge.php | 18 | ||||
-rw-r--r-- | lib/http.php | 14 | ||||
-rw-r--r-- | static/connectivity.js | 2 | ||||
-rw-r--r-- | static/style.css | 2 | ||||
-rw-r--r-- | templates/base.html.php | 2 | ||||
-rw-r--r-- | templates/frontpage.html.php | 16 | ||||
-rw-r--r-- | templates/token.html.php | 20 |
13 files changed, 95 insertions, 138 deletions
@@ -10,6 +10,13 @@ Officially hosted instance: https://rss-bridge.org/bridge01/ IRC channel #rssbridge at https://libera.chat/ +[Full documentation](https://rss-bridge.github.io/rss-bridge/index.html) + +Alternatively find another +[public instance](https://rss-bridge.github.io/rss-bridge/General/Public_Hosts.html). + +Requires minimum PHP 7.4. + [](UNLICENSE) [](https://github.com/rss-bridge/rss-bridge/releases/latest) @@ -44,15 +51,6 @@ IRC channel #rssbridge at https://libera.chat/ * `YoutubeBridge`: [Fetches videos by username/channel/playlist/search](https://rss-bridge.org/bridge01/#bridge-YoutubeBridge) * `YouTubeCommunityTabBridge`: [Fetches posts from a channel's community tab](https://rss-bridge.org/bridge01/#bridge-YouTubeCommunityTabBridge) -[Full documentation](https://rss-bridge.github.io/rss-bridge/index.html) - -Check out RSS-Bridge right now on https://rss-bridge.org/bridge01/ - -Alternatively find another -[public instance](https://rss-bridge.github.io/rss-bridge/General/Public_Hosts.html). - -Requires minimum PHP 7.4. - ## Tutorial ### How to install on traditional shared web hosting @@ -259,6 +257,14 @@ Learn more in ## How-to +### How to password-protect the instance (token) + +Modify `config.ini.php`: + + [authentication] + + token = "hunter2" + ### How to remove all cache items As current user: @@ -332,8 +338,6 @@ Learn more in [bridge api](https://rss-bridge.github.io/rss-bridge/Bridge_API/in ### How to enable all bridges -Modify `config.ini.php`: - enabled_bridges[] = * ### How to enable some bridges @@ -390,9 +394,7 @@ Modify `report_limit` so that an error must occur 3 times before it is reported. The report count is reset to 0 each day. -### How to password-protect the instance - -HTTP basic access authentication: +### How to password-protect the instance (HTTP Basic Auth) [authentication] diff --git a/actions/DisplayAction.php b/actions/DisplayAction.php index 915eace5..ed063825 100644 --- a/actions/DisplayAction.php +++ b/actions/DisplayAction.php @@ -102,6 +102,7 @@ class DisplayAction implements ActionInterface $bridge->loadConfiguration(); // Remove parameters that don't concern bridges $remove = [ + 'token', 'action', 'bridge', 'format', diff --git a/actions/FrontpageAction.php b/actions/FrontpageAction.php index c0f819d0..32795c45 100644 --- a/actions/FrontpageAction.php +++ b/actions/FrontpageAction.php @@ -4,8 +4,6 @@ final class FrontpageAction implements ActionInterface { public function execute(Request $request) { - $showInactive = (bool) $request->get('show_inactive'); - $messages = []; $activeBridges = 0; @@ -22,10 +20,8 @@ final class FrontpageAction implements ActionInterface $body = ''; foreach ($bridgeClassNames as $bridgeClassName) { if ($bridgeFactory->isEnabled($bridgeClassName)) { - $body .= BridgeCard::render($bridgeClassName); + $body .= BridgeCard::render($bridgeClassName, $request); $activeBridges++; - } elseif ($showInactive) { - $body .= BridgeCard::render($bridgeClassName, false) . "\n"; } } @@ -37,7 +33,6 @@ final class FrontpageAction implements ActionInterface 'bridges' => $body, 'active_bridges' => $activeBridges, 'total_bridges' => count($bridgeClassNames), - 'show_inactive' => $showInactive, ]); } } diff --git a/actions/SetBridgeCacheAction.php b/actions/SetBridgeCacheAction.php deleted file mode 100644 index 5b1c6f53..00000000 --- a/actions/SetBridgeCacheAction.php +++ /dev/null @@ -1,68 +0,0 @@ -<?php - -class SetBridgeCacheAction implements ActionInterface -{ - private CacheInterface $cache; - - public function __construct() - { - $this->cache = RssBridge::getCache(); - } - - public function execute(Request $request) - { - $requestArray = $request->toArray(); - - // Authentication - $accessTokenInConfig = Configuration::getConfig('authentication', 'access_token'); - if (!$accessTokenInConfig) { - return new Response('Access token is not set in this instance', 403, ['content-type' => 'text/plain']); - } - if (isset($requestArray['access_token'])) { - $accessTokenGiven = $requestArray['access_token']; - } else { - $header = trim($_SERVER['HTTP_AUTHORIZATION'] ?? ''); - $position = strrpos($header, 'Bearer '); - if ($position !== false) { - $accessTokenGiven = substr($header, $position + 7); - } else { - $accessTokenGiven = ''; - } - } - if (!$accessTokenGiven) { - return new Response('No access token given', 403, ['content-type' => 'text/plain']); - } - if (! hash_equals($accessTokenInConfig, $accessTokenGiven)) { - return new Response('Incorrect access token', 403, ['content-type' => 'text/plain']); - } - - // Begin actual work - $key = $requestArray['key'] ?? null; - if (!$key) { - return new Response('You must specify key', 400, ['content-type' => 'text/plain']); - } - - $bridgeFactory = new BridgeFactory(); - - $bridgeName = $requestArray['bridge'] ?? null; - $bridgeClassName = $bridgeFactory->createBridgeClassName($bridgeName); - if (!$bridgeClassName) { - return new Response(sprintf('Bridge not found: %s', $bridgeName), 400, ['content-type' => 'text/plain']); - } - - // whitelist control - if (!$bridgeFactory->isEnabled($bridgeClassName)) { - return new Response('This bridge is not whitelisted', 401, ['content-type' => 'text/plain']); - } - - $bridge = $bridgeFactory->create($bridgeClassName); - $bridge->loadConfiguration(); - $value = $requestArray['value']; - - $cacheKey = get_class($bridge) . '_' . $key; - $ttl = 86400 * 3; - $this->cache->set($cacheKey, $value, $ttl); - - return new Response('done', 200, ['Content-Type' => 'text/plain']); - } -} diff --git a/config.default.ini.php b/config.default.ini.php index ee1e54c9..91a4c4fe 100644 --- a/config.default.ini.php +++ b/config.default.ini.php @@ -102,21 +102,13 @@ by_bridge = false [authentication] -; Enables basic authentication for all requests to this RSS-Bridge instance. -; -; Warning: You'll have to upgrade existing feeds after enabling this option! -; -; true = enabled -; false = disabled (default) +; HTTP basic authentication enable = false - username = "admin" - -; The password cannot be the empty string if authentication is enabled. password = "" -; This will be used only for actions that require privileged access -access_token = "" +; Token authentication (URL) +token = "" [error] diff --git a/lib/BridgeCard.php b/lib/BridgeCard.php index 6b812740..e5456f33 100644 --- a/lib/BridgeCard.php +++ b/lib/BridgeCard.php @@ -2,14 +2,7 @@ final class BridgeCard { - /** - * Render bridge card - * - * @param class-string<BridgeAbstract> $bridgeClassName The bridge name - * @param bool $isActive Indicates if the bridge is active or not - * @return string The bridge card - */ - public static function render($bridgeClassName, $isActive = true) + public static function render(string $bridgeClassName, Request $request): string { $bridgeFactory = new BridgeFactory(); @@ -47,19 +40,21 @@ final class BridgeCard <h2><a href="{$uri}">{$name}</a></h2> <p class="description">{$description}</p> + <input type="checkbox" class="showmore-box" id="showmore-{$bridgeClassName}" /> <label class="showmore" for="showmore-{$bridgeClassName}">Show more</label> CARD; - // If we don't have any parameter for the bridge, we print a generic form to load it. + $token = $request->attribute('token'); + if (count($contexts) === 0) { // The bridge has zero parameters - $card .= self::renderForm($bridgeClassName, $isActive); + $card .= self::renderForm($bridgeClassName, '', [], $token); } elseif (count($contexts) === 1 && array_key_exists('global', $contexts)) { // The bridge has a single context with key 'global' - $card .= self::renderForm($bridgeClassName, $isActive, '', $contexts['global']); + $card .= self::renderForm($bridgeClassName, '', $contexts['global'], $token); } else { // The bridge has one or more contexts (named or unnamed) foreach ($contexts as $contextName => $contextParameters) { @@ -77,7 +72,7 @@ final class BridgeCard $card .= '<h5>' . $contextName . '</h5>' . PHP_EOL; } - $card .= self::renderForm($bridgeClassName, $isActive, $contextName, $contextParameters); + $card .= self::renderForm($bridgeClassName, $contextName, $contextParameters, $token); } } @@ -99,17 +94,21 @@ final class BridgeCard private static function renderForm( string $bridgeClassName, - bool $isActive = false, - string $contextName = '', - array $contextParameters = [] + string $contextName, + array $contextParameters, + ?string $token ) { $form = <<<EOD - <form method="GET" action="?"> + <form method="GET" action="?" class="bridge-form"> <input type="hidden" name="action" value="display" /> <input type="hidden" name="bridge" value="{$bridgeClassName}" /> - EOD; + if ($token) { + // todo: maybe escape the token? + $form .= sprintf('<input type="hidden" name="token" value="%s" />', $token); + } + if (!empty($contextName)) { $form .= sprintf('<input type="hidden" name="context" value="%s" />', $contextName); } @@ -167,11 +166,7 @@ final class BridgeCard $form .= '</div>'; } - if ($isActive) { - $form .= '<button type="submit" name="format" formtarget="_blank" value="Html">Generate feed</button>'; - } else { - $form .= '<span style="font-weight: bold;">Inactive</span>'; - } + $form .= '<button type="submit" name="format" formtarget="_blank" value="Html">Generate feed</button>'; return $form . '</form>' . PHP_EOL; } diff --git a/lib/RssBridge.php b/lib/RssBridge.php index c8d11596..1bb5f5ea 100644 --- a/lib/RssBridge.php +++ b/lib/RssBridge.php @@ -47,6 +47,7 @@ final class RssBridge ]), 503); } + // HTTP Basic auth check if (Configuration::getConfig('authentication', 'enable')) { if (Configuration::getConfig('authentication', 'password') === '') { return new Response('The authentication password cannot be the empty string', 500); @@ -71,6 +72,23 @@ final class RssBridge // At this point the username and password was correct } + // Add token as attribute to request + $request = $request->withAttribute('token', $request->get('token')); + + // Token authentication check + if (Configuration::getConfig('authentication', 'token')) { + if (! $request->attribute('token')) { + return new Response(render(__DIR__ . '/../templates/token.html.php', [ + 'message' => '', + ]), 401); + } + if (! hash_equals(Configuration::getConfig('authentication', 'token'), $request->attribute('token'))) { + return new Response(render(__DIR__ . '/../templates/token.html.php', [ + 'message' => 'Invalid token', + ]), 401); + } + } + $action = $request->get('action', 'Frontpage'); $actionName = strtolower($action) . 'Action'; $actionName = implode(array_map('ucfirst', explode('-', $actionName))); diff --git a/lib/http.php b/lib/http.php index d53909b4..e4f9bf48 100644 --- a/lib/http.php +++ b/lib/http.php @@ -170,6 +170,7 @@ final class Request { private array $get; private array $server; + private array $attributes; private function __construct() { @@ -180,6 +181,7 @@ final class Request $self = new self(); $self->get = $_GET; $self->server = $_SERVER; + $self->attributes = []; return $self; } @@ -200,6 +202,18 @@ final class Request return $this->server[$key] ?? $default; } + public function withAttribute(string $name, $value = true): self + { + $clone = clone $this; + $clone->attributes[$name] = $value; + return $clone; + } + + public function attribute(string $key, $default = null) + { + return $this->attributes[$key] ?? $default; + } + public function toArray(): array { return $this->get; diff --git a/static/connectivity.js b/static/connectivity.js index 55ee9434..2f39ce6b 100644 --- a/static/connectivity.js +++ b/static/connectivity.js @@ -48,7 +48,7 @@ function buildTable(bridgeList) { // Link to the actual bridge on frontpage var a = document.createElement('a'); - a.href = remote + "/?show_inactive=1#bridge-" + bridge; + a.href = remote + "/?#bridge-" + bridge; a.target = '_blank'; a.innerText = '[Show]'; a.style.marginLeft = '5px'; diff --git a/static/style.css b/static/style.css index cfaf66a1..4e6b1b2d 100644 --- a/static/style.css +++ b/static/style.css @@ -287,7 +287,7 @@ p.maintainer { } /* Hide all forms on the frontpage by default */ -form { +form.bridge-form { display: none; } diff --git a/templates/base.html.php b/templates/base.html.php index ca31823d..d2557599 100644 --- a/templates/base.html.php +++ b/templates/base.html.php @@ -7,6 +7,8 @@ <title><?= e($_title ?? 'RSS-Bridge') ?></title> <link href="static/style.css?2023-03-24" rel="stylesheet"> <link rel="icon" type="image/png" href="static/favicon.png"> + + <script src="static/rss-bridge.js"></script> </head> <body> diff --git a/templates/frontpage.html.php b/templates/frontpage.html.php index a0d274da..c1182673 100644 --- a/templates/frontpage.html.php +++ b/templates/frontpage.html.php @@ -1,4 +1,4 @@ -<script src="static/rss-bridge.js"></script> + <script> document.addEventListener('DOMContentLoaded', rssbridge_toggle_bridge); document.addEventListener('DOMContentLoaded', rssbridge_list_search); @@ -42,20 +42,6 @@ <?= $active_bridges ?>/<?= $total_bridges ?> active bridges.<br> - <?php if ($active_bridges !== $total_bridges): ?> - <?php if ($show_inactive): ?> - <a href="?show_inactive=0"> - <button class="small">Hide inactive bridges</button> - </a> - <br> - <?php else: ?> - <a href="?show_inactive=1"> - <button class="small">Show inactive bridges</button> - </a> - <br> - <?php endif; ?> - <?php endif; ?> - <br> <?php if ($admin_email): ?> diff --git a/templates/token.html.php b/templates/token.html.php new file mode 100644 index 00000000..1a036dbb --- /dev/null +++ b/templates/token.html.php @@ -0,0 +1,20 @@ +<?php +/** + * This template renders a form for user to enter a auth token if it's enabled + */ + +?> + +<h1> + Authentication with token required +</h1> + +<p> + <?= e($message) ?> +</p> + +<form action="" method="get"> + <label for="token">Token:</label> + <input type="password" name="token" id="token" placeholder="token"> + <input type="submit" value="OK"> +</form> |