fix:connection lost
This commit is contained in:
+164
-94
@@ -3,7 +3,6 @@ import { point, polygon, booleanPointInPolygon, pointToLineDistance } from '@tur
|
||||
import bcrypt from 'bcrypt';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { getConnection } from './pool.js';
|
||||
const db = await getConnection();
|
||||
|
||||
async function validateDeviceForUser(userId, deviceUuid, db) {
|
||||
const [userRows] = await db.execute('SELECT device_uuid FROM workers WHERE id = ?', [userId]);
|
||||
@@ -32,51 +31,58 @@ export default function() {
|
||||
const AUTO_REGISTER_NEW_DEVICES = true;
|
||||
|
||||
router.post('/auth/login', async (req, res) => {
|
||||
const { username, password, deviceUuid } = req.body;
|
||||
const [rows] = await db.execute('SELECT id, role, password_hash, status FROM workers WHERE username = ?', [username]);
|
||||
if (rows.length === 0) {
|
||||
return res.status(401).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
const user = rows[0];
|
||||
const db = await getConnection();
|
||||
try {
|
||||
const { username, password, deviceUuid } = req.body;
|
||||
const [rows] = await db.execute('SELECT id, role, password_hash, status FROM workers WHERE username = ?', [username]);
|
||||
if (rows.length === 0) {
|
||||
return res.status(401).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
const user = rows[0];
|
||||
|
||||
// Check if the user's status is 'active'
|
||||
if (user.status !== 'active') {
|
||||
return res.status(401).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
// Check if the user's status is 'active'
|
||||
if (user.status !== 'active') {
|
||||
return res.status(401).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
const passwordMatch = await bcrypt.compare(password, user.password_hash);
|
||||
if (!passwordMatch) {
|
||||
return res.status(401).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
const passwordMatch = await bcrypt.compare(password, user.password_hash);
|
||||
if (!passwordMatch) {
|
||||
return res.status(401).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Device UUID handling - controlled by configuration flags above
|
||||
if (DEVICE_UUID_ENABLED && user.role === 'worker') {
|
||||
const [deviceRows] = await db.execute('SELECT device_uuid FROM workers WHERE id = ?', [user.id]);
|
||||
const existingDeviceUuid = deviceRows[0].device_uuid;
|
||||
// Device UUID handling - controlled by configuration flags above
|
||||
if (DEVICE_UUID_ENABLED && user.role === 'worker') {
|
||||
const [deviceRows] = await db.execute('SELECT device_uuid FROM workers WHERE id = ?', [user.id]);
|
||||
const existingDeviceUuid = deviceRows[0].device_uuid;
|
||||
|
||||
if (existingDeviceUuid) {
|
||||
if (deviceUuid && deviceUuid !== existingDeviceUuid) {
|
||||
return res.status(403).json({ message: 'deviceMismatch' });
|
||||
} else if (!deviceUuid) {
|
||||
return res.status(403).json({ message: 'useMobileApp' });
|
||||
}
|
||||
} else {
|
||||
// User has no registered device
|
||||
if (deviceUuid && AUTO_REGISTER_NEW_DEVICES) {
|
||||
const deviceResult = await validateDeviceForUser(user.id, deviceUuid, db);
|
||||
if (!deviceResult.valid) {
|
||||
return res.status(500).json({ message: 'deviceRegistrationFailed' });
|
||||
if (existingDeviceUuid) {
|
||||
if (deviceUuid && deviceUuid !== existingDeviceUuid) {
|
||||
return res.status(403).json({ message: 'deviceMismatch' });
|
||||
} else if (!deviceUuid) {
|
||||
return res.status(403).json({ message: 'useMobileApp' });
|
||||
}
|
||||
} else {
|
||||
// User has no registered device
|
||||
if (deviceUuid && AUTO_REGISTER_NEW_DEVICES) {
|
||||
const deviceResult = await validateDeviceForUser(user.id, deviceUuid, db);
|
||||
if (!deviceResult.valid) {
|
||||
return res.status(500).json({ message: 'deviceRegistrationFailed' });
|
||||
}
|
||||
} else if (!deviceUuid && REQUIRE_DEVICE_FOR_WORKERS) {
|
||||
return res.status(403).json({ message: 'deviceRequired' });
|
||||
}
|
||||
// console.log(`Device UUID registered for worker ${user.id}: ${deviceUuid}`);
|
||||
} else if (!deviceUuid && REQUIRE_DEVICE_FOR_WORKERS) {
|
||||
return res.status(403).json({ message: 'deviceRequired' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Managers can always login, workers without device_uuid can login
|
||||
const token = jwt.sign({ userId: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '1h' });
|
||||
res.json({ token });
|
||||
// Managers can always login, workers without device_uuid can login
|
||||
const token = jwt.sign({ userId: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '1h' });
|
||||
res.json({ token });
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ message: 'Server error during login' });
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
});
|
||||
|
||||
const authenticateJWT = (req, res, next) => {
|
||||
@@ -87,7 +93,7 @@ export default function() {
|
||||
if (err) {
|
||||
return res.status(403).json({ message: 'Invalid or expired token' });
|
||||
}
|
||||
req.user = { ...user, id: user.userId }; // Correctly map userId to id
|
||||
req.user = { ...user, id: user.userId };
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
@@ -98,15 +104,15 @@ export default function() {
|
||||
router.use(authenticateJWT);
|
||||
|
||||
router.post('/clock', async (req, res) => {
|
||||
// NEW: borrow a connection so we can set session time_zone
|
||||
const db = await getConnection();
|
||||
try {
|
||||
const { userId, eventType, qrCodeValue, latitude, longitude } = req.body;
|
||||
|
||||
// 1) Kill Switch — now evaluated in the session's local day
|
||||
const clockingAllowed = await isClockingEnabled(db); // CHANGED: pass conn
|
||||
const clockingAllowed = await isClockingEnabled(db);
|
||||
if (!clockingAllowed) {
|
||||
const note = 'Clock-in/out function is not enabled for today.';
|
||||
await db.execute( // CHANGED: use conn
|
||||
await db.execute(
|
||||
`INSERT INTO clock_records
|
||||
(worker_id, event_type, qr_code_id, latitude, longitude, notes, timestamp)
|
||||
VALUES (?, "failed", ?, ?, ?, ?, CURRENT_TIME())`,
|
||||
@@ -115,13 +121,13 @@ export default function() {
|
||||
return res.status(403).json({ message: 'error.clockingDisabled' });
|
||||
}
|
||||
|
||||
// 2) Geofence Validation (unchanged logic, just switch db -> conn)
|
||||
// 2) Geofence Validation
|
||||
if (latitude != null && longitude != null) {
|
||||
const [activeFences] = await db.execute('SELECT coordinates FROM geofences WHERE is_active = 1'); // CHANGED
|
||||
const [activeFences] = await db.execute('SELECT coordinates FROM geofences WHERE is_active = 1');
|
||||
|
||||
if (activeFences.length === 0) {
|
||||
const note = 'Cannot clock in: No active work area is defined.';
|
||||
await db.execute( // CHANGED
|
||||
await db.execute(
|
||||
`INSERT INTO clock_records
|
||||
(worker_id, event_type, qr_code_id, latitude, longitude, notes, timestamp)
|
||||
VALUES (?, "failed", ?, ?, ?, ?, CURRENT_TIME())`,
|
||||
@@ -157,7 +163,7 @@ export default function() {
|
||||
}
|
||||
const distanceString = minDistance.toFixed(2);
|
||||
const note = `Outside geofence by ${distanceString}m`;
|
||||
await db.execute( // CHANGED
|
||||
await db.execute(
|
||||
`INSERT INTO clock_records
|
||||
(worker_id, event_type, qr_code_id, latitude, longitude, notes, timestamp)
|
||||
VALUES (?, "failed", ?, ?, ?, ?, CURRENT_TIME())`,
|
||||
@@ -167,15 +173,15 @@ export default function() {
|
||||
}
|
||||
}
|
||||
|
||||
// 3) QR Code and Status Validation (switch db -> conn; logic unchanged)
|
||||
// 3) QR Code and Status Validation
|
||||
if (qrCodeValue !== 'FORCE_CLOCK_OUT') {
|
||||
const [qrRows] = await db.execute('SELECT is_active FROM qr_codes WHERE id = ?', [qrCodeValue]); // CHANGED
|
||||
const [qrRows] = await db.execute('SELECT is_active FROM qr_codes WHERE id = ?', [qrCodeValue]);
|
||||
if (qrRows.length === 0 || !qrRows[0].is_active) {
|
||||
return res.status(400).json({ message: 'error.invalidQrCode' });
|
||||
}
|
||||
}
|
||||
|
||||
const [lastEvent] = await db.execute( // CHANGED
|
||||
const [lastEvent] = await db.execute(
|
||||
'SELECT event_type FROM clock_records WHERE worker_id = ? ORDER BY timestamp DESC LIMIT 1',
|
||||
[userId]
|
||||
);
|
||||
@@ -184,7 +190,7 @@ export default function() {
|
||||
return res.status(400).json({ message: errorKey });
|
||||
}
|
||||
|
||||
// 4) Record Successful Event — store UTC via SQL conversion (no JS date math)
|
||||
// 4) Record Successful Event
|
||||
await db.execute(
|
||||
`INSERT INTO clock_records
|
||||
(worker_id, event_type, qr_code_id, latitude, longitude, timestamp)
|
||||
@@ -197,50 +203,82 @@ export default function() {
|
||||
console.error('!!! CRITICAL ERROR in /clock route !!!:', error);
|
||||
res.status(500).json({ message: 'error.criticalServer' });
|
||||
} finally {
|
||||
;
|
||||
db.release();
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/workers/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const [rows] = await db.execute("SELECT full_name FROM workers WHERE id = ? AND role = 'worker'", [id]);
|
||||
if (rows.length === 0) {
|
||||
return res.status(404).json({ message: 'Worker not found.' });
|
||||
const db = await getConnection();
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const [rows] = await db.execute("SELECT full_name FROM workers WHERE id = ? AND role = 'worker'", [id]);
|
||||
if (rows.length === 0) {
|
||||
return res.status(404).json({ message: 'Worker not found.' });
|
||||
}
|
||||
res.json(rows[0]);
|
||||
} catch (error) {
|
||||
console.error('Get worker error:', error);
|
||||
res.status(500).json({ message: 'Server error fetching worker' });
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
res.json(rows[0]);
|
||||
});
|
||||
|
||||
router.get('/worker/status/:userId', async (req, res) => {
|
||||
const { userId } = req.params;
|
||||
const [rows] = await db.execute('SELECT event_type FROM clock_records WHERE worker_id = ? ORDER BY timestamp DESC LIMIT 1', [userId]);
|
||||
res.json({ eventType: rows.length > 0 ? rows[0].event_type : 'clock_out' });
|
||||
const db = await getConnection();
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const [rows] = await db.execute('SELECT event_type FROM clock_records WHERE worker_id = ? ORDER BY timestamp DESC LIMIT 1', [userId]);
|
||||
res.json({ eventType: rows.length > 0 ? rows[0].event_type : 'clock_out' });
|
||||
} catch (error) {
|
||||
console.error('Get worker status error:', error);
|
||||
res.status(500).json({ message: 'Server error fetching worker status' });
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/worker/clock-history/:userId', async (req, res) => {
|
||||
const { userId } = req.params;
|
||||
const [rows] = await db.execute(`
|
||||
SELECT cr.id, cr.event_type, cr.timestamp, COALESCE(qc.name, 'Manual Entry') as qrCodeUsedName
|
||||
FROM clock_records cr
|
||||
LEFT JOIN qr_codes qc ON cr.qr_code_id = qc.id
|
||||
WHERE cr.worker_id = ? ORDER BY cr.timestamp DESC
|
||||
`, [userId]);
|
||||
res.json(rows);
|
||||
const db = await getConnection();
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const [rows] = await db.execute(`
|
||||
SELECT cr.id, cr.event_type, cr.timestamp, COALESCE(qc.name, 'Manual Entry') as qrCodeUsedName
|
||||
FROM clock_records cr
|
||||
LEFT JOIN qr_codes qc ON cr.qr_code_id = qc.id
|
||||
WHERE cr.worker_id = ? ORDER BY cr.timestamp DESC
|
||||
`, [userId]);
|
||||
res.json(rows);
|
||||
} catch (error) {
|
||||
console.error('Get clock history error:', error);
|
||||
res.status(500).json({ message: 'Server error fetching clock history' });
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/worker/change-password', async (req, res) => {
|
||||
const { userId } = req.user;
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
if (!currentPassword || !newPassword || newPassword.length < 6) {
|
||||
return res.status(400).json({ message: 'Invalid input.' });
|
||||
const db = await getConnection();
|
||||
try {
|
||||
const { userId } = req.user;
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
if (!currentPassword || !newPassword || newPassword.length < 6) {
|
||||
return res.status(400).json({ message: 'Invalid input.' });
|
||||
}
|
||||
const [rows] = await db.execute('SELECT password_hash FROM workers WHERE id = ?', [userId]);
|
||||
const passwordMatch = await bcrypt.compare(currentPassword, rows[0].password_hash);
|
||||
if (!passwordMatch) {
|
||||
return res.status(401).json({ message: 'Incorrect current password.' });
|
||||
}
|
||||
const newHashedPassword = await bcrypt.hash(newPassword, 10);
|
||||
await db.execute('UPDATE workers SET password_hash = ? WHERE id = ?', [newHashedPassword, userId]);
|
||||
res.json({ message: 'Password updated successfully.' });
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
res.status(500).json({ message: 'Server error changing password' });
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
const [rows] = await db.execute('SELECT password_hash FROM workers WHERE id = ?', [userId]);
|
||||
const passwordMatch = await bcrypt.compare(currentPassword, rows[0].password_hash);
|
||||
if (!passwordMatch) {
|
||||
return res.status(401).json({ message: 'Incorrect current password.' });
|
||||
}
|
||||
const newHashedPassword = await bcrypt.hash(newPassword, 10);
|
||||
await db.execute('UPDATE workers SET password_hash = ? WHERE id = ?', [newHashedPassword, userId]);
|
||||
res.json({ message: 'Password updated successfully.' });
|
||||
});
|
||||
|
||||
router.post('/location/update', async (req, res) => {
|
||||
@@ -249,30 +287,62 @@ export default function() {
|
||||
});
|
||||
|
||||
router.post('/device/register', async (req, res) => {
|
||||
const { userId, deviceUuid } = req.body;
|
||||
const result = await validateDeviceForUser(userId, deviceUuid, db);
|
||||
res.status(result.valid ? 200 : 409).json(result);
|
||||
const db = await getConnection();
|
||||
try {
|
||||
const { userId, deviceUuid } = req.body;
|
||||
const result = await validateDeviceForUser(userId, deviceUuid, db);
|
||||
res.status(result.valid ? 200 : 409).json(result);
|
||||
} catch (error) {
|
||||
console.error('Device register error:', error);
|
||||
res.status(500).json({ message: 'Server error registering device' });
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/device/validate', async (req, res) => {
|
||||
const { userId, deviceUuid } = req.body;
|
||||
const result = await validateDeviceForUser(userId, deviceUuid, db);
|
||||
res.json(result);
|
||||
const db = await getConnection();
|
||||
try {
|
||||
const { userId, deviceUuid } = req.body;
|
||||
const result = await validateDeviceForUser(userId, deviceUuid, db);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Device validate error:', error);
|
||||
res.status(500).json({ message: 'Server error validating device' });
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/security/status/:userId', async (req, res) => {
|
||||
const { userId } = req.params;
|
||||
const [securityRows] = await db.execute('SELECT * FROM security_checks WHERE user_id = ? ORDER BY created_at DESC LIMIT 1', [userId]);
|
||||
const [alertRows] = await db.execute('SELECT * FROM security_alerts WHERE user_id = ? AND created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)', [userId]);
|
||||
res.json({
|
||||
latestSecurityCheck: securityRows[0] || null,
|
||||
recentAlerts: alertRows,
|
||||
});
|
||||
const db = await getConnection();
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const [securityRows] = await db.execute('SELECT * FROM security_checks WHERE user_id = ? ORDER BY created_at DESC LIMIT 1', [userId]);
|
||||
const [alertRows] = await db.execute('SELECT * FROM security_alerts WHERE user_id = ? AND created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)', [userId]);
|
||||
res.json({
|
||||
latestSecurityCheck: securityRows[0] || null,
|
||||
recentAlerts: alertRows,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Security status error:', error);
|
||||
res.status(500).json({ message: 'Server error fetching security status' });
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/security/app-blacklist', async (req, res) => {
|
||||
const [rows] = await db.execute('SELECT package_name FROM app_blacklist');
|
||||
res.json(rows.map(row => row.package_name));
|
||||
const db = await getConnection();
|
||||
try {
|
||||
const [rows] = await db.execute('SELECT package_name FROM app_blacklist');
|
||||
res.json(rows.map(row => row.package_name));
|
||||
} catch (error) {
|
||||
console.error('App blacklist error:', error);
|
||||
res.status(500).json({ message: 'Server error fetching app blacklist' });
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
|
||||
Reference in New Issue
Block a user