Skip to content

Commit

Permalink
Merge pull request #3350 from Minnozz/custom-port
Browse files Browse the repository at this point in the history
Correctly handle serving BookWyrm on custom port
  • Loading branch information
mouse-reeve committed Apr 24, 2024
2 parents 366c647 + 4f58b11 commit ad830dd
Show file tree
Hide file tree
Showing 43 changed files with 150 additions and 174 deletions.
11 changes: 8 additions & 3 deletions .env.example
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 12 additions & 7 deletions bookwyrm/connectors/connector_manager.py
Expand Up @@ -118,20 +118,22 @@ 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}")

base_url = f"{url.scheme}://{url.netloc}"

try:
connector_info = models.Connector.objects.get(identifier=identifier)
except models.Connector.DoesNotExist:
connector_info = models.Connector.objects.create(
identifier=identifier,
connector_file="bookwyrm_connector",
base_url=f"https://{identifier}",
books_url=f"https://{identifier}/book",
covers_url=f"https://{identifier}/images/covers",
search_url=f"https://{identifier}/search?q=",
base_url=base_url,
books_url=f"{base_url}/book",
covers_url=f"{base_url}/images/covers",
search_url=f"{base_url}/search?q=",
priority=2,
)

Expand Down Expand Up @@ -188,8 +190,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
Expand Down
3 changes: 2 additions & 1 deletion bookwyrm/emailing.py
Expand Up @@ -4,7 +4,7 @@

from bookwyrm import models, settings
from bookwyrm.tasks import app, EMAIL
from bookwyrm.settings import DOMAIN
from bookwyrm.settings import DOMAIN, BASE_URL


def email_data():
Expand All @@ -14,6 +14,7 @@ def email_data():
"site_name": site.name,
"logo": site.logo_small_url,
"domain": DOMAIN,
"base_url": BASE_URL,
"user": None,
}

Expand Down
2 changes: 1 addition & 1 deletion bookwyrm/forms/links.py
Expand Up @@ -26,7 +26,7 @@ def clean(self):
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":
Expand Down
4 changes: 2 additions & 2 deletions bookwyrm/models/author.py
Expand Up @@ -8,7 +8,7 @@
import pgtrigger

from bookwyrm import activitypub
from bookwyrm.settings import DOMAIN
from bookwyrm.settings import BASE_URL
from bookwyrm.utils.db import format_trigger

from .book import BookDataModel, MergedAuthor
Expand Down Expand Up @@ -70,7 +70,7 @@ def isfdb_link(self):

def get_remote_id(self):
"""editions and works both use "book" instead of model_name"""
return f"https://{DOMAIN}/author/{self.id}"
return f"{BASE_URL}/author/{self.id}"

class Meta:
"""sets up indexes and triggers"""
Expand Down
6 changes: 3 additions & 3 deletions bookwyrm/models/base_model.py
Expand Up @@ -10,7 +10,7 @@
from django.utils.translation import gettext_lazy as _
from django.utils.text import slugify

from bookwyrm.settings import DOMAIN
from bookwyrm.settings import BASE_URL
from .fields import RemoteIdField


Expand Down Expand Up @@ -38,7 +38,7 @@ class BookWyrmModel(models.Model):

def get_remote_id(self):
"""generate the url that resolves to the local object, without a slug"""
base_path = f"https://{DOMAIN}"
base_path = BASE_URL
if hasattr(self, "user"):
base_path = f"{base_path}{self.user.local_path}"

Expand All @@ -53,7 +53,7 @@ class Meta:
@property
def local_path(self):
"""how to link to this object in the local app, with a slug"""
local = self.get_remote_id().replace(f"https://{DOMAIN}", "")
local = self.get_remote_id().replace(BASE_URL, "")

name = None
if hasattr(self, "name_field"):
Expand Down
4 changes: 2 additions & 2 deletions bookwyrm/models/book.py
Expand Up @@ -21,7 +21,7 @@
from bookwyrm.isbn.isbn import hyphenator_singleton as hyphenator
from bookwyrm.preview_images import generate_edition_preview_image_task
from bookwyrm.settings import (
DOMAIN,
BASE_URL,
DEFAULT_LANGUAGE,
LANGUAGE_ARTICLES,
ENABLE_PREVIEW_IMAGES,
Expand Down Expand Up @@ -327,7 +327,7 @@ def save(self, *args: Any, **kwargs: Any) -> None:

def get_remote_id(self):
"""editions and works both use "book" instead of model_name"""
return f"https://{DOMAIN}/book/{self.id}"
return f"{BASE_URL}/book/{self.id}"

def guess_sort_title(self):
"""Get a best-guess sort title for the current book"""
Expand Down
2 changes: 1 addition & 1 deletion bookwyrm/models/connector.py
Expand Up @@ -11,7 +11,7 @@
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)
Expand Down
5 changes: 2 additions & 3 deletions bookwyrm/models/federated_server.py
Expand Up @@ -16,7 +16,7 @@
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
)
Expand Down Expand Up @@ -64,5 +64,4 @@ def unblock(self):
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()
4 changes: 2 additions & 2 deletions bookwyrm/models/group.py
@@ -1,7 +1,7 @@
""" do book related things with other users """
from django.db import models, IntegrityError, transaction
from django.db.models import Q
from bookwyrm.settings import DOMAIN
from bookwyrm.settings import BASE_URL
from .base_model import BookWyrmModel
from . import fields
from .relationship import UserBlocks
Expand All @@ -17,7 +17,7 @@ class Group(BookWyrmModel):

def get_remote_id(self):
"""don't want the user to be in there in this case"""
return f"https://{DOMAIN}/group/{self.id}"
return f"{BASE_URL}/group/{self.id}"

