tiny_auth_support #6
11
.env.example
11
.env.example
@@ -13,11 +13,6 @@ PYTORRENT_SCGI_RETRIES=8
|
||||
# css/js libs
|
||||
PYTORRENT_USE_OFFLINE_LIBS=true
|
||||
|
||||
# Reverse proxy / HTTPS
|
||||
PYTORRENT_PROXY_FIX_ENABLE=false
|
||||
PYTORRENT_SESSION_COOKIE_SECURE=false
|
||||
# PYTORRENT_SOCKETIO_CORS_ALLOWED_ORIGINS=https://your-domain.com
|
||||
|
||||
# Retention / Smart Queue
|
||||
PYTORRENT_TRAFFIC_HISTORY_RETENTION_DAYS=90
|
||||
PYTORRENT_JOBS_RETENTION_DAYS=30
|
||||
@@ -64,3 +59,9 @@ PYTORRENT_AUTH_PROXY_AUTO_CREATE=true
|
||||
# Defaults for auto-created users
|
||||
PYTORRENT_AUTH_PROXY_AUTO_CREATE_ROLE=admin
|
||||
PYTORRENT_AUTH_PROXY_AUTO_CREATE_PERMISSION=rw
|
||||
|
||||
# Reverse proxy / HTTPS
|
||||
PYTORRENT_PROXY_FIX_ENABLE=true
|
||||
PYTORRENT_SESSION_COOKIE_SECURE=false
|
||||
#PYTORRENT_SOCKETIO_CORS_ALLOWED_ORIGINS=https://pytorrent.domain.com
|
||||
#PYTORRENT_API_ALLOWED_ORIGINS=https://pytorrent.domain.com
|
||||
26
auth.md
26
auth.md
@@ -33,6 +33,28 @@ PYTORRENT_AUTH_PROXY_AUTO_CREATE_ROLE=admin
|
||||
PYTORRENT_AUTH_PROXY_AUTO_CREATE_PERMISSION=rw
|
||||
```
|
||||
|
||||
|
||||
## Reverse proxy origin checks
|
||||
|
||||
pyTorrent blocks unsafe API requests when the browser `Origin`/`Referer` does not match the application origin. Behind HTTPS reverse proxy this requires either correct forwarded headers or an explicit API origin allowlist.
|
||||
|
||||
Recommended variables for reverse proxy mode:
|
||||
|
||||
```env
|
||||
PYTORRENT_PROXY_FIX_ENABLE=true
|
||||
PYTORRENT_SESSION_COOKIE_SECURE=true
|
||||
PYTORRENT_SOCKETIO_CORS_ALLOWED_ORIGINS=https://pytorrent.example.com
|
||||
PYTORRENT_API_ALLOWED_ORIGINS=https://pytorrent.example.com
|
||||
```
|
||||
|
||||
`PYTORRENT_API_ALLOWED_ORIGINS` accepts a comma-separated list, for example:
|
||||
|
||||
```env
|
||||
PYTORRENT_API_ALLOWED_ORIGINS=https://pytorrent.example.com
|
||||
```
|
||||
|
||||
If `PYTORRENT_API_ALLOWED_ORIGINS` is not set, pyTorrent reuses `PYTORRENT_SOCKETIO_CORS_ALLOWED_ORIGINS` for API origin checks.
|
||||
|
||||
## Local authentication
|
||||
|
||||
Use this when pyTorrent should manage its own login screen and passwords.
|
||||
@@ -80,14 +102,14 @@ location / {
|
||||
}
|
||||
|
||||
location /tinyauth {
|
||||
proxy_pass http://10.87.7.99:3000/api/auth/nginx;
|
||||
proxy_pass http://10.10.11.11:3000/api/auth/nginx;
|
||||
proxy_set_header x-forwarded-proto $scheme;
|
||||
proxy_set_header x-forwarded-host $http_host;
|
||||
proxy_set_header x-forwarded-uri $request_uri;
|
||||
}
|
||||
|
||||
location @tinyauth_login {
|
||||
return 302 http://auth.linuxiarz.pl/login?redirect_uri=$scheme://$http_host$request_uri;
|
||||
return 302 http://auth.domian/login?redirect_uri=$scheme://$http_host$request_uri;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -86,8 +86,14 @@ PROXY_FIX_X_HOST = _env_int("PYTORRENT_PROXY_FIX_X_HOST", 1, 0)
|
||||
PROXY_FIX_X_PORT = _env_int("PYTORRENT_PROXY_FIX_X_PORT", 1, 0)
|
||||
PROXY_FIX_X_PREFIX = _env_int("PYTORRENT_PROXY_FIX_X_PREFIX", 1, 0)
|
||||
|
||||
def _env_csv(name: str) -> list[str]:
|
||||
return [item.strip().rstrip("/") for item in os.getenv(name, "").split(",") if item.strip()]
|
||||
|
||||
_SOCKETIO_CORS = os.getenv("PYTORRENT_SOCKETIO_CORS_ALLOWED_ORIGINS", "").strip()
|
||||
SOCKETIO_CORS_ALLOWED_ORIGINS = None if not _SOCKETIO_CORS else [item.strip() for item in _SOCKETIO_CORS.split(",") if item.strip()]
|
||||
# Note: API origin checks are separate from Socket.IO CORS. When unset, reuse the Socket.IO allowlist for operator-friendly reverse proxy setups.
|
||||
_API_ALLOWED_ORIGINS = _env_csv("PYTORRENT_API_ALLOWED_ORIGINS")
|
||||
API_ALLOWED_ORIGINS = _API_ALLOWED_ORIGINS or _env_csv("PYTORRENT_SOCKETIO_CORS_ALLOWED_ORIGINS")
|
||||
|
||||
TRAFFIC_HISTORY_RETENTION_DAYS = _env_int("PYTORRENT_TRAFFIC_HISTORY_RETENTION_DAYS", 90, 1)
|
||||
JOBS_RETENTION_DAYS = _env_int("PYTORRENT_JOBS_RETENTION_DAYS", 30, 1)
|
||||
|
||||
@@ -16,6 +16,7 @@ from ..config import (
|
||||
AUTH_PROXY_AUTO_CREATE_PERMISSION,
|
||||
AUTH_PROXY_AUTO_CREATE_ROLE,
|
||||
AUTH_PROXY_USER_HEADER,
|
||||
API_ALLOWED_ORIGINS,
|
||||
)
|
||||
from ..db import connect, default_user_id, utcnow
|
||||
|
||||
@@ -171,14 +172,29 @@ def visible_profile_ids(user_id: int | None = None) -> set[int] | None:
|
||||
|
||||
|
||||
|
||||
def _origin_key(value: str) -> str:
|
||||
parsed = urlparse(str(value or "").strip())
|
||||
if not parsed.scheme or not parsed.netloc:
|
||||
return ""
|
||||
return f"{parsed.scheme.lower()}://{parsed.netloc.lower()}"
|
||||
|
||||
|
||||
def _request_origin() -> str:
|
||||
return _origin_key(f"{request.scheme}://{request.host}")
|
||||
|
||||
|
||||
def same_origin_request() -> bool:
|
||||
"""Return False only when an unsafe request clearly comes from another origin."""
|
||||
"""Return False only when an unsafe API request clearly comes from an untrusted origin."""
|
||||
origin = request.headers.get("Origin") or request.headers.get("Referer")
|
||||
if not origin:
|
||||
return True
|
||||
try:
|
||||
parsed = urlparse(origin)
|
||||
return parsed.scheme == request.scheme and parsed.netloc == request.host
|
||||
source_origin = _origin_key(origin)
|
||||
if not source_origin:
|
||||
return False
|
||||
if source_origin == _request_origin():
|
||||
return True
|
||||
return source_origin in set(API_ALLOWED_ORIGINS)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user