# Locked version until https://github.com/django/channels_redis/issues/332
# is resolved
channels-redis = "==3.4.1"
+django-ipware = "*"
[dev-packages]
{
"_meta": {
"hash": {
- "sha256": "99f415c5ce96020dc3fcb137dc15d47cc5431686bdce1ca42e6254a2719060a8"
+ "sha256": "0e1a26c5e9acb1d745f951f92d00d60272f83406467d90551e558972697b53cd"
},
"pipfile-spec": 6,
"requires": {},
"index": "pypi",
"version": "==2.4.0"
},
+ "django-ipware": {
+ "hashes": [
+ "sha256:602a58325a4808bd19197fef2676a0b2da2df40d0ecf21be414b2ff48c72ad05",
+ "sha256:878dbb06a87e25550798e9ef3204ed70a200dd8b15e47dcef848cf08244f04c9"
+ ],
+ "index": "pypi",
+ "version": "==4.0.2"
+ },
"djangorestframework": {
"hashes": [
"sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8",
Defaults to "<http://localhost:8000>".
+`PAPERLESS_TRUSTED_PROXIES=<comma-separated-list>`
+
+: This may be needed to prevent IP address spoofing if you are using e.g.
+fail2ban with log entries for failed authorization attempts. Value should be
+IP address(es).
+
+ Defaults to empty string.
+
`PAPERLESS_FORCE_SCRIPT_NAME=<path>`
: To host paperless under a subpath url like example.com/paperless you
--- /dev/null
+from django.apps import AppConfig
+from django.utils.translation import gettext_lazy as _
+from paperless.signals import handle_failed_login
+
+
+class PaperlessConfig(AppConfig):
+ name = "paperless"
+
+ verbose_name = _("Paperless")
+
+ def ready(self):
+ from django.contrib.auth.signals import user_login_failed
+
+ user_login_failed.connect(handle_failed_login)
+ AppConfig.ready(self)
# always allow localhost. Necessary e.g. for healthcheck in docker.
ALLOWED_HOSTS = [_paperless_uri.hostname] + ["localhost"]
+# For use with trusted proxies
+_trusted_proxies = os.getenv("PAPERLESS_TRUSTED_PROXIES")
+if _trusted_proxies:
+ TRUSTED_PROXIES = _trusted_proxies.split(",")
+else:
+ TRUSTED_PROXIES = []
+
# The secret key has a default that should be fine so long as you're hosting
# Paperless on a closed network. However, if you're putting this anywhere
# public, you should change the key to something unique and verbose.
--- /dev/null
+import logging
+
+from django.conf import settings
+from ipware import get_client_ip
+
+logger = logging.getLogger("paperless.auth")
+
+
+# https://docs.djangoproject.com/en/4.1/ref/contrib/auth/#django.contrib.auth.signals.user_login_failed
+def handle_failed_login(sender, credentials, request, **kwargs):
+ client_ip, is_routable = get_client_ip(
+ request,
+ proxy_trusted_ips=settings.TRUSTED_PROXIES,
+ )
+ if client_ip is None:
+ logger.info(
+ f"Login failed for user `{credentials['username']}`."
+ + " Unable to determine IP address.",
+ )
+ else:
+ if is_routable:
+ # We got the client's IP address
+ logger.info(
+ f"Login failed for user `{credentials['username']}`"
+ + f" from IP `{client_ip}.`",
+ )
+ else:
+ # The client's IP address is private
+ logger.info(
+ f"Login failed for user `{credentials['username']}`"
+ + f" from private IP `{client_ip}.`",
+ )