@classmethod
def followers_filter(cls, queryset, viewer):
Expand Down
2 changes: 1 addition & 1 deletion bookwyrm/models/link.py
Expand Up @@ -38,7 +38,7 @@ def save(self, *args, **kwargs):
"""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
Expand Down
4 changes: 2 additions & 2 deletions bookwyrm/models/list.py
Expand Up @@ -7,7 +7,7 @@
from django.utils import timezone

from bookwyrm import activitypub
from bookwyrm.settings import DOMAIN
from bookwyrm.settings import BASE_URL

from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin
from .base_model import BookWyrmModel
Expand Down Expand Up @@ -50,7 +50,7 @@ class List(OrderedCollectionMixin, BookWyrmModel):

def get_remote_id(self):
"""don't want the user to be in there in this case"""
return f"https://{DOMAIN}/list/{self.id}"
return f"{BASE_URL}/list/{self.id}"

@property
def collection_queryset(self):
Expand Down
4 changes: 2 additions & 2 deletions bookwyrm/models/report.py
Expand Up @@ -3,7 +3,7 @@
from django.db import models
from django.utils.translation import gettext_lazy as _

from bookwyrm.settings import DOMAIN
from bookwyrm.settings import BASE_URL
from .base_model import BookWyrmModel


Expand Down Expand Up @@ -46,7 +46,7 @@ def raise_not_editable(self, viewer):
raise PermissionDenied()

def get_remote_id(self):
return f"https://{DOMAIN}/settings/reports/{self.id}"
return f"{BASE_URL}/settings/reports/{self.id}"

def comment(self, user, note):
"""comment on a report"""
Expand Down
4 changes: 2 additions & 2 deletions bookwyrm/models/shelf.py
Expand Up @@ -6,7 +6,7 @@
from django.utils import timezone

from bookwyrm import activitypub
from bookwyrm.settings import DOMAIN
from bookwyrm.settings import BASE_URL
from bookwyrm.tasks import BROADCAST
from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin
from .base_model import BookWyrmModel
Expand Down Expand Up @@ -71,7 +71,7 @@ def get_remote_id(self):
@property
def local_path(self):
"""No slugs"""
return self.get_remote_id().replace(f"https://{DOMAIN}", "")
return self.get_remote_id().replace(BASE_URL, "")

def raise_not_deletable(self, viewer):
"""don't let anyone delete a default shelf"""
Expand Down
6 changes: 3 additions & 3 deletions bookwyrm/models/site.py
Expand Up @@ -12,7 +12,7 @@

from bookwyrm.connectors.abstract_connector import get_data
from bookwyrm.preview_images import generate_site_preview_image_task
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES, STATIC_FULL_URL
from bookwyrm.settings import BASE_URL, ENABLE_PREVIEW_IMAGES, STATIC_FULL_URL
from bookwyrm.settings import RELEASE_API
from bookwyrm.tasks import app, MISC
from .base_model import BookWyrmModel, new_access_code
Expand Down Expand Up @@ -188,7 +188,7 @@ def valid(self):
@property
def link(self):
"""formats the invite link"""
return f"https://{DOMAIN}/invite/{self.code}"
return f"{BASE_URL}/invite/{self.code}"


class InviteRequest(BookWyrmModel):
Expand Down Expand Up @@ -235,7 +235,7 @@ def valid(self):
@property
def link(self):
"""formats the invite link"""
return f"https://{DOMAIN}/password-reset/{self.code}"
return f"{BASE_URL}/password-reset/{self.code}"


# pylint: disable=unused-argument
Expand Down
20 changes: 6 additions & 14 deletions bookwyrm/models/user.py
Expand Up @@ -19,7 +19,7 @@
from bookwyrm.models.shelf import Shelf
from bookwyrm.models.status import Status
from bookwyrm.preview_images import generate_user_preview_image_task
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES, USE_HTTPS, LANGUAGES
from bookwyrm.settings import BASE_URL, ENABLE_PREVIEW_IMAGES, LANGUAGES
from bookwyrm.signatures import create_key_pair
from bookwyrm.tasks import app, MISC
from bookwyrm.utils import regex
Expand All @@ -42,12 +42,6 @@ def get_feed_filter_choices():
return [f[0] for f in FeedFilterChoices]


def site_link():
"""helper for generating links to the site"""
protocol = "https" if USE_HTTPS else "http"
return f"{protocol}://{DOMAIN}"


# pylint: disable=too-many-public-methods
class User(OrderedCollectionPageMixin, AbstractUser):
"""a user who wants to read books"""
Expand Down Expand Up @@ -214,8 +208,7 @@ def active_follower_requests(self):
@property
def confirmation_link(self):
"""helper for generating confirmation links"""
link = site_link()
return f"{link}/confirm-email/{self.confirmation_code}"
return f"{BASE_URL}/confirm-email/{self.confirmation_code}"

@property
def following_link(self):
Expand Down Expand Up @@ -349,7 +342,7 @@ def save(self, *args, **kwargs):
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:
Expand All @@ -369,11 +362,10 @@ def save(self, *args, **kwargs):

with transaction.atomic():
# populate fields for local users
link = site_link()
self.remote_id = f"{link}/user/{self.localname}"
self.remote_id = f"{BASE_URL}/user/{self.localname}"
self.followers_url = f"{self.remote_id}/followers"
self.inbox = f"{self.remote_id}/inbox"
self.shared_inbox = f"{link}/inbox"
self.shared_inbox = f"{BASE_URL}/inbox"
self.outbox = f"{self.remote_id}/outbox"

# an id needs to be set before we can proceed with related models
Expand Down Expand Up @@ -558,7 +550,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:
Expand Down

0 comments on commit ad830dd

Please sign in to comment.