From 3aefbb548efcef558f4b96de1d8df7cad597b572 Mon Sep 17 00:00:00 2001 From: Bart Schuurmans Date: Sun, 7 Apr 2024 17:19:48 +0200 Subject: [PATCH] Allow serving BookWyrm on a non-standard port --- .env.example | 11 ++++++++--- bookwyrm/connectors/connector_manager.py | 9 ++++++--- bookwyrm/forms/links.py | 2 +- bookwyrm/models/connector.py | 2 +- bookwyrm/models/federated_server.py | 5 ++--- bookwyrm/models/link.py | 2 +- bookwyrm/models/user.py | 4 ++-- bookwyrm/settings.py | 23 +++++++++++++---------- bookwyrm/templatetags/utilities.py | 2 +- bookwyrm/tests/test_signing.py | 4 ++-- pytest.ini | 1 + 11 files changed, 38 insertions(+), 27 deletions(-) diff --git a/.env.example b/.env.example index 6a217df0c..c61ceba1e 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,11 @@ DEFAULT_LANGUAGE="English" ## Leave unset to allow all hosts # ALLOWED_HOSTS="localhost,127.0.0.1,[::1]" +# Specify when the site is served from a port that is not the default +# for the protocol (80 for HTTP or 443 for HTTPS). +# Probably only necessary in development. +# PORT=1333 + MEDIA_ROOT=images/ # Database configuration @@ -139,9 +144,9 @@ HTTP_X_FORWARDED_PROTO=false TWO_FACTOR_LOGIN_VALIDITY_WINDOW=2 TWO_FACTOR_LOGIN_MAX_SECONDS=60 -# Additional hosts to allow in the Content-Security-Policy, "self" (should be DOMAIN) -# and AWS_S3_CUSTOM_DOMAIN (if used) are added by default. -# Value should be a comma-separated list of host names. +# Additional hosts to allow in the Content-Security-Policy, "self" (should be +# DOMAIN with optionally ":" + PORT) and AWS_S3_CUSTOM_DOMAIN (if used) are +# added by default. Value should be a comma-separated list of host names. CSP_ADDITIONAL_HOSTS= # Time before being logged out (in seconds) diff --git a/bookwyrm/connectors/connector_manager.py b/bookwyrm/connectors/connector_manager.py index 444a626ba..bdea00719 100644 --- a/bookwyrm/connectors/connector_manager.py +++ b/bookwyrm/connectors/connector_manager.py @@ -118,9 +118,9 @@ def get_connectors() -> Iterator[abstract_connector.AbstractConnector]: def get_or_create_connector(remote_id: str) -> abstract_connector.AbstractConnector: """get the connector related to the object's server""" url = urlparse(remote_id) - identifier = url.netloc + identifier = url.hostname if not identifier: - raise ValueError("Invalid remote id") + raise ValueError(f"Invalid remote id: {remote_id}") try: connector_info = models.Connector.objects.get(identifier=identifier) @@ -188,8 +188,11 @@ def raise_not_valid_url(url: str) -> None: if not parsed.scheme in ["http", "https"]: raise ConnectorException("Invalid scheme: ", url) + if not parsed.hostname: + raise ConnectorException("Hostname missing: ", url) + try: - ipaddress.ip_address(parsed.netloc) + ipaddress.ip_address(parsed.hostname) raise ConnectorException("Provided url is an IP address: ", url) except ValueError: # it's not an IP address, which is good diff --git a/bookwyrm/forms/links.py b/bookwyrm/forms/links.py index 345c5c1d4..5156d2578 100644 --- a/bookwyrm/forms/links.py +++ b/bookwyrm/forms/links.py @@ -26,7 +26,7 @@ class FileLinkForm(CustomForm): url = cleaned_data.get("url") filetype = cleaned_data.get("filetype") book = cleaned_data.get("book") - domain = urlparse(url).netloc + domain = urlparse(url).hostname if models.LinkDomain.objects.filter(domain=domain).exists(): status = models.LinkDomain.objects.get(domain=domain).status if status == "blocked": diff --git a/bookwyrm/models/connector.py b/bookwyrm/models/connector.py index 99e73ab37..f4b5be04c 100644 --- a/bookwyrm/models/connector.py +++ b/bookwyrm/models/connector.py @@ -11,7 +11,7 @@ ConnectorFiles = models.TextChoices("ConnectorFiles", CONNECTORS) class Connector(BookWyrmModel): """book data source connectors""" - identifier = models.CharField(max_length=255, unique=True) + identifier = models.CharField(max_length=255, unique=True) # domain priority = models.IntegerField(default=2) name = models.CharField(max_length=255, null=True, blank=True) connector_file = models.CharField(max_length=255, choices=ConnectorFiles.choices) diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index e1081ed45..5e08fc11d 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -16,7 +16,7 @@ FederationStatus = [ class FederatedServer(BookWyrmModel): """store which servers we federate with""" - server_name = models.CharField(max_length=255, unique=True) + server_name = models.CharField(max_length=255, unique=True) # domain status = models.CharField( max_length=255, default="federated", choices=FederationStatus ) @@ -64,5 +64,4 @@ class FederatedServer(BookWyrmModel): def is_blocked(cls, url: str) -> bool: """look up if a domain is blocked""" url = urlparse(url) - domain = url.netloc - return cls.objects.filter(server_name=domain, status="blocked").exists() + return cls.objects.filter(server_name=url.hostname, status="blocked").exists() diff --git a/bookwyrm/models/link.py b/bookwyrm/models/link.py index d334a9d29..67bf9c4d4 100644 --- a/bookwyrm/models/link.py +++ b/bookwyrm/models/link.py @@ -38,7 +38,7 @@ class Link(ActivitypubMixin, BookWyrmModel): """create a link""" # get or create the associated domain if not self.domain: - domain = urlparse(self.url).netloc + domain = urlparse(self.url).hostname self.domain, _ = LinkDomain.objects.get_or_create(domain=domain) # this is never broadcast, the owning model broadcasts an update diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 0ec2c6529..8db9af7b6 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -349,7 +349,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): if not self.local and not re.match(regex.FULL_USERNAME, self.username): # generate a username that uses the domain (webfinger format) actor_parts = urlparse(self.remote_id) - self.username = f"{self.username}@{actor_parts.netloc}" + self.username = f"{self.username}@{actor_parts.hostname}" # this user already exists, no need to populate fields if not created: @@ -558,7 +558,7 @@ def set_remote_server(user_id, allow_external_connections=False): user = User.objects.get(id=user_id) actor_parts = urlparse(user.remote_id) federated_server = get_or_create_remote_server( - actor_parts.netloc, allow_external_connections=allow_external_connections + actor_parts.hostname, allow_external_connections=allow_external_connections ) # if we were unable to find the server, we need to create a new entry for it if not federated_server: diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 27c86a22a..72c5221a4 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -350,28 +350,31 @@ USE_L10N = True USE_TZ = True - -USER_AGENT = f"BookWyrm (BookWyrm/{VERSION}; +https://{DOMAIN}/)" - # Imagekit generated thumbnails ENABLE_THUMBNAIL_GENERATION = env.bool("ENABLE_THUMBNAIL_GENERATION", False) IMAGEKIT_CACHEFILE_DIR = "thumbnails" IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = "bookwyrm.thumbnail_generation.Strategy" -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.2/howto/static-files/ - PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) CSP_ADDITIONAL_HOSTS = env.list("CSP_ADDITIONAL_HOSTS", []) -# Storage - PROTOCOL = "http" if USE_HTTPS: PROTOCOL = "https" SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True +PORT = env.int("PORT", 443 if USE_HTTPS else 80) +if (USE_HTTPS and PORT == 443) or (not USE_HTTPS and PORT == 80): + NETLOC = DOMAIN +else: + NETLOC = f"{DOMAIN}:{PORT}" +BASE_URL = f"{PROTOCOL}://{NETLOC}" + +USER_AGENT = f"BookWyrm (BookWyrm/{VERSION}; +{BASE_URL})" + +# Storage + USE_S3 = env.bool("USE_S3", False) USE_AZURE = env.bool("USE_AZURE", False) S3_SIGNED_URL_EXPIRY = env.int("S3_SIGNED_URL_EXPIRY", 900) @@ -440,11 +443,11 @@ elif USE_AZURE: else: # Static settings STATIC_URL = "/static/" - STATIC_FULL_URL = f"{PROTOCOL}://{DOMAIN}{STATIC_URL}" + STATIC_FULL_URL = BASE_URL + STATIC_URL STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage" # Media settings MEDIA_URL = "/images/" - MEDIA_FULL_URL = f"{PROTOCOL}://{DOMAIN}{MEDIA_URL}" + MEDIA_FULL_URL = BASE_URL + MEDIA_URL DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage" # Exports settings EXPORTS_STORAGE = "bookwyrm.storage_backends.ExportsFileStorage" diff --git a/bookwyrm/templatetags/utilities.py b/bookwyrm/templatetags/utilities.py index fb2113de4..bc87a60d7 100644 --- a/bookwyrm/templatetags/utilities.py +++ b/bookwyrm/templatetags/utilities.py @@ -120,7 +120,7 @@ def id_to_username(user_id): """given an arbitrary remote id, return the username""" if user_id: url = urlparse(user_id) - domain = url.netloc + domain = url.hostname parts = url.path.split("/") name = parts[-1] value = f"{name}@{domain}" diff --git a/bookwyrm/tests/test_signing.py b/bookwyrm/tests/test_signing.py index e41548bcf..79370844a 100644 --- a/bookwyrm/tests/test_signing.py +++ b/bookwyrm/tests/test_signing.py @@ -15,7 +15,7 @@ from django.utils.http import http_date from bookwyrm import models from bookwyrm.activitypub import Follow -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import DOMAIN, NETLOC from bookwyrm.signatures import create_key_pair, make_signature, make_digest @@ -77,7 +77,7 @@ class Signature(TestCase): "HTTP_SIGNATURE": signature, "HTTP_DIGEST": digest, "HTTP_CONTENT_TYPE": "application/activity+json; charset=utf-8", - "HTTP_HOST": DOMAIN, + "HTTP_HOST": NETLOC, }, ) diff --git a/pytest.ini b/pytest.ini index 2988a7dc5..b963fb316 100644 --- a/pytest.ini +++ b/pytest.ini @@ -11,6 +11,7 @@ env = DEBUG = false USE_HTTPS = true DOMAIN = your.domain.here + PORT = 4242 ALLOWED_HOSTS = your.domain.here BOOKWYRM_DATABASE_BACKEND = postgres MEDIA_ROOT = images/