Added Super User Creator

Added creator
Corrected license in swagger
This commit is contained in:
Dispatcharr 2025-03-03 14:57:38 -06:00
parent 9feccc76cc
commit 814d6a6cda
6 changed files with 166 additions and 12 deletions

View file

@ -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'),

View file

@ -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):

View file

@ -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
]

View file

@ -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,),

View file

@ -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 <SuperuserForm onSuccess={() => setNeedsSuperuser(false)} />;
}
return (
<ThemeProvider theme={theme}>
<CssBaseline />
@ -99,12 +120,9 @@ const App = () => {
</ListItemButton>
</ListItem>
</List>
<Divider />
<Sidebar open />
</Drawer>
<Box
sx={{
display: 'flex',
@ -149,11 +167,7 @@ const App = () => {
</Box>
</Box>
</Router>
{/* Global Snackbar for system-wide messages */}
<Alert />
{/* Always-available floating video; remains visible across page changes */}
<FloatingVideo />
</ThemeProvider>
);

View file

@ -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 (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
backgroundColor: '#f5f5f5',
}}
>
<Paper elevation={3} sx={{ padding: 3, width: '100%', maxWidth: 400 }}>
<Typography variant="h5" align="center" gutterBottom>
Create your Super User Account
</Typography>
{error && (
<Typography variant="body2" color="error" sx={{ mb: 2 }}>
{error}
</Typography>
)}
<form onSubmit={handleSubmit}>
<Grid2 container spacing={2} justifyContent="center" direction="column">
<Grid2 item xs={12}>
<TextField
label="Username"
variant="standard"
fullWidth
name="username"
value={formData.username}
onChange={handleChange}
required
size="small"
/>
</Grid2>
<Grid2 item xs={12}>
<TextField
label="Password"
variant="standard"
type="password"
fullWidth
name="password"
value={formData.password}
onChange={handleChange}
required
size="small"
/>
</Grid2>
<Grid2 item xs={12}>
<TextField
label="Email (optional)"
variant="standard"
type="email"
fullWidth
name="email"
value={formData.email}
onChange={handleChange}
size="small"
/>
</Grid2>
<Grid2 item xs={12}>
<Button type="submit" variant="contained" color="primary" fullWidth>
Create Superuser
</Button>
</Grid2>
</Grid2>
</form>
</Paper>
</Box>
);
}
export default SuperuserForm;