diff --git a/.gitignore b/.gitignore index 0a77252..a54f9ac 100644 --- a/.gitignore +++ b/.gitignore @@ -11,9 +11,13 @@ __pycache__/ .venv/ # app +/secrets.json /static/CACHE/ /media/* !/media/robots.txt !/media/sitemap.xml config.json *.log +/ops/* +/static/ops/* + diff --git a/auth_app/templates/auth/login.html b/auth_app/templates/auth/login.html index 5312da9..002b150 100644 --- a/auth_app/templates/auth/login.html +++ b/auth_app/templates/auth/login.html @@ -10,18 +10,6 @@ - -
@@ -33,8 +21,11 @@
{% csrf_token %} {{ form }} - -
-
- +
+
+ + + + + diff --git a/auth_app/views.py b/auth_app/views.py index a535f8e..64b242b 100644 --- a/auth_app/views.py +++ b/auth_app/views.py @@ -1,8 +1,25 @@ -from django.shortcuts import render, redirect -from django.contrib.auth import authenticate, login, logout +import requests +from django.conf import settings from django.contrib import messages +from django.contrib.auth import authenticate, login, logout +from django.shortcuts import redirect, render + from .forms import LoginForm +TURNSTILE_VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify" + + +def verify_turnstile(token, ip=None): + data = { + "secret": settings.CLOUDFLARE_TURNSTILE_SECRET_KEY, + "response": token, + } + if ip: + data["remoteip"] = ip + + r = requests.post(TURNSTILE_VERIFY_URL, data=data, timeout=5) + return r.json() + def user_login(request): if request.user.is_authenticated: @@ -12,6 +29,16 @@ def user_login(request): form = LoginForm(initial={"next": initial_next}) if request.method == "POST": + # cf turnstile + token = request.POST.get("cf-turnstile-response") + if not token: + messages.error(request, "Captcha missing.") + return redirect("login") + result = verify_turnstile(token, request.META.get("REMOTE_ADDR")) + if not result.get("success"): + messages.error(request, "Captcha failed. Try again.") + + # auth form = LoginForm(request.POST) if form.is_valid(): username = form.cleaned_data["username"] @@ -24,7 +51,11 @@ def user_login(request): else: messages.error(request, "Invalid username or password.") - return render(request, "auth/login.html", {"form": form}) + return render( + request, + "auth/login.html", + {"form": form, "TURNSTILE_SITE_KEY": settings.CLOUDFLARE_TURNSTILE_SITE_KEY}, + ) def user_logout(request): diff --git a/error_handlers/urls.py b/error_handlers/urls.py new file mode 100644 index 0000000..ec0ea75 --- /dev/null +++ b/error_handlers/urls.py @@ -0,0 +1,4 @@ +from django.urls import path +from . import views + +app_name = "error_handlers" diff --git a/error_handlers/views.py b/error_handlers/views.py index d44aef3..8b115b3 100644 --- a/error_handlers/views.py +++ b/error_handlers/views.py @@ -12,6 +12,17 @@ def error_400(request, exception): ) +def error_401(request, exception): + status_code = 401 + message = "UNAUTHORIZED" + return render( + request, + "error.html", + {"status_code": " ".join(str(status_code)), "message": message}, + status=status_code, + ) + + def error_403(request, exception): status_code = 403 message = "PERMISSION DENIED" diff --git a/nukeops/settings.py b/nukeops/settings.py index f960c86..8166903 100644 --- a/nukeops/settings.py +++ b/nukeops/settings.py @@ -180,3 +180,24 @@ "compressor.finders.CompressorFinder", ] COMPRESS_ROOT = STATIC_ROOT if conf["static_path"] else "static/" + +try: + from ops.overwrite import app_overwrite + + INSTALLED_APPS = app_overwrite(INSTALLED_APPS) +except ImportError: + pass + +# Secrets +BASE_DIR = Path(__file__).resolve().parent.parent + +SECRETS_FILE = BASE_DIR / "secrets.json" + +if SECRETS_FILE.exists(): + with open(SECRETS_FILE) as f: + secrets = json.load(f) +else: + secrets = {} + +CLOUDFLARE_TURNSTILE_SITE_KEY = secrets.get("TURNSTILE_SITE_KEY", "") +CLOUDFLARE_TURNSTILE_SECRET_KEY = secrets.get("TURNSTILE_SECRET_KEY", "") diff --git a/nukeops/urls.py b/nukeops/urls.py index 16ce29d..04ec1b7 100644 --- a/nukeops/urls.py +++ b/nukeops/urls.py @@ -14,13 +14,14 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ -from django.conf.urls import handler404, handler500 + +# from django.conf.urls import handler404, handler500 from django.contrib import admin from django.urls import include, path, re_path from django.views.static import serve from auth_app.views import user_login -from error_handlers.views import error_400, error_403, error_404, error_500 +from error_handlers.views import error_400, error_401, error_403, error_404, error_500 from .settings import MEDIA_ROOT from .views import media_access @@ -38,6 +39,14 @@ ] handler400 = error_400 +handler401 = error_401 handler403 = error_403 handler404 = error_404 handler500 = error_500 + +try: + from ops.overwrite import urls_overwrite + + urlpatterns = urls_overwrite(urlpatterns) +except ImportError: + pass diff --git a/requirements.txt b/requirements.txt index 4769ea8..8ac6da8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ wheel==0.42.0 channels==4.0.0 Django==5.0.2 +requests==2.32.5 mysqlclient==2.2.0 daphne==4.0.0 channels-redis==4.1.0