aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md30
-rw-r--r--actions/DisplayAction.php1
-rw-r--r--actions/FrontpageAction.php7
-rw-r--r--actions/SetBridgeCacheAction.php68
-rw-r--r--config.default.ini.php14
-rw-r--r--lib/BridgeCard.php39
-rw-r--r--lib/RssBridge.php18
-rw-r--r--lib/http.php14
-rw-r--r--static/connectivity.js2
-rw-r--r--static/style.css2
-rw-r--r--templates/base.html.php2
-rw-r--r--templates/frontpage.html.php16
-rw-r--r--templates/token.html.php20
13 files changed, 95 insertions, 138 deletions
diff --git a/README.md b/README.md
index d6d1046c..8e8ae117 100644
--- a/README.md
+++ b/README.md
@@ -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.
+
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE)
[![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg?logo=github)](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>