528 lines
16 KiB
JavaScript
528 lines
16 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const bcrypt = require('bcrypt');
|
|
const jwt = require('jsonwebtoken');
|
|
const { requirePermission, getUserPermissions } = require('./permissions');
|
|
|
|
// Get pool from global or create a new one if not available
|
|
let pool;
|
|
if (typeof global.pool !== 'undefined') {
|
|
pool = global.pool;
|
|
} else {
|
|
// If global pool is not available, create a new connection
|
|
const { Pool } = require('pg');
|
|
pool = new Pool({
|
|
host: process.env.DB_HOST,
|
|
user: process.env.DB_USER,
|
|
password: process.env.DB_PASSWORD,
|
|
database: process.env.DB_NAME,
|
|
port: process.env.DB_PORT,
|
|
});
|
|
console.log('Created new database pool in routes.js');
|
|
}
|
|
|
|
// Authentication middleware
|
|
const authenticate = async (req, res, next) => {
|
|
try {
|
|
const authHeader = req.headers.authorization;
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
const token = authHeader.split(' ')[1];
|
|
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
|
|
// Get user from database
|
|
const result = await pool.query(
|
|
'SELECT id, username, email, is_admin, rocket_chat_user_id FROM users WHERE id = $1',
|
|
[decoded.userId]
|
|
);
|
|
|
|
console.log('Database query result for user', decoded.userId, ':', result.rows[0]);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(401).json({ error: 'User not found' });
|
|
}
|
|
|
|
// Attach user to request
|
|
req.user = result.rows[0];
|
|
next();
|
|
} catch (error) {
|
|
console.error('Authentication error:', error);
|
|
res.status(401).json({ error: 'Invalid token' });
|
|
}
|
|
};
|
|
|
|
// Login route
|
|
router.post('/login', async (req, res) => {
|
|
try {
|
|
const { username, password } = req.body;
|
|
|
|
// Get user from database
|
|
const result = await pool.query(
|
|
'SELECT id, username, password, is_admin, is_active, rocket_chat_user_id FROM users WHERE username = $1',
|
|
[username]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(401).json({ error: 'Invalid username or password' });
|
|
}
|
|
|
|
const user = result.rows[0];
|
|
|
|
// Check if user is active
|
|
if (!user.is_active) {
|
|
return res.status(403).json({ error: 'Account is inactive' });
|
|
}
|
|
|
|
// Verify password
|
|
const validPassword = await bcrypt.compare(password, user.password);
|
|
if (!validPassword) {
|
|
return res.status(401).json({ error: 'Invalid username or password' });
|
|
}
|
|
|
|
// Update last login
|
|
await pool.query(
|
|
'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = $1',
|
|
[user.id]
|
|
);
|
|
|
|
// Generate JWT
|
|
const token = jwt.sign(
|
|
{ userId: user.id, username: user.username },
|
|
process.env.JWT_SECRET,
|
|
{ expiresIn: '8h' }
|
|
);
|
|
|
|
// Get user permissions
|
|
const permissions = await getUserPermissions(user.id);
|
|
|
|
res.json({
|
|
token,
|
|
user: {
|
|
id: user.id,
|
|
username: user.username,
|
|
is_admin: user.is_admin,
|
|
rocket_chat_user_id: user.rocket_chat_user_id,
|
|
permissions
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Get current user
|
|
router.get('/me', authenticate, async (req, res) => {
|
|
try {
|
|
// Get user permissions
|
|
const permissions = await getUserPermissions(req.user.id);
|
|
|
|
res.json({
|
|
id: req.user.id,
|
|
username: req.user.username,
|
|
email: req.user.email,
|
|
is_admin: req.user.is_admin,
|
|
rocket_chat_user_id: req.user.rocket_chat_user_id,
|
|
permissions,
|
|
// Debug info
|
|
_debug_raw_user: req.user,
|
|
_server_identifier: "INVENTORY_AUTH_SERVER_MODIFIED"
|
|
});
|
|
} catch (error) {
|
|
console.error('Error getting current user:', error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Get all users
|
|
router.get('/users', authenticate, requirePermission('view:users'), async (req, res) => {
|
|
try {
|
|
const result = await pool.query(`
|
|
SELECT id, username, email, is_admin, is_active, rocket_chat_user_id, created_at, last_login
|
|
FROM users
|
|
ORDER BY username
|
|
`);
|
|
|
|
res.json(result.rows);
|
|
} catch (error) {
|
|
console.error('Error getting users:', error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Get user with permissions
|
|
router.get('/users/:id', authenticate, requirePermission('view:users'), async (req, res) => {
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
// Get user details
|
|
const userResult = await pool.query(`
|
|
SELECT id, username, email, is_admin, is_active, rocket_chat_user_id, created_at, last_login
|
|
FROM users
|
|
WHERE id = $1
|
|
`, [userId]);
|
|
|
|
if (userResult.rows.length === 0) {
|
|
return res.status(404).json({ error: 'User not found' });
|
|
}
|
|
|
|
// Get user permissions
|
|
const permissionsResult = await pool.query(`
|
|
SELECT p.id, p.name, p.code, p.category, p.description
|
|
FROM permissions p
|
|
JOIN user_permissions up ON p.id = up.permission_id
|
|
WHERE up.user_id = $1
|
|
ORDER BY p.category, p.name
|
|
`, [userId]);
|
|
|
|
// Combine user and permissions
|
|
const user = {
|
|
...userResult.rows[0],
|
|
permissions: permissionsResult.rows
|
|
};
|
|
|
|
res.json(user);
|
|
} catch (error) {
|
|
console.error('Error getting user:', error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Create new user
|
|
router.post('/users', authenticate, requirePermission('create:users'), async (req, res) => {
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
const { username, email, password, is_admin, is_active, rocket_chat_user_id, permissions } = req.body;
|
|
|
|
console.log("Create user request:", {
|
|
username,
|
|
email,
|
|
is_admin,
|
|
is_active,
|
|
rocket_chat_user_id,
|
|
permissions: permissions || []
|
|
});
|
|
|
|
// Validate required fields
|
|
if (!username || !password) {
|
|
return res.status(400).json({ error: 'Username and password are required' });
|
|
}
|
|
|
|
// Check if username is taken
|
|
const existingUser = await client.query(
|
|
'SELECT id FROM users WHERE username = $1',
|
|
[username]
|
|
);
|
|
|
|
if (existingUser.rows.length > 0) {
|
|
return res.status(400).json({ error: 'Username already exists' });
|
|
}
|
|
|
|
// Start transaction
|
|
await client.query('BEGIN');
|
|
|
|
// Hash password
|
|
const saltRounds = 10;
|
|
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
|
|
|
// Insert new user
|
|
const userResult = await client.query(`
|
|
INSERT INTO users (username, email, password, is_admin, is_active, rocket_chat_user_id, created_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP)
|
|
RETURNING id
|
|
`, [username, email || null, hashedPassword, !!is_admin, is_active !== false, rocket_chat_user_id || null]);
|
|
|
|
const userId = userResult.rows[0].id;
|
|
|
|
// Assign permissions if provided and not admin
|
|
if (!is_admin && Array.isArray(permissions) && permissions.length > 0) {
|
|
console.log("Adding permissions for new user:", userId);
|
|
console.log("Permissions received:", permissions);
|
|
|
|
// Check permission format
|
|
const permissionIds = permissions.map(p => {
|
|
if (typeof p === 'object' && p.id) {
|
|
console.log("Permission is an object with ID:", p.id);
|
|
return parseInt(p.id, 10);
|
|
} else if (typeof p === 'number') {
|
|
console.log("Permission is a number:", p);
|
|
return p;
|
|
} else if (typeof p === 'string' && !isNaN(parseInt(p, 10))) {
|
|
console.log("Permission is a string that can be parsed as a number:", p);
|
|
return parseInt(p, 10);
|
|
} else {
|
|
console.log("Unknown permission format:", typeof p, p);
|
|
// If it's a permission code, we need to look up the ID
|
|
return null;
|
|
}
|
|
}).filter(id => id !== null);
|
|
|
|
console.log("Filtered permission IDs:", permissionIds);
|
|
|
|
if (permissionIds.length > 0) {
|
|
const permissionValues = permissionIds
|
|
.map(permId => `(${userId}, ${permId})`)
|
|
.join(',');
|
|
|
|
console.log("Inserting permission values:", permissionValues);
|
|
|
|
try {
|
|
await client.query(`
|
|
INSERT INTO user_permissions (user_id, permission_id)
|
|
VALUES ${permissionValues}
|
|
ON CONFLICT DO NOTHING
|
|
`);
|
|
console.log("Successfully inserted permissions for new user:", userId);
|
|
} catch (err) {
|
|
console.error("Error inserting permissions for new user:", err);
|
|
throw err;
|
|
}
|
|
} else {
|
|
console.log("No valid permission IDs found to insert for new user");
|
|
}
|
|
} else {
|
|
console.log("Not adding permissions: is_admin =", is_admin, "permissions array:", Array.isArray(permissions), "length:", permissions ? permissions.length : 0);
|
|
}
|
|
|
|
await client.query('COMMIT');
|
|
|
|
res.status(201).json({
|
|
id: userId,
|
|
message: 'User created successfully'
|
|
});
|
|
} catch (error) {
|
|
await client.query('ROLLBACK');
|
|
console.error('Error creating user:', error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
});
|
|
|
|
// Update user
|
|
router.put('/users/:id', authenticate, requirePermission('edit:users'), async (req, res) => {
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
const userId = req.params.id;
|
|
const { username, email, password, is_admin, is_active, rocket_chat_user_id, permissions } = req.body;
|
|
|
|
console.log("Update user request:", {
|
|
userId,
|
|
username,
|
|
email,
|
|
is_admin,
|
|
is_active,
|
|
rocket_chat_user_id,
|
|
permissions: permissions || []
|
|
});
|
|
|
|
// Check if user exists
|
|
const userExists = await client.query(
|
|
'SELECT id FROM users WHERE id = $1',
|
|
[userId]
|
|
);
|
|
|
|
if (userExists.rows.length === 0) {
|
|
return res.status(404).json({ error: 'User not found' });
|
|
}
|
|
|
|
// Start transaction
|
|
await client.query('BEGIN');
|
|
|
|
// Build update fields
|
|
const updateFields = [];
|
|
const updateValues = [userId]; // First parameter is the user ID
|
|
let paramIndex = 2;
|
|
|
|
if (username !== undefined) {
|
|
updateFields.push(`username = $${paramIndex++}`);
|
|
updateValues.push(username);
|
|
}
|
|
|
|
if (email !== undefined) {
|
|
updateFields.push(`email = $${paramIndex++}`);
|
|
updateValues.push(email || null);
|
|
}
|
|
|
|
if (is_admin !== undefined) {
|
|
updateFields.push(`is_admin = $${paramIndex++}`);
|
|
updateValues.push(!!is_admin);
|
|
}
|
|
|
|
if (is_active !== undefined) {
|
|
updateFields.push(`is_active = $${paramIndex++}`);
|
|
updateValues.push(!!is_active);
|
|
}
|
|
|
|
if (rocket_chat_user_id !== undefined) {
|
|
updateFields.push(`rocket_chat_user_id = $${paramIndex++}`);
|
|
updateValues.push(rocket_chat_user_id || null);
|
|
}
|
|
|
|
// Update password if provided
|
|
if (password) {
|
|
const saltRounds = 10;
|
|
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
|
updateFields.push(`password = $${paramIndex++}`);
|
|
updateValues.push(hashedPassword);
|
|
}
|
|
|
|
// Update user if there are fields to update
|
|
if (updateFields.length > 0) {
|
|
updateFields.push(`updated_at = CURRENT_TIMESTAMP`);
|
|
|
|
await client.query(`
|
|
UPDATE users
|
|
SET ${updateFields.join(', ')}
|
|
WHERE id = $1
|
|
`, updateValues);
|
|
}
|
|
|
|
// Update permissions if provided
|
|
if (Array.isArray(permissions)) {
|
|
console.log("Updating permissions for user:", userId);
|
|
console.log("Permissions received:", permissions);
|
|
|
|
// First remove existing permissions
|
|
await client.query(
|
|
'DELETE FROM user_permissions WHERE user_id = $1',
|
|
[userId]
|
|
);
|
|
console.log("Deleted existing permissions for user:", userId);
|
|
|
|
// Add new permissions if any and not admin
|
|
const newIsAdmin = is_admin !== undefined ? is_admin : (await client.query('SELECT is_admin FROM users WHERE id = $1', [userId])).rows[0].is_admin;
|
|
|
|
console.log("User is admin:", newIsAdmin);
|
|
|
|
if (!newIsAdmin && permissions.length > 0) {
|
|
console.log("Adding permissions:", permissions);
|
|
|
|
// Check permission format
|
|
const permissionIds = permissions.map(p => {
|
|
if (typeof p === 'object' && p.id) {
|
|
console.log("Permission is an object with ID:", p.id);
|
|
return parseInt(p.id, 10);
|
|
} else if (typeof p === 'number') {
|
|
console.log("Permission is a number:", p);
|
|
return p;
|
|
} else if (typeof p === 'string' && !isNaN(parseInt(p, 10))) {
|
|
console.log("Permission is a string that can be parsed as a number:", p);
|
|
return parseInt(p, 10);
|
|
} else {
|
|
console.log("Unknown permission format:", typeof p, p);
|
|
// If it's a permission code, we need to look up the ID
|
|
return null;
|
|
}
|
|
}).filter(id => id !== null);
|
|
|
|
console.log("Filtered permission IDs:", permissionIds);
|
|
|
|
if (permissionIds.length > 0) {
|
|
const permissionValues = permissionIds
|
|
.map(permId => `(${userId}, ${permId})`)
|
|
.join(',');
|
|
|
|
console.log("Inserting permission values:", permissionValues);
|
|
|
|
try {
|
|
await client.query(`
|
|
INSERT INTO user_permissions (user_id, permission_id)
|
|
VALUES ${permissionValues}
|
|
ON CONFLICT DO NOTHING
|
|
`);
|
|
console.log("Successfully inserted permissions for user:", userId);
|
|
} catch (err) {
|
|
console.error("Error inserting permissions:", err);
|
|
throw err;
|
|
}
|
|
} else {
|
|
console.log("No valid permission IDs found to insert");
|
|
}
|
|
}
|
|
}
|
|
|
|
await client.query('COMMIT');
|
|
|
|
res.json({ message: 'User updated successfully' });
|
|
} catch (error) {
|
|
await client.query('ROLLBACK');
|
|
console.error('Error updating user:', error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
});
|
|
|
|
// Delete user
|
|
router.delete('/users/:id', authenticate, requirePermission('delete:users'), async (req, res) => {
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
// Check that user is not deleting themselves
|
|
if (req.user.id === parseInt(userId, 10)) {
|
|
return res.status(400).json({ error: 'Cannot delete your own account' });
|
|
}
|
|
|
|
// Delete user (this will cascade to user_permissions due to FK constraints)
|
|
const result = await pool.query(
|
|
'DELETE FROM users WHERE id = $1 RETURNING id',
|
|
[userId]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(404).json({ error: 'User not found' });
|
|
}
|
|
|
|
res.json({ message: 'User deleted successfully' });
|
|
} catch (error) {
|
|
console.error('Error deleting user:', error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Get all permissions grouped by category
|
|
router.get('/permissions/categories', authenticate, requirePermission('view:users'), async (req, res) => {
|
|
try {
|
|
const result = await pool.query(`
|
|
SELECT category, json_agg(
|
|
json_build_object(
|
|
'id', id,
|
|
'name', name,
|
|
'code', code,
|
|
'description', description
|
|
) ORDER BY name
|
|
) as permissions
|
|
FROM permissions
|
|
GROUP BY category
|
|
ORDER BY category
|
|
`);
|
|
|
|
res.json(result.rows);
|
|
} catch (error) {
|
|
console.error('Error getting permissions:', error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Get all permissions
|
|
router.get('/permissions', authenticate, requirePermission('view:users'), async (req, res) => {
|
|
try {
|
|
const result = await pool.query(`
|
|
SELECT *
|
|
FROM permissions
|
|
ORDER BY category, name
|
|
`);
|
|
|
|
res.json(result.rows);
|
|
} catch (error) {
|
|
console.error('Error getting permissions:', error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|