Repository: paperless-ngx/paperless-ngx

Issue:
[BUG] Incorrect logout redirection with subpath
### Description

When `paperless-ngx` is configured under a subpath (eg. `https://example.com/paperless`) on logout it redirects to webserver root (`https://example.com`) instead of `paperless-ngx` root (`https://example.com/paperless`). The current workaround is to set `PAPERLESS_LOGOUT_REDIRECT_URL` to `https://example.com/paperless` explicitly. Previous versions did not have such behaviour.

### Steps to reproduce

1. Configure `paperless-ngx` under subpath (eg. `https://example.com/paperless`)
This step involves setting `PAPERLESS_URL` to `https://example.com` and `PAPERLESS_FORCE_SCRIPT_NAME` to `/paperless`
3. Login
4. Logout
5. On logout, the user gets redirected to `https://example.com` instead of `https://example.com/paperless`

### Webserver logs

```bash
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:29 +0100] "POST /paperless/accounts/login/?next=/paperless/ HTTP/3.0" 302 888 0.581/0.581 "https://example.com/paperless/accounts/login/?next=/paperless/" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|-"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:29 +0100] "GET /paperless/ HTTP/3.0" 200 3834 0.011/0.012 "https://example.com/paperless/accounts/login/?next=/paperless/" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|2.69"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:29 +0100] "GET /paperless/static/frontend/en-US/styles.css HTTP/3.0" 304 399 0.003/0.002 "https://example.com/paperless/" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|-"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:29 +0100] "GET /paperless/static/frontend/en-US/runtime.js HTTP/3.0" 304 398 0.004/0.005 "https://example.com/paperless/" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|-"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:29 +0100] "GET /paperless/static/frontend/en-US/polyfills.js HTTP/3.0" 304 398 0.005/0.005 "https://example.com/paperless/" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|-"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:29 +0100] "GET /paperless/static/frontend/en-US/main.js HTTP/3.0" 304 399 0.005/0.005 "https://example.com/paperless/" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|-"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:29 +0100] "GET /paperless/api/ui_settings/ HTTP/3.0" 200 1492 0.017/0.016 "https://example.com/paperless/" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|3.98"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:29 +0100] "GET /paperless/favicon.ico HTTP/3.0" 200 111453 0.008/0.007 "https://example.com/paperless/" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|-"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:29 +0100] "GET /paperless/api/saved_views/?page=1&page_size=100000 HTTP/3.0" 200 533 0.027/0.027 "https://example.com/paperless/dashboard" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|1.15"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:29 +0100] "GET /paperless/api/tasks/ HTTP/3.0" 200 1193 0.034/0.034 "https://example.com/paperless/dashboard" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|3.65"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:29 +0100] "GET /paperless/api/statistics/ HTTP/3.0" 200 630 0.041/0.041 "https://example.com/paperless/dashboard" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|1.98"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:47 +0100] "GET /paperless/ws/status/ HTTP/1.1" 101 561 17.705/17.705 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|-"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:47 +0100] "GET /paperless/accounts/logout/ HTTP/3.0" 302 632 0.019/0.019 "https://example.com/paperless/dashboard" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|-"
2a02:a466:ff0:1::4 - - [02/Mar/2024:17:02:47 +0100] "GET /sympa HTTP/3.0" 200 2851 0.084/0.084 "https://example.com/" "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0" "-|3.96"
```


### Browser logs

_No response_

### Paperless-ngx version

2.5.4

### Host OS

Gentoo Linux x86_64

### Installation method

Bare metal

### Browser

Firefox

### Configuration changes

PAPERLESS_URL=https://example.com PAPERLESS_FORCE_SCRIPT_NAME=/paperless

### Other

_No response_

### Please confirm the following

- [X] I believe this issue is a bug that affects all users of Paperless-ngx, not something specific to my installation.
- [X] I have already searched for relevant existing issues and discussions before opening this report.
- [X] I have updated the title field above with a concise description.


Discussion:
Probably allauth again...

I'd assume some configuration of [ACCOUNT_LOGOUT_REDIRECT_URL](https://docs.allauth.org/en/latest/account/configuration.html) when `PAPERLESS_FORCE_SCRIPT_NAME` has been set

Resolving patch:
diff --git a/src/paperless/settings.py b/src/paperless/settings.py
--- a/src/paperless/settings.py
+++ b/src/paperless/settings.py
@@ -351,11 +351,19 @@ def _parse_beat_schedule() -> dict:
 
 ROOT_URLCONF = "paperless.urls"
 
-FORCE_SCRIPT_NAME = os.getenv("PAPERLESS_FORCE_SCRIPT_NAME")
-BASE_URL = (FORCE_SCRIPT_NAME or "") + "/"
-LOGIN_URL = BASE_URL + "accounts/login/"
-LOGIN_REDIRECT_URL = "/dashboard"
-LOGOUT_REDIRECT_URL = os.getenv("PAPERLESS_LOGOUT_REDIRECT_URL")
+
+def _parse_base_paths() -> tuple[str, str, str, str, str]:
+    script_name = os.getenv("PAPERLESS_FORCE_SCRIPT_NAME")
+    base_url = (script_name or "") + "/"
+    login_url = base_url + "accounts/login/"
+    login_redirect_url = base_url + "dashboard"
+    logout_redirect_url = os.getenv("PAPERLESS_LOGOUT_REDIRECT_URL", base_url)
+    return script_name, base_url, login_url, login_redirect_url, logout_redirect_url
+
+
+FORCE_SCRIPT_NAME, BASE_URL, LOGIN_URL, LOGIN_REDIRECT_URL, LOGOUT_REDIRECT_URL = (
+    _parse_base_paths()
+)
 
 WSGI_APPLICATION = "paperless.wsgi.application"
 ASGI_APPLICATION = "paperless.asgi.application"


Test patch:
diff --git a/src/paperless/tests/test_settings.py b/src/paperless/tests/test_settings.py
--- a/src/paperless/tests/test_settings.py
+++ b/src/paperless/tests/test_settings.py
@@ -5,6 +5,7 @@
 
 from celery.schedules import crontab
 
+from paperless.settings import _parse_base_paths
 from paperless.settings import _parse_beat_schedule
 from paperless.settings import _parse_db_settings
 from paperless.settings import _parse_ignore_dates
@@ -374,3 +375,58 @@ def test_paperless_url(self):
 
             self.assertIn(url, settings.CSRF_TRUSTED_ORIGINS)
             self.assertIn(url, settings.CORS_ALLOWED_ORIGINS)
+
+
+class TestPathSettings(TestCase):
+    def test_default_paths(self):
+        """
+        GIVEN:
+            - PAPERLESS_FORCE_SCRIPT_NAME is not set
+        WHEN:
+            - Settings are parsed
+        THEN:
+            - Paths are as expected
+        """
+        base_paths = _parse_base_paths()
+        self.assertEqual(None, base_paths[0])  # FORCE_SCRIPT_NAME
+        self.assertEqual("/", base_paths[1])  # BASE_URL
+        self.assertEqual("/accounts/login/", base_paths[2])  # LOGIN_URL
+        self.assertEqual("/dashboard", base_paths[3])  # LOGIN_REDIRECT_URL
+        self.assertEqual("/", base_paths[4])  # LOGOUT_REDIRECT_URL
+
+    @mock.patch("os.environ", {"PAPERLESS_FORCE_SCRIPT_NAME": "/paperless"})
+    def test_subpath(self):
+        """
+        GIVEN:
+            - PAPERLESS_FORCE_SCRIPT_NAME is set
+        WHEN:
+            - Settings are parsed
+        THEN:
+            - The path is returned and present in related settings
+        """
+        base_paths = _parse_base_paths()
+        self.assertEqual("/paperless", base_paths[0])  # FORCE_SCRIPT_NAME
+        self.assertEqual("/paperless/", base_paths[1])  # BASE_URL
+        self.assertEqual("/paperless/accounts/login/", base_paths[2])  # LOGIN_URL
+        self.assertEqual("/paperless/dashboard", base_paths[3])  # LOGIN_REDIRECT_URL
+        self.assertEqual("/paperless/", base_paths[4])  # LOGOUT_REDIRECT_URL
+
+    @mock.patch(
+        "os.environ",
+        {
+            "PAPERLESS_FORCE_SCRIPT_NAME": "/paperless",
+            "PAPERLESS_LOGOUT_REDIRECT_URL": "/foobar/",
+        },
+    )
+    def test_subpath_with_explicit_logout_url(self):
+        """
+        GIVEN:
+            - PAPERLESS_FORCE_SCRIPT_NAME is set and so is PAPERLESS_LOGOUT_REDIRECT_URL
+        WHEN:
+            - Settings are parsed
+        THEN:
+            - The correct logout redirect URL is returned
+        """
+        base_paths = _parse_base_paths()
+        self.assertEqual("/paperless/", base_paths[1])  # BASE_URL
+        self.assertEqual("/foobar/", base_paths[4])  # LOGOUT_REDIRECT_URL