mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 02:35:14 +00:00
Added Super User Creator
Added creator Corrected license in swagger
This commit is contained in:
parent
9feccc76cc
commit
814d6a6cda
6 changed files with 166 additions and 12 deletions
|
|
@ -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'),
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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,),
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
111
frontend/src/components/forms/SuperuserForm.js
Normal file
111
frontend/src/components/forms/SuperuserForm.js
Normal 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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue