From 814d6a6cda930a3e17ba295e5d78dbc2405fc7fa Mon Sep 17 00:00:00 2001 From: Dispatcharr Date: Mon, 3 Mar 2025 14:57:38 -0600 Subject: [PATCH] Added Super User Creator Added creator Corrected license in swagger --- apps/accounts/api_urls.py | 5 +- apps/accounts/api_views.py | 25 ++++ apps/accounts/urls.py | 1 + dispatcharr/urls.py | 2 +- frontend/src/App.js | 34 ++++-- .../src/components/forms/SuperuserForm.js | 111 ++++++++++++++++++ 6 files changed, 166 insertions(+), 12 deletions(-) create mode 100644 frontend/src/components/forms/SuperuserForm.js diff --git a/apps/accounts/api_urls.py b/apps/accounts/api_urls.py index 6f2d2742..e1518105 100644 --- a/apps/accounts/api_urls.py +++ b/apps/accounts/api_urls.py @@ -2,7 +2,7 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter from .api_views import ( AuthViewSet, UserViewSet, GroupViewSet, - list_permissions + list_permissions, initialize_superuser ) from rest_framework_simplejwt import views as jwt_views @@ -28,6 +28,9 @@ urlpatterns = [ path('auth/login/', auth_view, name='user-login'), path('auth/logout/', logout_view, name='user-logout'), + # Superuser API + path('initialize-superuser/', initialize_superuser, name='initialize_superuser'), + # Permissions API path('permissions/', list_permissions, name='list-permissions'), diff --git a/apps/accounts/api_views.py b/apps/accounts/api_views.py index 15afe489..27d844df 100644 --- a/apps/accounts/api_views.py +++ b/apps/accounts/api_views.py @@ -1,14 +1,39 @@ from django.contrib.auth import authenticate, login, logout from django.contrib.auth.models import Group, Permission +from django.http import JsonResponse, HttpResponse +from django.views.decorators.csrf import csrf_exempt from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.response import Response from rest_framework import viewsets from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi +import json + from .models import User from .serializers import UserSerializer, GroupSerializer, PermissionSerializer +@csrf_exempt # In production, consider CSRF protection strategies or ensure this endpoint is only accessible when no superuser exists. +def initialize_superuser(request): + # If a superuser already exists, always indicate that + if User.objects.filter(is_superuser=True).exists(): + return JsonResponse({"superuser_exists": True}) + + if request.method == "POST": + try: + data = json.loads(request.body) + username = data.get("username") + password = data.get("password") + email = data.get("email", "") + if not username or not password: + return JsonResponse({"error": "Username and password are required."}, status=400) + # Create the superuser + User.objects.create_superuser(username=username, password=password, email=email) + return JsonResponse({"superuser_exists": True}) + except Exception as e: + return JsonResponse({"error": str(e)}, status=500) + # For GET requests, indicate no superuser exists + return JsonResponse({"superuser_exists": False}) # 🔹 1) Authentication APIs class AuthViewSet(viewsets.ViewSet): diff --git a/apps/accounts/urls.py b/apps/accounts/urls.py index c276f6c4..bcdad78b 100644 --- a/apps/accounts/urls.py +++ b/apps/accounts/urls.py @@ -8,4 +8,5 @@ urlpatterns = [ path('login/', auth_views.LoginView.as_view(template_name='login.html'), name='login'), # Logout view using Django's built-in authentication path('logout/', auth_views.LogoutView.as_view(next_page='accounts:login'), name='logout'), + # Onetime use superuser account creation ] diff --git a/dispatcharr/urls.py b/dispatcharr/urls.py index 57d85cfb..4aa72aa6 100644 --- a/dispatcharr/urls.py +++ b/dispatcharr/urls.py @@ -15,7 +15,7 @@ schema_view = get_schema_view( description="API documentation for Dispatcharr", terms_of_service="https://www.google.com/policies/terms/", contact=openapi.Contact(email="contact@dispatcharr.local"), - license=openapi.License(name="Unlicense"), + license=openapi.License(name="Creative Commons by-nc-sa"), ), public=True, permission_classes=(permissions.AllowAny,), diff --git a/frontend/src/App.js b/frontend/src/App.js index 257e209a..9dc0fedd 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,5 +1,6 @@ // frontend/src/App.js import React, { useEffect, useState } from 'react'; +import axios from 'axios'; import { BrowserRouter as Router, Route, @@ -29,17 +30,16 @@ import StreamProfiles from './pages/StreamProfiles'; import useAuthStore from './store/auth'; import logo from './images/logo.png'; import Alert from './components/Alert'; - -// NEW: import the floating PiP component import FloatingVideo from './components/FloatingVideo'; +import SuperuserForm from './components/forms/SuperuserForm'; const drawerWidth = 240; const miniDrawerWidth = 60; - const defaultRoute = '/channels'; const App = () => { const [open, setOpen] = useState(true); + const [needsSuperuser, setNeedsSuperuser] = useState(false); const { isAuthenticated, setIsAuthenticated, @@ -52,6 +52,22 @@ const App = () => { setOpen(!open); }; + // Check if a superuser exists on first load. + useEffect(() => { + async function checkSuperuser() { + try { + const res = await axios.get('/api/accounts/initialize-superuser/'); + if (!res.data.superuser_exists) { + setNeedsSuperuser(true); + } + } catch (error) { + console.error('Error checking superuser status:', error); + } + } + checkSuperuser(); + }, []); + + // Authentication check. useEffect(() => { const checkAuth = async () => { const loggedIn = await initializeAuth(); @@ -65,6 +81,11 @@ const App = () => { checkAuth(); }, [initializeAuth, initData, setIsAuthenticated, logout]); + // If no superuser exists, show the initialization form. + if (needsSuperuser) { + return setNeedsSuperuser(false)} />; + } + return ( @@ -99,12 +120,9 @@ const App = () => { - - - { - - {/* Global Snackbar for system-wide messages */} - - {/* Always-available floating video; remains visible across page changes */} ); diff --git a/frontend/src/components/forms/SuperuserForm.js b/frontend/src/components/forms/SuperuserForm.js new file mode 100644 index 00000000..9554bed0 --- /dev/null +++ b/frontend/src/components/forms/SuperuserForm.js @@ -0,0 +1,111 @@ +// frontend/src/components/forms/SuperuserForm.js +import React, { useState } from 'react'; +import axios from 'axios'; +import { Box, Paper, Typography, Grid as Grid2, TextField, Button } from '@mui/material'; + +function SuperuserForm({ onSuccess }) { + const [formData, setFormData] = useState({ + username: '', + password: '', + email: '', + }); + const [error, setError] = useState(''); + + const handleChange = (e) => { + setFormData((prev) => ({ + ...prev, + [e.target.name]: e.target.value, + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + const res = await axios.post('/api/accounts/initialize-superuser/', { + username: formData.username, + password: formData.password, + email: formData.email, + }); + if (res.data.superuser_exists) { + onSuccess(); + } + } catch (err) { + let msg = 'Failed to create superuser.'; + if (err.response && err.response.data && err.response.data.error) { + msg += ` ${err.response.data.error}`; + } + setError(msg); + } + }; + + return ( + + + + Create your Super User Account + + {error && ( + + {error} + + )} +
+ + + + + + + + + + + + + + +
+
+
+ ); +} + +export default SuperuserForm;