Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Newspipe support for reverse proxy

I submitted this patch upstream.

I added a new option in the config, named prefix. If you set this, all the internal urls will add it. We use a ReverseProxy trick to add the prefix. This solves the generated urls, but for the hardcoded urls built throughout the jinja2 templates, I had to just manually update those. We use a neat trick to add a custom function to the template-parsing context which we can use to get the prefix (which safely returns the empty string if we had not set it, because we initialize the variable as empty).

Most of the lines of this patch are just adjusting all the templates to add the function call to {{ prefix() }}

diff --git a/instance/config.py b/instance/config.py
index 42e624c..af7617a 100644
--- a/instance/config.py
+++ b/instance/config.py
@@ -10,6 +10,9 @@ PORT = 5000
 DEBUG = True
 API_ROOT = "/api/v2.0"

+# Optional, and useful if you are using a reverse proxy with this virtual path prefix
+#PREFIX = "/newspipe"
+
 CSRF_ENABLED = True
 SECRET_KEY = "LCx3BchmHRxFzkEv4BqQJyeXRLXenf"
 SECURITY_PASSWORD_SALT = "L8gTsyrpRQEF8jNWQPyvRfv7U5kJkD"
diff --git a/instance/sqlite.py b/instance/sqlite.py
index 1f8d620..c6eaa46 100644
--- a/instance/sqlite.py
+++ b/instance/sqlite.py
@@ -10,6 +10,9 @@ PORT = 5000
 DEBUG = True
 API_ROOT = "/api/v2.0"

+# Optional, and useful if you are using a reverse proxy with this virtual path prefix
+#PREFIX = "/newspipe"
+
 CSRF_ENABLED = True
 SECRET_KEY = "LCx3BchmHRxFzkEv4BqQJyeXRLXenf"
 SECURITY_PASSWORD_SALT = "L8gTsyrpRQEF8jNWQPyvRfv7U5kJkD"
