mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-04 22:13:21 +00:00
Implemented first-time admin registration flow if no admin present
This commit is contained in:
@@ -10,6 +10,107 @@ const { v4: uuidv4 } = require('uuid');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
// Check if any admin users exist (for first-time setup)
|
||||||
|
router.get('/check-admin-users', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const adminCount = await prisma.users.count({
|
||||||
|
where: { role: 'admin' }
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
hasAdminUsers: adminCount > 0,
|
||||||
|
adminCount: adminCount
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking admin users:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Failed to check admin users',
|
||||||
|
hasAdminUsers: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create first admin user (for first-time setup)
|
||||||
|
router.post('/setup-admin', [
|
||||||
|
body('username').isLength({ min: 1 }).withMessage('Username is required'),
|
||||||
|
body('email').isEmail().withMessage('Valid email is required'),
|
||||||
|
body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters')
|
||||||
|
], async (req, res) => {
|
||||||
|
try {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'Validation failed',
|
||||||
|
details: errors.array()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { username, email, password } = req.body;
|
||||||
|
|
||||||
|
// Check if any admin users already exist
|
||||||
|
const adminCount = await prisma.users.count({
|
||||||
|
where: { role: 'admin' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (adminCount > 0) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'Admin users already exist. This endpoint is only for first-time setup.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if username or email already exists
|
||||||
|
const existingUser = await prisma.users.findFirst({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ username: username.trim() },
|
||||||
|
{ email: email.trim() }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'Username or email already exists'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password
|
||||||
|
const passwordHash = await bcrypt.hash(password, 12);
|
||||||
|
|
||||||
|
// Create admin user
|
||||||
|
const user = await prisma.users.create({
|
||||||
|
data: {
|
||||||
|
id: uuidv4(),
|
||||||
|
username: username.trim(),
|
||||||
|
email: email.trim(),
|
||||||
|
password_hash: passwordHash,
|
||||||
|
role: 'admin',
|
||||||
|
is_active: true,
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date()
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
email: true,
|
||||||
|
role: true,
|
||||||
|
created_at: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(201).json({
|
||||||
|
message: 'Admin user created successfully',
|
||||||
|
user: user
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating admin user:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Failed to create admin user'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Generate JWT token
|
// Generate JWT token
|
||||||
const generateToken = (userId) => {
|
const generateToken = (userId) => {
|
||||||
return jwt.sign(
|
return jwt.sign(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Routes, Route } from 'react-router-dom'
|
import { Routes, Route } from 'react-router-dom'
|
||||||
import { AuthProvider } from './contexts/AuthContext'
|
import { AuthProvider, useAuth } from './contexts/AuthContext'
|
||||||
import { ThemeProvider } from './contexts/ThemeContext'
|
import { ThemeProvider } from './contexts/ThemeContext'
|
||||||
import { UpdateNotificationProvider } from './contexts/UpdateNotificationContext'
|
import { UpdateNotificationProvider } from './contexts/UpdateNotificationContext'
|
||||||
import ProtectedRoute from './components/ProtectedRoute'
|
import ProtectedRoute from './components/ProtectedRoute'
|
||||||
@@ -18,21 +18,38 @@ import Options from './pages/Options'
|
|||||||
import Profile from './pages/Profile'
|
import Profile from './pages/Profile'
|
||||||
import HostDetail from './pages/HostDetail'
|
import HostDetail from './pages/HostDetail'
|
||||||
import PackageDetail from './pages/PackageDetail'
|
import PackageDetail from './pages/PackageDetail'
|
||||||
|
import FirstTimeAdminSetup from './components/FirstTimeAdminSetup'
|
||||||
|
|
||||||
|
function AppRoutes() {
|
||||||
|
const { needsFirstTimeSetup, checkingSetup, isAuthenticated } = useAuth()
|
||||||
|
|
||||||
|
// Show loading while checking if setup is needed
|
||||||
|
if (checkingSetup) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-secondary-50 dark:from-secondary-900 dark:to-secondary-800 flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto mb-4"></div>
|
||||||
|
<p className="text-secondary-600 dark:text-secondary-300">Checking system status...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show first-time setup if no admin users exist
|
||||||
|
if (needsFirstTimeSetup && !isAuthenticated) {
|
||||||
|
return <FirstTimeAdminSetup />
|
||||||
|
}
|
||||||
|
|
||||||
function App() {
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<Routes>
|
||||||
<AuthProvider>
|
<Route path="/login" element={<Login />} />
|
||||||
<UpdateNotificationProvider>
|
<Route path="/" element={
|
||||||
<Routes>
|
<ProtectedRoute requirePermission="can_view_dashboard">
|
||||||
<Route path="/login" element={<Login />} />
|
<Layout>
|
||||||
<Route path="/" element={
|
<Dashboard />
|
||||||
<ProtectedRoute requirePermission="can_view_dashboard">
|
</Layout>
|
||||||
<Layout>
|
</ProtectedRoute>
|
||||||
<Dashboard />
|
} />
|
||||||
</Layout>
|
|
||||||
</ProtectedRoute>
|
|
||||||
} />
|
|
||||||
<Route path="/hosts" element={
|
<Route path="/hosts" element={
|
||||||
<ProtectedRoute requirePermission="can_view_hosts">
|
<ProtectedRoute requirePermission="can_view_hosts">
|
||||||
<Layout>
|
<Layout>
|
||||||
@@ -110,7 +127,16 @@ function App() {
|
|||||||
</Layout>
|
</Layout>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
} />
|
} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<ThemeProvider>
|
||||||
|
<AuthProvider>
|
||||||
|
<UpdateNotificationProvider>
|
||||||
|
<AppRoutes />
|
||||||
</UpdateNotificationProvider>
|
</UpdateNotificationProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
246
frontend/src/components/FirstTimeAdminSetup.jsx
Normal file
246
frontend/src/components/FirstTimeAdminSetup.jsx
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import { useAuth } from '../contexts/AuthContext'
|
||||||
|
import { UserPlus, Shield, CheckCircle, AlertCircle } from 'lucide-react'
|
||||||
|
|
||||||
|
const FirstTimeAdminSetup = () => {
|
||||||
|
const { login } = useAuth()
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
})
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
const [success, setSuccess] = useState(false)
|
||||||
|
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
const { name, value } = e.target
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
[name]: value
|
||||||
|
}))
|
||||||
|
// Clear error when user starts typing
|
||||||
|
if (error) setError('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
if (!formData.username.trim()) {
|
||||||
|
setError('Username is required')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!formData.email.trim()) {
|
||||||
|
setError('Email is required')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!formData.email.includes('@')) {
|
||||||
|
setError('Please enter a valid email address')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (formData.password.length < 6) {
|
||||||
|
setError('Password must be at least 6 characters')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (formData.password !== formData.confirmPassword) {
|
||||||
|
setError('Passwords do not match')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (!validateForm()) return
|
||||||
|
|
||||||
|
setIsLoading(true)
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/v1/auth/setup-admin', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: formData.username.trim(),
|
||||||
|
email: formData.email.trim(),
|
||||||
|
password: formData.password
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setSuccess(true)
|
||||||
|
// Auto-login the user after successful setup
|
||||||
|
setTimeout(() => {
|
||||||
|
login(formData.username.trim(), formData.password)
|
||||||
|
}, 2000)
|
||||||
|
} else {
|
||||||
|
setError(data.error || 'Failed to create admin user')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Setup error:', error)
|
||||||
|
setError('Network error. Please try again.')
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-secondary-50 dark:from-secondary-900 dark:to-secondary-800 flex items-center justify-center p-4">
|
||||||
|
<div className="max-w-md w-full">
|
||||||
|
<div className="card p-8 text-center">
|
||||||
|
<div className="flex justify-center mb-6">
|
||||||
|
<div className="bg-green-100 dark:bg-green-900 p-4 rounded-full">
|
||||||
|
<CheckCircle className="h-12 w-12 text-green-600 dark:text-green-400" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold text-secondary-900 dark:text-white mb-4">
|
||||||
|
Admin Account Created!
|
||||||
|
</h1>
|
||||||
|
<p className="text-secondary-600 dark:text-secondary-300 mb-6">
|
||||||
|
Your admin account has been successfully created. You will be automatically logged in shortly.
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary-600"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-secondary-50 dark:from-secondary-900 dark:to-secondary-800 flex items-center justify-center p-4">
|
||||||
|
<div className="max-w-md w-full">
|
||||||
|
<div className="card p-8">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="flex justify-center mb-4">
|
||||||
|
<div className="bg-primary-100 dark:bg-primary-900 p-4 rounded-full">
|
||||||
|
<Shield className="h-12 w-12 text-primary-600 dark:text-primary-400" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold text-secondary-900 dark:text-white mb-2">
|
||||||
|
Welcome to PatchMon
|
||||||
|
</h1>
|
||||||
|
<p className="text-secondary-600 dark:text-secondary-300">
|
||||||
|
Let's set up your admin account to get started
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="mb-6 p-4 bg-danger-50 dark:bg-danger-900 border border-danger-200 dark:border-danger-700 rounded-lg">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<AlertCircle className="h-5 w-5 text-danger-600 dark:text-danger-400 mr-2" />
|
||||||
|
<span className="text-danger-700 dark:text-danger-300 text-sm">{error}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="username" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
value={formData.username}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="input w-full"
|
||||||
|
placeholder="Enter your username"
|
||||||
|
required
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
|
||||||
|
Email Address
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="input w-full"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
required
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="password" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
value={formData.password}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="input w-full"
|
||||||
|
placeholder="Enter your password (min 6 characters)"
|
||||||
|
required
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="confirmPassword" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
|
||||||
|
Confirm Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="confirmPassword"
|
||||||
|
name="confirmPassword"
|
||||||
|
value={formData.confirmPassword}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="input w-full"
|
||||||
|
placeholder="Confirm your password"
|
||||||
|
required
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading}
|
||||||
|
className="btn-primary w-full flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<>
|
||||||
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
||||||
|
Creating Admin Account...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<UserPlus className="h-4 w-4" />
|
||||||
|
Create Admin Account
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="mt-8 p-4 bg-blue-50 dark:bg-blue-900 border border-blue-200 dark:border-blue-700 rounded-lg">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<Shield className="h-5 w-5 text-blue-600 dark:text-blue-400 mr-2 mt-0.5 flex-shrink-0" />
|
||||||
|
<div className="text-sm text-blue-700 dark:text-blue-300">
|
||||||
|
<p className="font-medium mb-1">Admin Privileges</p>
|
||||||
|
<p>This account will have full administrative access to manage users, hosts, packages, and system settings.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FirstTimeAdminSetup
|
||||||
@@ -16,6 +16,8 @@ export const AuthProvider = ({ children }) => {
|
|||||||
const [permissions, setPermissions] = useState(null)
|
const [permissions, setPermissions] = useState(null)
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [permissionsLoading, setPermissionsLoading] = useState(false)
|
const [permissionsLoading, setPermissionsLoading] = useState(false)
|
||||||
|
const [needsFirstTimeSetup, setNeedsFirstTimeSetup] = useState(false)
|
||||||
|
const [checkingSetup, setCheckingSetup] = useState(true)
|
||||||
|
|
||||||
// Initialize auth state from localStorage
|
// Initialize auth state from localStorage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -217,11 +219,48 @@ export const AuthProvider = ({ children }) => {
|
|||||||
const canExportData = () => hasPermission('can_export_data')
|
const canExportData = () => hasPermission('can_export_data')
|
||||||
const canManageSettings = () => hasPermission('can_manage_settings')
|
const canManageSettings = () => hasPermission('can_manage_settings')
|
||||||
|
|
||||||
|
// Check if any admin users exist (for first-time setup)
|
||||||
|
const checkAdminUsersExist = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/v1/auth/check-admin-users', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json()
|
||||||
|
setNeedsFirstTimeSetup(!data.hasAdminUsers)
|
||||||
|
} else {
|
||||||
|
// If endpoint doesn't exist or fails, assume setup is needed
|
||||||
|
setNeedsFirstTimeSetup(true)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking admin users:', error)
|
||||||
|
// If there's an error, assume setup is needed
|
||||||
|
setNeedsFirstTimeSetup(true)
|
||||||
|
} finally {
|
||||||
|
setCheckingSetup(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for admin users on initial load
|
||||||
|
useEffect(() => {
|
||||||
|
if (!token && !user) {
|
||||||
|
checkAdminUsersExist()
|
||||||
|
} else {
|
||||||
|
setCheckingSetup(false)
|
||||||
|
}
|
||||||
|
}, [token, user])
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
user,
|
user,
|
||||||
token,
|
token,
|
||||||
permissions,
|
permissions,
|
||||||
isLoading: isLoading || permissionsLoading,
|
isLoading: isLoading || permissionsLoading || checkingSetup,
|
||||||
|
needsFirstTimeSetup,
|
||||||
|
checkingSetup,
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
updateProfile,
|
updateProfile,
|
||||||
|
|||||||
112
setup-admin-user-fixed.js
Normal file
112
setup-admin-user-fixed.js
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
const bcrypt = require('bcryptjs');
|
||||||
|
const readline = require('readline');
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
|
||||||
|
|
||||||
|
async function setupAdminUser() {
|
||||||
|
try {
|
||||||
|
console.log('🔐 Setting up PatchMon Admin User');
|
||||||
|
console.log('=====================================\n');
|
||||||
|
|
||||||
|
// Check if any users exist
|
||||||
|
const existingUsers = await prisma.users.count();
|
||||||
|
if (existingUsers > 0) {
|
||||||
|
console.log('⚠️ Users already exist in the database.');
|
||||||
|
const overwrite = await question('Do you want to create another admin user? (y/N): ');
|
||||||
|
if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {
|
||||||
|
console.log('❌ Setup cancelled.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user input
|
||||||
|
const username = await question('Enter admin username: ');
|
||||||
|
if (!username.trim()) {
|
||||||
|
console.log('❌ Username is required.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const email = await question('Enter admin email: ');
|
||||||
|
if (!email.trim()) {
|
||||||
|
console.log('❌ Email is required.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const password = await question('Enter admin password (min 6 characters): ');
|
||||||
|
if (password.length < 6) {
|
||||||
|
console.log('❌ Password must be at least 6 characters.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if username or email already exists
|
||||||
|
const existingUser = await prisma.users.findFirst({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ username: username.trim() },
|
||||||
|
{ email: email.trim() }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
console.log('❌ Username or email already exists.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password
|
||||||
|
console.log('\n🔄 Creating admin user...');
|
||||||
|
const passwordHash = await bcrypt.hash(password, 12);
|
||||||
|
|
||||||
|
// Create admin user
|
||||||
|
const user = await prisma.users.create({
|
||||||
|
data: {
|
||||||
|
id: require('crypto').randomUUID(),
|
||||||
|
username: username.trim(),
|
||||||
|
email: email.trim(),
|
||||||
|
password_hash: passwordHash,
|
||||||
|
role: 'admin',
|
||||||
|
is_active: true,
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date()
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
email: true,
|
||||||
|
role: true,
|
||||||
|
created_at: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ Admin user created successfully!');
|
||||||
|
console.log('\n📋 User Details:');
|
||||||
|
console.log(` Username: ${user.username}`);
|
||||||
|
console.log(` Email: ${user.email}`);
|
||||||
|
console.log(` Role: ${user.role}`);
|
||||||
|
console.log(` Created: ${user.created_at.toISOString()}`);
|
||||||
|
|
||||||
|
console.log('\n🎉 Setup complete!');
|
||||||
|
console.log('\nNext steps:');
|
||||||
|
console.log('1. The backend server is already running as a systemd service');
|
||||||
|
console.log('2. The frontend is already built and served by Nginx');
|
||||||
|
console.log('3. Visit https://' + process.env.FQDN + ' and login with your credentials');
|
||||||
|
console.log('4. Use the management script: ./manage.sh {status|restart|logs|update|backup|credentials|reset-admin}');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error setting up admin user:', error);
|
||||||
|
} finally {
|
||||||
|
rl.close();
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the setup
|
||||||
|
setupAdminUser();
|
||||||
@@ -17,7 +17,7 @@ async function setupAdminUser() {
|
|||||||
console.log('=====================================\n');
|
console.log('=====================================\n');
|
||||||
|
|
||||||
// Check if any users exist
|
// Check if any users exist
|
||||||
const existingUsers = await prisma.user.count();
|
const existingUsers = await prisma.users.count();
|
||||||
if (existingUsers > 0) {
|
if (existingUsers > 0) {
|
||||||
console.log('⚠️ Users already exist in the database.');
|
console.log('⚠️ Users already exist in the database.');
|
||||||
const overwrite = await question('Do you want to create another admin user? (y/N): ');
|
const overwrite = await question('Do you want to create another admin user? (y/N): ');
|
||||||
@@ -47,7 +47,7 @@ async function setupAdminUser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if username or email already exists
|
// Check if username or email already exists
|
||||||
const existingUser = await prisma.user.findFirst({
|
const existingUser = await prisma.users.findFirst({
|
||||||
where: {
|
where: {
|
||||||
OR: [
|
OR: [
|
||||||
{ username: username.trim() },
|
{ username: username.trim() },
|
||||||
@@ -66,19 +66,23 @@ async function setupAdminUser() {
|
|||||||
const passwordHash = await bcrypt.hash(password, 12);
|
const passwordHash = await bcrypt.hash(password, 12);
|
||||||
|
|
||||||
// Create admin user
|
// Create admin user
|
||||||
const user = await prisma.user.create({
|
const user = await prisma.users.create({
|
||||||
data: {
|
data: {
|
||||||
|
id: require('crypto').randomUUID(),
|
||||||
username: username.trim(),
|
username: username.trim(),
|
||||||
email: email.trim(),
|
email: email.trim(),
|
||||||
passwordHash: passwordHash,
|
password_hash: passwordHash,
|
||||||
role: 'admin'
|
role: 'admin',
|
||||||
|
is_active: true,
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date()
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
email: true,
|
email: true,
|
||||||
role: true,
|
role: true,
|
||||||
createdAt: true
|
created_at: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -87,7 +91,7 @@ async function setupAdminUser() {
|
|||||||
console.log(` Username: ${user.username}`);
|
console.log(` Username: ${user.username}`);
|
||||||
console.log(` Email: ${user.email}`);
|
console.log(` Email: ${user.email}`);
|
||||||
console.log(` Role: ${user.role}`);
|
console.log(` Role: ${user.role}`);
|
||||||
console.log(` Created: ${user.createdAt.toISOString()}`);
|
console.log(` Created: ${user.created_at.toISOString()}`);
|
||||||
|
|
||||||
console.log('\n🎉 Setup complete!');
|
console.log('\n🎉 Setup complete!');
|
||||||
console.log('\nNext steps:');
|
console.log('\nNext steps:');
|
||||||
|
|||||||
Reference in New Issue
Block a user