implement authentication and authorization
This commit is contained in:
+151
@@ -0,0 +1,151 @@
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const passport = require('passport');
|
||||
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
|
||||
const { Strategy: LocalStrategy } = require('passport-local');
|
||||
|
||||
/**
|
||||
* Authentication and Authorization Module
|
||||
* Handles user authentication, session management, and role-based access control
|
||||
* Using Passport.js with JWT tokens
|
||||
*/
|
||||
|
||||
// JWT Secret - In production, use environment variable
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'nodecast-tv-secret-key-change-in-production';
|
||||
const JWT_EXPIRY = '24h';
|
||||
|
||||
/**
|
||||
* Hash password using bcrypt
|
||||
*/
|
||||
async function hashPassword(password) {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
return bcrypt.hash(password, salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify password against hash
|
||||
*/
|
||||
async function verifyPassword(password, hash) {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JWT token
|
||||
*/
|
||||
function generateToken(user) {
|
||||
return jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role
|
||||
},
|
||||
JWT_SECRET,
|
||||
{ expiresIn: JWT_EXPIRY }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify JWT token
|
||||
*/
|
||||
function verifyToken(token) {
|
||||
try {
|
||||
return jwt.verify(token, JWT_SECRET);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Passport Local Strategy for username/password authentication
|
||||
*/
|
||||
function configureLocalStrategy(getUserByUsername, verifyUserPassword) {
|
||||
passport.use(new LocalStrategy(
|
||||
async (username, password, done) => {
|
||||
try {
|
||||
const user = await getUserByUsername(username);
|
||||
|
||||
if (!user) {
|
||||
return done(null, false, { message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
const isValid = await verifyUserPassword(password, user.passwordHash);
|
||||
|
||||
if (!isValid) {
|
||||
return done(null, false, { message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
return done(null, user);
|
||||
} catch (err) {
|
||||
return done(err);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Passport JWT Strategy for token-based authentication
|
||||
*/
|
||||
function configureJwtStrategy(getUserById) {
|
||||
const options = {
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: JWT_SECRET
|
||||
};
|
||||
|
||||
passport.use(new JwtStrategy(options, async (payload, done) => {
|
||||
try {
|
||||
const user = await getUserById(payload.id);
|
||||
|
||||
if (!user) {
|
||||
return done(null, false);
|
||||
}
|
||||
|
||||
return done(null, {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role
|
||||
});
|
||||
} catch (err) {
|
||||
return done(err, false);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware: Require authentication using Passport JWT
|
||||
*/
|
||||
const requireAuth = passport.authenticate('jwt', { session: false });
|
||||
|
||||
/**
|
||||
* Middleware: Require admin role
|
||||
*/
|
||||
function requireAdmin(req, res, next) {
|
||||
if (!req.user || req.user.role !== 'admin') {
|
||||
return res.status(403).json({ error: 'Forbidden - Admin access required' });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware: Check for specific role
|
||||
*/
|
||||
function requireRole(role) {
|
||||
return (req, res, next) => {
|
||||
if (!req.user || req.user.role !== role) {
|
||||
return res.status(403).json({ error: `Forbidden - ${role} access required` });
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
passport,
|
||||
hashPassword,
|
||||
verifyPassword,
|
||||
generateToken,
|
||||
verifyToken,
|
||||
configureLocalStrategy,
|
||||
configureJwtStrategy,
|
||||
requireAuth,
|
||||
requireAdmin,
|
||||
requireRole
|
||||
};
|
||||
+104
-1
@@ -22,6 +22,7 @@ async function loadDb() {
|
||||
hiddenItems: data.hiddenItems || [],
|
||||
favorites: data.favorites || [],
|
||||
settings: data.settings || getDefaultSettings(),
|
||||
users: data.users || [],
|
||||
nextId: data.nextId || 1
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -32,6 +33,7 @@ async function loadDb() {
|
||||
hiddenItems: [],
|
||||
favorites: [],
|
||||
settings: getDefaultSettings(),
|
||||
users: [],
|
||||
nextId: 1
|
||||
};
|
||||
}
|
||||
@@ -45,6 +47,7 @@ async function loadDb() {
|
||||
hiddenItems: [],
|
||||
favorites: [],
|
||||
settings: getDefaultSettings(),
|
||||
users: [],
|
||||
nextId: 1
|
||||
};
|
||||
}
|
||||
@@ -311,4 +314,104 @@ const settings = {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { sources, hiddenItems, favorites, settings, getDefaultSettings };
|
||||
// User operations
|
||||
const users = {
|
||||
async getAll() {
|
||||
const db = await loadDb();
|
||||
return db.users || [];
|
||||
},
|
||||
|
||||
async getById(id) {
|
||||
const db = await loadDb();
|
||||
return db.users?.find(u => u.id === parseInt(id));
|
||||
},
|
||||
|
||||
async getByUsername(username) {
|
||||
const db = await loadDb();
|
||||
return db.users?.find(u => u.username === username);
|
||||
},
|
||||
|
||||
async create(userData) {
|
||||
const db = await loadDb();
|
||||
if (!db.users) {
|
||||
db.users = [];
|
||||
}
|
||||
|
||||
// Check if username already exists
|
||||
if (db.users.some(u => u.username === userData.username)) {
|
||||
throw new Error('Username already exists');
|
||||
}
|
||||
|
||||
const newUser = {
|
||||
id: db.nextId++,
|
||||
username: userData.username,
|
||||
passwordHash: userData.passwordHash,
|
||||
role: userData.role || 'viewer',
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
db.users.push(newUser);
|
||||
await saveDb(db);
|
||||
|
||||
// Return user without password hash
|
||||
const { passwordHash, ...userWithoutPassword } = newUser;
|
||||
return userWithoutPassword;
|
||||
},
|
||||
|
||||
async update(id, updates) {
|
||||
const db = await loadDb();
|
||||
const userIndex = db.users?.findIndex(u => u.id === parseInt(id));
|
||||
|
||||
if (userIndex === -1 || userIndex === undefined) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
// Check if username is being changed and if it already exists
|
||||
if (updates.username && updates.username !== db.users[userIndex].username) {
|
||||
if (db.users.some(u => u.username === updates.username)) {
|
||||
throw new Error('Username already exists');
|
||||
}
|
||||
}
|
||||
|
||||
db.users[userIndex] = {
|
||||
...db.users[userIndex],
|
||||
...updates,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
await saveDb(db);
|
||||
|
||||
// Return user without password hash
|
||||
const { passwordHash, ...userWithoutPassword } = db.users[userIndex];
|
||||
return userWithoutPassword;
|
||||
},
|
||||
|
||||
async delete(id) {
|
||||
const db = await loadDb();
|
||||
const userIndex = db.users?.findIndex(u => u.id === parseInt(id));
|
||||
|
||||
if (userIndex === -1 || userIndex === undefined) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
// Prevent deleting the last admin
|
||||
const user = db.users[userIndex];
|
||||
if (user.role === 'admin') {
|
||||
const adminCount = db.users.filter(u => u.role === 'admin').length;
|
||||
if (adminCount <= 1) {
|
||||
throw new Error('Cannot delete the last admin user');
|
||||
}
|
||||
}
|
||||
|
||||
db.users.splice(userIndex, 1);
|
||||
await saveDb(db);
|
||||
return true;
|
||||
},
|
||||
|
||||
async count() {
|
||||
const db = await loadDb();
|
||||
return db.users?.length || 0;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { loadDb, saveDb, sources, hiddenItems, favorites, settings, users, getDefaultSettings };
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const passport = require('passport');
|
||||
|
||||
// Initialize database
|
||||
require('./db');
|
||||
@@ -13,6 +14,10 @@ app.set('trust proxy', true);
|
||||
|
||||
// Middleware
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
|
||||
// Initialize Passport
|
||||
app.use(passport.initialize());
|
||||
|
||||
app.use(express.static(path.join(__dirname, '..', 'public')));
|
||||
|
||||
// FFMPEG Configuration (optional - for transcoding support)
|
||||
@@ -49,6 +54,7 @@ function findFFmpeg() {
|
||||
app.locals.ffmpegPath = findFFmpeg();
|
||||
|
||||
// API Routes
|
||||
app.use('/api/auth', require('./routes/auth'));
|
||||
app.use('/api/sources', require('./routes/sources'));
|
||||
app.use('/api/proxy', require('./routes/proxy'));
|
||||
app.use('/api/channels', require('./routes/channels'));
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db');
|
||||
const auth = require('../auth');
|
||||
|
||||
// Configure Passport strategies
|
||||
auth.configureLocalStrategy(
|
||||
async (username) => await db.users.getByUsername(username),
|
||||
async (password, hash) => await auth.verifyPassword(password, hash)
|
||||
);
|
||||
|
||||
auth.configureJwtStrategy(
|
||||
async (id) => await db.users.getById(id)
|
||||
);
|
||||
|
||||
/**
|
||||
* Check if initial setup is required
|
||||
* GET /api/auth/setup-required
|
||||
*/
|
||||
router.get('/setup-required', async (req, res) => {
|
||||
try {
|
||||
const userCount = await db.users.count();
|
||||
res.json({ setupRequired: userCount === 0 });
|
||||
} catch (err) {
|
||||
console.error('Error in /setup-required:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Initial setup - Create admin user
|
||||
* POST /api/auth/setup
|
||||
*/
|
||||
router.post('/setup', async (req, res) => {
|
||||
try {
|
||||
const userCount = await db.users.count();
|
||||
|
||||
// Check if setup already done
|
||||
if (userCount > 0) {
|
||||
return res.status(400).json({ error: 'Setup already completed' });
|
||||
}
|
||||
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ error: 'Username and password required' });
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({ error: 'Password must be at least 6 characters' });
|
||||
}
|
||||
|
||||
// Create admin user
|
||||
const passwordHash = await auth.hashPassword(password);
|
||||
const adminUser = await db.users.create({
|
||||
username,
|
||||
passwordHash,
|
||||
role: 'admin'
|
||||
});
|
||||
|
||||
// Generate token for immediate login
|
||||
const token = auth.generateToken(adminUser);
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Admin user created successfully',
|
||||
token,
|
||||
user: adminUser
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error in /setup:', err);
|
||||
res.status(500).json({ error: err.message || 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Login with Passport Local Strategy
|
||||
* POST /api/auth/login
|
||||
*/
|
||||
router.post('/login', (req, res, next) => {
|
||||
auth.passport.authenticate('local', { session: false }, (err, user, info) => {
|
||||
if (err) {
|
||||
console.error('Login error:', err);
|
||||
return res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: info?.message || 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
const token = auth.generateToken(user);
|
||||
|
||||
res.json({
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role
|
||||
}
|
||||
});
|
||||
})(req, res, next);
|
||||
});
|
||||
|
||||
/**
|
||||
* Logout (client-side handles token removal)
|
||||
* POST /api/auth/logout
|
||||
*/
|
||||
router.post('/logout', (req, res) => {
|
||||
// With JWT, logout is handled client-side by removing the token
|
||||
// This endpoint exists for consistency and future server-side token blacklisting
|
||||
res.json({ success: true, message: 'Logged out successfully' });
|
||||
});
|
||||
|
||||
/**
|
||||
* Get current user
|
||||
* GET /api/auth/me
|
||||
*/
|
||||
router.get('/me', auth.requireAuth, async (req, res) => {
|
||||
try {
|
||||
const user = await db.users.getById(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error in /me:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get all users (admin only)
|
||||
* GET /api/auth/users
|
||||
*/
|
||||
router.get('/users', auth.requireAuth, auth.requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const allUsers = await db.users.getAll();
|
||||
|
||||
// Remove password hashes
|
||||
const users = allUsers.map(u => {
|
||||
const { passwordHash, ...userWithoutPassword } = u;
|
||||
return userWithoutPassword;
|
||||
});
|
||||
|
||||
res.json(users);
|
||||
} catch (err) {
|
||||
console.error('Error fetching users:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a new user (admin only)
|
||||
* POST /api/auth/users
|
||||
*/
|
||||
router.post('/users', auth.requireAuth, auth.requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { username, password, role } = req.body;
|
||||
|
||||
if (!username || !password || !role) {
|
||||
return res.status(400).json({ error: 'Username, password, and role are required' });
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({ error: 'Password must be at least 6 characters' });
|
||||
}
|
||||
|
||||
if (!['admin', 'viewer'].includes(role)) {
|
||||
return res.status(400).json({ error: 'Role must be either "admin" or "viewer"' });
|
||||
}
|
||||
|
||||
const passwordHash = await auth.hashPassword(password);
|
||||
const newUser = await db.users.create({
|
||||
username,
|
||||
passwordHash,
|
||||
role
|
||||
});
|
||||
|
||||
res.status(201).json(newUser);
|
||||
} catch (err) {
|
||||
console.error('Error creating user:', err);
|
||||
res.status(500).json({ error: err.message || 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Update a user (admin only)
|
||||
* PUT /api/auth/users/:id
|
||||
*/
|
||||
router.put('/users/:id', auth.requireAuth, auth.requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { username, password, role } = req.body;
|
||||
|
||||
const updates = {};
|
||||
|
||||
if (username) {
|
||||
updates.username = username;
|
||||
}
|
||||
|
||||
if (password) {
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({ error: 'Password must be at least 6 characters' });
|
||||
}
|
||||
updates.passwordHash = await auth.hashPassword(password);
|
||||
}
|
||||
|
||||
if (role) {
|
||||
if (!['admin', 'viewer'].includes(role)) {
|
||||
return res.status(400).json({ error: 'Role must be either "admin" or "viewer"' });
|
||||
}
|
||||
|
||||
// Prevent removing admin role from the last admin
|
||||
const user = await db.users.getById(id);
|
||||
if (user && user.role === 'admin' && role !== 'admin') {
|
||||
const allUsers = await db.users.getAll();
|
||||
const adminCount = allUsers.filter(u => u.role === 'admin').length;
|
||||
if (adminCount <= 1) {
|
||||
return res.status(400).json({ error: 'Cannot remove admin role from the last admin user' });
|
||||
}
|
||||
}
|
||||
|
||||
updates.role = role;
|
||||
}
|
||||
|
||||
const updatedUser = await db.users.update(id, updates);
|
||||
res.json(updatedUser);
|
||||
} catch (err) {
|
||||
console.error('Error updating user:', err);
|
||||
res.status(500).json({ error: err.message || 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete a user (admin only)
|
||||
* DELETE /api/auth/users/:id
|
||||
*/
|
||||
router.delete('/users/:id', auth.requireAuth, auth.requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// Prevent deleting yourself
|
||||
if (parseInt(id) === req.user.id) {
|
||||
return res.status(400).json({ error: 'Cannot delete your own account' });
|
||||
}
|
||||
|
||||
await db.users.delete(id);
|
||||
res.json({ success: true, message: 'User deleted successfully' });
|
||||
} catch (err) {
|
||||
console.error('Error deleting user:', err);
|
||||
res.status(500).json({ error: err.message || 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,186 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db');
|
||||
const auth = require('../auth');
|
||||
|
||||
/**
|
||||
* Get all users (admin only)
|
||||
* GET /api/users
|
||||
*/
|
||||
router.get('/', auth.requireAuth, auth.requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const data = await db.loadDb();
|
||||
const users = (data.users || []).map(u => ({
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
role: u.role,
|
||||
createdAt: u.createdAt
|
||||
}));
|
||||
res.json(users);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Create user (admin only)
|
||||
* POST /api/users
|
||||
*/
|
||||
router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { username, password, role } = req.body;
|
||||
|
||||
if (!username || !password || !role) {
|
||||
return res.status(400).json({ error: 'Username, password, and role required' });
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({ error: 'Password must be at least 6 characters' });
|
||||
}
|
||||
|
||||
if (!['admin', 'viewer'].includes(role)) {
|
||||
return res.status(400).json({ error: 'Role must be admin or viewer' });
|
||||
}
|
||||
|
||||
const data = await db.loadDb();
|
||||
|
||||
// Check if username exists
|
||||
if (data.users?.some(u => u.username === username)) {
|
||||
return res.status(400).json({ error: 'Username already exists' });
|
||||
}
|
||||
|
||||
// Create user
|
||||
const passwordHash = await auth.hashPassword(password);
|
||||
const newUser = {
|
||||
id: data.nextUserId || (data.users?.length || 0) + 1,
|
||||
username,
|
||||
passwordHash,
|
||||
role,
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
data.users = data.users || [];
|
||||
data.users.push(newUser);
|
||||
data.nextUserId = newUser.id + 1;
|
||||
|
||||
await db.saveDb(data);
|
||||
|
||||
res.json({
|
||||
id: newUser.id,
|
||||
username: newUser.username,
|
||||
role: newUser.role,
|
||||
createdAt: newUser.createdAt
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Create user error:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Update user (admin only)
|
||||
* PUT /api/users/:id
|
||||
*/
|
||||
router.put('/:id', auth.requireAuth, auth.requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const userId = parseInt(req.params.id);
|
||||
const { username, password, role } = req.body;
|
||||
|
||||
const data = await db.loadDb();
|
||||
const userIndex = data.users?.findIndex(u => u.id === userId);
|
||||
|
||||
if (userIndex === -1 || userIndex === undefined) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
const user = data.users[userIndex];
|
||||
|
||||
// Update username if provided
|
||||
if (username && username !== user.username) {
|
||||
// Check if new username exists
|
||||
if (data.users.some(u => u.username === username && u.id !== userId)) {
|
||||
return res.status(400).json({ error: 'Username already exists' });
|
||||
}
|
||||
user.username = username;
|
||||
}
|
||||
|
||||
// Update password if provided
|
||||
if (password) {
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({ error: 'Password must be at least 6 characters' });
|
||||
}
|
||||
user.passwordHash = await auth.hashPassword(password);
|
||||
}
|
||||
|
||||
// Update role if provided
|
||||
if (role) {
|
||||
if (!['admin', 'viewer'].includes(role)) {
|
||||
return res.status(400).json({ error: 'Role must be admin or viewer' });
|
||||
}
|
||||
|
||||
// Prevent removing last admin
|
||||
if (user.role === 'admin' && role !== 'admin') {
|
||||
const adminCount = data.users.filter(u => u.role === 'admin').length;
|
||||
if (adminCount <= 1) {
|
||||
return res.status(400).json({ error: 'Cannot remove last admin user' });
|
||||
}
|
||||
}
|
||||
|
||||
user.role = role;
|
||||
}
|
||||
|
||||
await db.saveDb(data);
|
||||
|
||||
res.json({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
createdAt: user.createdAt
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Update user error:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete user (admin only)
|
||||
* DELETE /api/users/:id
|
||||
*/
|
||||
router.delete('/:id', auth.requireAuth, auth.requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const userId = parseInt(req.params.id);
|
||||
|
||||
const data = await db.loadDb();
|
||||
const userIndex = data.users?.findIndex(u => u.id === userId);
|
||||
|
||||
if (userIndex === -1 || userIndex === undefined) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
const user = data.users[userIndex];
|
||||
|
||||
// Prevent deleting yourself
|
||||
if (user.id === req.session.userId) {
|
||||
return res.status(400).json({ error: 'Cannot delete your own account' });
|
||||
}
|
||||
|
||||
// Prevent deleting last admin
|
||||
if (user.role === 'admin') {
|
||||
const adminCount = data.users.filter(u => u.role === 'admin').length;
|
||||
if (adminCount <= 1) {
|
||||
return res.status(400).json({ error: 'Cannot delete last admin user' });
|
||||
}
|
||||
}
|
||||
|
||||
data.users.splice(userIndex, 1);
|
||||
await db.saveDb(data);
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
console.error('Delete user error:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user