diff --git a/newspipe/bootstrap.py b/newspipe/bootstrap.py
index 1204491..1c5c967 100644
--- a/newspipe/bootstrap.py
+++ b/newspipe/bootstrap.py
@@ -44,6 +44,14 @@ def set_logging(
             handler.setLevel(log_level)
         logger.setLevel(log_level)

+class ReverseProxied(object):
+    def __init__(self, app, script_name):
+        self.app = app
+        self.script_name = script_name
+
+    def __call__(self, environ, start_response):
+        environ['SCRIPT_NAME'] = self.script_name
+        return self.app(environ, start_response)

 # Create Flask application
 application = Flask(__name__, instance_relative_config=True)
@@ -63,6 +71,11 @@ else:

 set_logging(application.config["LOG_PATH"])

+_prefix = ""
+if "PREFIX" in application.config:
+    application.wsgi_app = ReverseProxied(application.wsgi_app, script_name=application.config["PREFIX"])
+    _prefix = application.config["PREFIX"]
+
 db = SQLAlchemy(application)

 migrate = Migrate(application, db)
@@ -100,3 +113,9 @@ application.jinja_env.filters["datetime"] = format_datetime
 application.jinja_env.filters["datetimeformat"] = datetimeformat
 # inject application in Jinja env
 application.jinja_env.globals["application"] = application
+
+@application.context_processor
+def utility_processor():
+    def prefix():
+        return _prefix.rstrip("/")
+    return dict(prefix=prefix)
diff --git a/newspipe/controllers/user.py b/newspipe/controllers/user.py
index 00ffb96..d2c5bc0 100644
--- a/newspipe/controllers/user.py
+++ b/newspipe/controllers/user.py
@@ -138,7 +138,7 @@ class LdapuserController:
         namelist = []
         try:
             query = dns.resolver.query(f"_ldap._tcp.{domain}", "SRV")
-        except dns.resolver.NXDOMAIN:
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
             # no records exist that match the request, so we were probably
             # given a specific hostname, and an empty query will trigger
             # the logic below that will add the original domain to the list.
diff --git a/newspipe/static/js/articles.js b/newspipe/static/js/articles.js
index f1cee6e..29ebac2 100644
--- a/newspipe/static/js/articles.js
+++ b/newspipe/static/js/articles.js
@@ -86,7 +86,7 @@ Array.prototype.map.call(nodes, function(node) {
       }

       // sends the updates to the server
-      fetch(API_ROOT + "article/" + article_id, {
+      fetch(prefix + API_ROOT + "article/" + article_id, {
         method: "PUT",
         headers: {
           'Content-Type': 'application/json',
@@ -157,7 +157,7 @@ Array.prototype.map.call(nodes, function(node) {
       }

       // sends the updates to the server
-      fetch(API_ROOT + "article/" + article_id, {
+      fetch(prefix + API_ROOT + "article/" + article_id, {
         method: "PUT",
         headers: {
           'Content-Type': 'application/json',
@@ -189,7 +189,7 @@ Array.prototype.map.call(nodes, function(node) {
         data = JSON.stringify(data);

         // sends the updates to the server
-        fetch(API_ROOT + "articles", {
+        fetch(prefix + API_ROOT + "articles", {
           method: "DELETE",
           headers: {
             'Content-Type': 'application/json',
diff --git a/newspipe/static/js/config.js b/newspipe/static/js/config.js
new file mode 100644
index 0000000..7fad438
--- /dev/null
+++ b/newspipe/static/js/config.js
@@ -0,0 +1,7 @@
+/*
+ * Set variables here for the javascript used by newspipe.
+ */
+var prefix = "/newspipe";
+
+/* Set exactly one trailing slash on prefix. */
+prefix = prefix.replace(/\/+$/,"/");
diff --git a/newspipe/templates/article.html b/newspipe/templates/article.html
index c62e3f0..d9cf9a6 100644
--- a/newspipe/templates/article.html
+++ b/newspipe/templates/article.html
@@ -4,7 +4,7 @@
     <div class="row" data-article="{{ article.id }}" id="filters" data-filter="{{ filter_ }}">
         <div class="col">
             <h2><a href="{{ article.link  }}" target="_blank">{{ article.title|safe }}</a></h2>
-            <h3>{{ _('from') }} <a href="/feed/{{ article.source.id }}">{{ article.source.title }}</a></h3>
+            <h3>{{ _('from') }} <a href="{{ prefix() }}/feed/{{ article.source.id }}">{{ article.source.title }}</a></h3>
             <a href="{{ url_for("article.delete", article_id=article.id) }}"><i class="fa fa-times delete" aria-hidden="true" title="{{ _('Delete this article') }}"></i></a>
             {% if article.like %}
                 <a href="#"><i class="fa fa-star like" aria-hidden="true" title="{{ _('One of your favorites') }}"></i></a>
diff --git a/newspipe/templates/duplicates.html b/newspipe/templates/duplicates.html
index 38dc52b..d138226 100644
--- a/newspipe/templates/duplicates.html
+++ b/newspipe/templates/duplicates.html
@@ -1,7 +1,7 @@
 {% extends "layout.html" %}
 {% block content %}
 <div class="container">
-    <p><h1>{{ _('Duplicates in the feed') }} <a href="/feed/{{ feed.id }}">{{ feed.title }}</a>.</h1><p>
+    <p><h1>{{ _('Duplicates in the feed') }} <a href="{{ prefix() }}/feed/{{ feed.id }}">{{ feed.title }}</a>.</h1><p>
     <div class="table-responsive">
         <table class="table table-striped">
             <thead>
@@ -19,8 +19,8 @@
             {% for pair in duplicates %}
                 <tr>
                     <td>{{ loop.index }}</td>
-                    <td id="{{ pair[0].id }}"><a href="{{ url_for("article.delete", article_id=pair[0].id) }}"><i class="fa fa-times" aria-hidden="true" title="{{ _('Delete this article') }}"></i></a>&nbsp;<a href="/article/{{ pair[0].id }}">{{ pair[0].title }}</a> ({{ pair[0].retrieved_date }})</td>
-                    <td id="{{ pair[1].id }}"><a href="{{ url_for("article.delete", article_id=pair[1].id) }}"><i class="fa fa-times" aria-hidden="true" title="{{ _('Delete this article') }}"></i></a>&nbsp;<a href="/article/{{ pair[1].id }}">{{ pair[1].title }}</a> ({{ pair[1].retrieved_date }})</td>
+                    <td id="{{ pair[0].id }}"><a href="{{ url_for("article.delete", article_id=pair[0].id) }}"><i class="fa fa-times" aria-hidden="true" title="{{ _('Delete this article') }}"></i></a>&nbsp;<a href="{{ prefix() }}/article/{{ pair[0].id }}">{{ pair[0].title }}</a> ({{ pair[0].retrieved_date }})</td>
+                    <td id="{{ pair[1].id }}"><a href="{{ url_for("article.delete", article_id=pair[1].id) }}"><i class="fa fa-times" aria-hidden="true" title="{{ _('Delete this article') }}"></i></a>&nbsp;<a href="{{ prefix() }}/article/{{ pair[1].id }}">{{ pair[1].title }}</a> ({{ pair[1].retrieved_date }})</td>
                 </tr>
             {% endfor %}
             </tbody>
diff --git a/newspipe/templates/history.html b/newspipe/templates/history.html
index 153c2f1..00e22ef 100644
--- a/newspipe/templates/history.html
+++ b/newspipe/templates/history.html
@@ -43,7 +43,7 @@
       <ul class="list-group">
         {% for date in articles_counter | sort(reverse = True) %}
         {% for article in articles %}
-        <li class="list-group-item">{{ article.date | datetime }} - <a href="/article/{{ article.id }}">{{ article.title | safe }}</a></li>
+        <li class="list-group-item">{{ article.date | datetime }} - <a href="{{ prefix() }}/article/{{ article.id }}">{{ article.title | safe }}</a></li>
         {% endfor %}
         {% endfor %}
       </ul>
diff --git a/newspipe/templates/home.html b/newspipe/templates/home.html
index 631b769..5feb18d 100644
--- a/newspipe/templates/home.html
+++ b/newspipe/templates/home.html
@@ -42,7 +42,7 @@
                             {% if feed_id == feed.id %}</b>{% endif %}
                         </a></li>
                         <li class="nav-item feed-commands {% if in_error.get(fid, 0) > 0 %}d-none{% endif %}" data-bs-feed="{{ feed.id }}"><span class="nav-link">
-                            <a href="/feed/{{ feed.id }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
+                            <a href="{{ prefix() }}/feed/{{ feed.id }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
                             <a href="{{ url_for('feed.form', feed_id=feed.id) }}"><i class="fa fa-pencil-square-o" aria-hidden="true" title="{{ _('Edit this feed') }}"></i></a>
                             <a href="{{ url_for('article.mark_as', new_value='unread', feed_id=feed.id) }}"><i class="fa fa-square-o" aria-hidden="true" title="{{ _('Mark this feed as unread') }}"></i></a>
                             <a href="{{ url_for('article.mark_as', new_value='read', feed_id=feed.id) }}"><i class="fa fa-check-square-o" aria-hidden="true" title="{{ _('Mark this feed as read') }}"></i></a>
@@ -73,7 +73,7 @@
                 {% if feed_id == fid %}</b>{% endif %}
             </a></li>
             <li class="nav-item feed-commands {% if in_error.get(fid, 0) > 0 %}d-none{% endif %}" data-bs-feed="{{ fid }}"><span class="nav-link">
-                <a href="/feed/{{ fid }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
+                <a href="{{ prefix() }}/feed/{{ fid }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
                 <a href="{{ url_for('feed.form', feed_id=fid) }}"><i class="fa fa-pencil-square-o" aria-hidden="true" title="{{ _('Edit this feed') }}"></i></a>
                 <a href="{{ url_for('article.mark_as', new_value='unread', feed_id=fid) }}"><i class="fa fa-square-o" aria-hidden="true" title="{{ _('Mark this feed as unread') }}"></i></a>
                 <a href="{{ url_for('article.mark_as', new_value='read', feed_id=fid) }}"><i class="fa fa-check-square-o" aria-hidden="true" title="{{ _('Mark this feed as read') }}"></i></a>
@@ -102,7 +102,7 @@
                 {% if feed_id == fid %}</b>{% endif %}
             </a></li>
             <li class="nav-item feed-commands {% if in_error.get(fid, 0) > 0 %}d-none{% endif %}" data-bs-feed="{{ fid }}"><span class="nav-link">
-                <a href="/feed/{{ fid }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
+                <a href="{{ prefix() }}/feed/{{ fid }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
                 <a href="{{ url_for('feed.form', feed_id=fid) }}"><i class="fa fa-pencil-square-o" aria-hidden="true" title="{{ _('Edit this feed') }}"></i></a>
                 <a href="{{ url_for('article.mark_as', new_value='unread', feed_id=fid) }}"><i class="fa fa-square-o" aria-hidden="true" title="{{ _('Mark this feed as unread') }}"></i></a>
                 <a href="{{ url_for('article.mark_as', new_value='read', feed_id=fid) }}"><i class="fa fa-check-square-o" aria-hidden="true" title="{{ _('Mark this feed as read') }}"></i></a>
@@ -174,11 +174,11 @@
                             {% if not feed_id %}
                                 <td class="d-none d-md-block">
                                     <img src="{{ url_for('icon.icon', url=feeds[article.source.id].icon_url) }}" width="16px">
-                                    <a href="/article/redirect/{{ article.id}}" target="_blank">{{ article.source.title | safe }}</a>
+                                    <a href="{{ prefix() }}/article/redirect/{{ article.id}}" target="_blank">{{ article.source.title | safe }}</a>
                                 </td>
                             {% endif %}
                             <td {%if filter_ == 'all' and article.readed == False %}style='font-weight:bold'{% endif %}>
-                                <a href="/article/{{ article.id }}" title="{{ article.title }}">{{ article.title | truncate(100, False, '...') }}</a>
+                                <a href="{{ prefix() }}/article/{{ article.id }}" title="{{ article.title }}">{{ article.title | truncate(100, False, '...') }}</a>
                             </td>
                             <td class="date d-none d-lg-block">{{ article.date | datetime(format='short') }}</td>
                         </tr>
diff --git a/newspipe/templates/layout.html b/newspipe/templates/layout.html
index 2464040..466ff4e 100644
--- a/newspipe/templates/layout.html
+++ b/newspipe/templates/layout.html
@@ -21,7 +21,7 @@
     {% block menu %}
     <nav class="navbar navbar-expand-lg navbar-dark bg-newspipe-blue">
        <div class="container-fluid">
-        <a class="navbar-brand" href="/">Newspipe</a>
+        <a class="navbar-brand" href="{{ prefix() }}/">Newspipe</a>
         <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
             <span class="navbar-toggler-icon"></span>
         </button>
@@ -150,6 +150,7 @@
     {% block content %}{% endblock %}

     <!-- Placed at the end of the document so the pages load faster -->
+    <script type="text/javascript" src="{{ url_for('static', filename = 'js/config.js') }}"></script>
     <script type="text/javascript" src="{{ url_for('static', filename = 'js/articles.js') }}"></script>
     <script type="text/javascript" src="{{ url_for('static', filename = 'js/feed.js') }}"></script>
   </body>
diff --git a/newspipe/templates/login.html b/newspipe/templates/login.html
index b995230..78af3c4 100644
--- a/newspipe/templates/login.html
+++ b/newspipe/templates/login.html
@@ -19,7 +19,7 @@
                     <div class="alert alert-warning" role="alert">{{ message }}</div>
                 {% endfor %}
                 {{ form.submit(class_="btn btn-primary") }}
-                {% if self_registration %}<a href="/signup" class="btn btn-info">{{ _('Sign up') }}</a>{% endif %}
+                {% if self_registration %}<a href="{{ prefix() }}/signup" class="btn btn-info">{{ _('Sign up') }}</a>{% endif %}
             </form>
         </div>
     </div>
diff --git a/newspipe/templates/management.html b/newspipe/templates/management.html
index 4e977f8..19dbded 100644
--- a/newspipe/templates/management.html
+++ b/newspipe/templates/management.html
@@ -6,7 +6,7 @@
             <div class="row">
                 <div class="col">
                     <h2>{{ _('Your subscriptions') }}</h2>
-                    <p>{{ _('You are subscribed to') }} {{ nb_feeds }} <a href="/feeds">{{ _('feeds') }}</a>. <a href="{{ url_for("feed.form") }}">{{ _('Add') }}</a> {{ _('a feed') }}.</p>
+                    <p>{{ _('You are subscribed to') }} {{ nb_feeds }} <a href="{{ prefix() }}/feeds">{{ _('feeds') }}</a>. <a href="{{ url_for("feed.form") }}">{{ _('Add') }}</a> {{ _('a feed') }}.</p>
                     <p>{{ nb_articles }} {{ _('articles are stored in the database with') }} {{ nb_unread_articles }} {{ _('unread articles') }}.</p>
                     <p>{{ _('You have') }} {{ nb_categories }} <a href="{{ url_for("categories.list_")}}">{{ _('categories') }}</a>.</p>
                     <a href="{{ url_for("articles.expire", weeks=10) }}" class="btn btn-primary" onclick="return confirm('{{ _('You are going to delete old articles.') }}');">{{ _('Delete articles older than 10 weeks') }}</a>

Comments