Allow serving BookWyrm on a non-standard port

This commit is contained in:
Bart Schuurmans 2024-04-07 17:19:48 +02:00
parent baea105c18
commit 3aefbb548e
11 changed files with 38 additions and 27 deletions

View file

@ -16,6 +16,11 @@ DEFAULT_LANGUAGE="English"
## Leave unset to allow all hosts ## Leave unset to allow all hosts
# ALLOWED_HOSTS="localhost,127.0.0.1,[::1]" # 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/ MEDIA_ROOT=images/
# Database configuration # Database configuration
@ -139,9 +144,9 @@ HTTP_X_FORWARDED_PROTO=false
TWO_FACTOR_LOGIN_VALIDITY_WINDOW=2 TWO_FACTOR_LOGIN_VALIDITY_WINDOW=2
TWO_FACTOR_LOGIN_MAX_SECONDS=60 TWO_FACTOR_LOGIN_MAX_SECONDS=60
# Additional hosts to allow in the Content-Security-Policy, "self" (should be DOMAIN) # Additional hosts to allow in the Content-Security-Policy, "self" (should be
# and AWS_S3_CUSTOM_DOMAIN (if used) are added by default. # DOMAIN with optionally ":" + PORT) and AWS_S3_CUSTOM_DOMAIN (if used) are
# Value should be a comma-separated list of host names. # added by default. Value should be a comma-separated list of host names.
CSP_ADDITIONAL_HOSTS= CSP_ADDITIONAL_HOSTS=
# Time before being logged out (in seconds) # Time before being logged out (in seconds)

View file

@ -118,9 +118,9 @@ def get_connectors() -> Iterator[abstract_connector.AbstractConnector]:
def get_or_create_connector(remote_id: str) -> abstract_connector.AbstractConnector: def get_or_create_connector(remote_id: str) -> abstract_connector.AbstractConnector:
"""get the connector related to the object's server""" """get the connector related to the object's server"""
url = urlparse(remote_id) url = urlparse(remote_id)
identifier = url.netloc identifier = url.hostname
if not identifier: if not identifier:
raise ValueError("Invalid remote id") raise ValueError(f"Invalid remote id: {remote_id}")
try: try:
connector_info = models.Connector.objects.get(identifier=identifier) 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"]: if not parsed.scheme in ["http", "https"]:
raise ConnectorException("Invalid scheme: ", url) raise ConnectorException("Invalid scheme: ", url)
if not parsed.hostname:
raise ConnectorException("Hostname missing: ", url)
try: try:
ipaddress.ip_address(parsed.netloc) ipaddress.ip_address(parsed.hostname)
raise ConnectorException("Provided url is an IP address: ", url) raise ConnectorException("Provided url is an IP address: ", url)
except ValueError: except ValueError:
# it's not an IP address, which is good # it's not an IP address, which is good

View file

@ -26,7 +26,7 @@ class FileLinkForm(CustomForm):
url = cleaned_data.get("url") url = cleaned_data.get("url")
filetype = cleaned_data.get("filetype") filetype = cleaned_data.get("filetype")
book = cleaned_data.get("book") book = cleaned_data.get("book")
domain = urlparse(url).netloc domain = urlparse(url).hostname
if models.LinkDomain.objects.filter(domain=domain).exists(): if models.LinkDomain.objects.filter(domain=domain).exists():
status = models.LinkDomain.objects.get(domain=domain).status status = models.LinkDomain.objects.get(domain=domain).status
if status == "blocked": if status == "blocked":

View file

@ -11,7 +11,7 @@ ConnectorFiles = models.TextChoices("ConnectorFiles", CONNECTORS)
class Connector(BookWyrmModel): class Connector(BookWyrmModel):
"""book data source connectors""" """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) priority = models.IntegerField(default=2)
name = models.CharField(max_length=255, null=True, blank=True) name = models.CharField(max_length=255, null=True, blank=True)
connector_file = models.CharField(max_length=255, choices=ConnectorFiles.choices) connector_file = models.CharField(max_length=255, choices=ConnectorFiles.choices)

View file

@ -16,7 +16,7 @@ FederationStatus = [
class FederatedServer(BookWyrmModel): class FederatedServer(BookWyrmModel):
"""store which servers we federate with""" """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( status = models.CharField(
max_length=255, default="federated", choices=FederationStatus max_length=255, default="federated", choices=FederationStatus
) )
@ -64,5 +64,4 @@ class FederatedServer(BookWyrmModel):
def is_blocked(cls, url: str) -> bool: def is_blocked(cls, url: str) -> bool:
"""look up if a domain is blocked""" """look up if a domain is blocked"""
url = urlparse(url) url = urlparse(url)
domain = url.netloc return cls.objects.filter(server_name=url.hostname, status="blocked").exists()
return cls.objects.filter(server_name=domain, status="blocked").exists()

View file

@ -38,7 +38,7 @@ class Link(ActivitypubMixin, BookWyrmModel):
"""create a link""" """create a link"""
# get or create the associated domain # get or create the associated domain
if not self.domain: if not self.domain:
domain = urlparse(self.url).netloc domain = urlparse(self.url).hostname
self.domain, _ = LinkDomain.objects.get_or_create(domain=domain) self.domain, _ = LinkDomain.objects.get_or_create(domain=domain)
# this is never broadcast, the owning model broadcasts an update # this is never broadcast, the owning model broadcasts an update

View file

@ -349,7 +349,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
if not self.local and not re.match(regex.FULL_USERNAME, self.username): if not self.local and not re.match(regex.FULL_USERNAME, self.username):
# generate a username that uses the domain (webfinger format) # generate a username that uses the domain (webfinger format)
actor_parts = urlparse(self.remote_id) 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 # this user already exists, no need to populate fields
if not created: if not created:
@ -558,7 +558,7 @@ def set_remote_server(user_id, allow_external_connections=False):
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
actor_parts = urlparse(user.remote_id) actor_parts = urlparse(user.remote_id)
federated_server = get_or_create_remote_server( 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 we were unable to find the server, we need to create a new entry for it
if not federated_server: if not federated_server:

View file

@ -350,28 +350,31 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
USER_AGENT = f"BookWyrm (BookWyrm/{VERSION}; +https://{DOMAIN}/)"
# Imagekit generated thumbnails # Imagekit generated thumbnails
ENABLE_THUMBNAIL_GENERATION = env.bool("ENABLE_THUMBNAIL_GENERATION", False) ENABLE_THUMBNAIL_GENERATION = env.bool("ENABLE_THUMBNAIL_GENERATION", False)
IMAGEKIT_CACHEFILE_DIR = "thumbnails" IMAGEKIT_CACHEFILE_DIR = "thumbnails"
IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = "bookwyrm.thumbnail_generation.Strategy" 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__)) PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
CSP_ADDITIONAL_HOSTS = env.list("CSP_ADDITIONAL_HOSTS", []) CSP_ADDITIONAL_HOSTS = env.list("CSP_ADDITIONAL_HOSTS", [])
# Storage
PROTOCOL = "http" PROTOCOL = "http"
if USE_HTTPS: if USE_HTTPS:
PROTOCOL = "https" PROTOCOL = "https"
SESSION_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True
CSRF_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_S3 = env.bool("USE_S3", False)
USE_AZURE = env.bool("USE_AZURE", False) USE_AZURE = env.bool("USE_AZURE", False)
S3_SIGNED_URL_EXPIRY = env.int("S3_SIGNED_URL_EXPIRY", 900) S3_SIGNED_URL_EXPIRY = env.int("S3_SIGNED_URL_EXPIRY", 900)
@ -440,11 +443,11 @@ elif USE_AZURE:
else: else:
# Static settings # Static settings
STATIC_URL = "/static/" 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" STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
# Media settings # Media settings
MEDIA_URL = "/images/" 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" DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
# Exports settings # Exports settings
EXPORTS_STORAGE = "bookwyrm.storage_backends.ExportsFileStorage" EXPORTS_STORAGE = "bookwyrm.storage_backends.ExportsFileStorage"

View file

@ -120,7 +120,7 @@ def id_to_username(user_id):
"""given an arbitrary remote id, return the username""" """given an arbitrary remote id, return the username"""
if user_id: if user_id:
url = urlparse(user_id) url = urlparse(user_id)
domain = url.netloc domain = url.hostname
parts = url.path.split("/") parts = url.path.split("/")
name = parts[-1] name = parts[-1]
value = f"{name}@{domain}" value = f"{name}@{domain}"

View file

@ -15,7 +15,7 @@ from django.utils.http import http_date
from bookwyrm import models from bookwyrm import models
from bookwyrm.activitypub import Follow 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 from bookwyrm.signatures import create_key_pair, make_signature, make_digest
@ -77,7 +77,7 @@ class Signature(TestCase):
"HTTP_SIGNATURE": signature, "HTTP_SIGNATURE": signature,
"HTTP_DIGEST": digest, "HTTP_DIGEST": digest,
"HTTP_CONTENT_TYPE": "application/activity+json; charset=utf-8", "HTTP_CONTENT_TYPE": "application/activity+json; charset=utf-8",
"HTTP_HOST": DOMAIN, "HTTP_HOST": NETLOC,
}, },
) )

View file

@ -11,6 +11,7 @@ env =
DEBUG = false DEBUG = false
USE_HTTPS = true USE_HTTPS = true
DOMAIN = your.domain.here DOMAIN = your.domain.here
PORT = 4242
ALLOWED_HOSTS = your.domain.here ALLOWED_HOSTS = your.domain.here
BOOKWYRM_DATABASE_BACKEND = postgres BOOKWYRM_DATABASE_BACKEND = postgres
MEDIA_ROOT = images/ MEDIA_ROOT = images/