Clean up development artifacts and obsolete code
Dependencies: - Remove unused bcrypt package (only bcryptjs is used) - Remove @types/bcrypt (only @types/bcryptjs needed) Scripts cleanup: - Delete migration scripts (one-time use, already applied): - add-mqtt-tables.js - add-parent-user-column.js - migrate-device-ownership.js - fix-acl-topic-patterns.js - update-acl-permission.js - Delete personal test/debug scripts: - reset-joachim-password.js - test-joachim-password.js - check-admin.js - check-user-password.js - test-password.js - test-device-access.js - test-user-visibility.js - Move change-mqtt-admin-password.sh to scripts/ directory Code cleanup: - Remove debug console.log statements from: - app/api/locations/ingest/route.ts - components/map/MapView.tsx (2 debug logs) - lib/db.ts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -36,15 +36,6 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// Debug logging for speed and battery values
|
||||
console.log('[Ingest Debug] Received locations:', locations.map(loc => ({
|
||||
username: loc.username,
|
||||
speed: loc.speed,
|
||||
speed_type: typeof loc.speed,
|
||||
battery: loc.battery,
|
||||
battery_type: typeof loc.battery
|
||||
})));
|
||||
|
||||
// Validate required fields
|
||||
for (const loc of locations) {
|
||||
if (!loc.latitude || !loc.longitude || !loc.timestamp) {
|
||||
|
||||
@@ -164,18 +164,7 @@ export default function MapView({ selectedDevice, timeFilter, isPaused, filterMo
|
||||
|
||||
const data: LocationResponse = await response.json();
|
||||
|
||||
// Debug: Log last 3 locations to see speed/battery values
|
||||
if (data.history && data.history.length > 0) {
|
||||
console.log('[MapView Debug] Last 3 locations:', data.history.slice(0, 3).map(loc => ({
|
||||
username: loc.username,
|
||||
timestamp: loc.timestamp,
|
||||
speed: loc.speed,
|
||||
speed_type: typeof loc.speed,
|
||||
speed_is_null: loc.speed === null,
|
||||
speed_is_undefined: loc.speed === undefined,
|
||||
battery: loc.battery,
|
||||
})));
|
||||
|
||||
// Auto-center to latest location
|
||||
const latest = data.history[0];
|
||||
if (latest && latest.latitude && latest.longitude) {
|
||||
@@ -301,18 +290,6 @@ export default function MapView({ selectedDevice, timeFilter, isPaused, filterMo
|
||||
// Calculate z-index: newer locations get higher z-index
|
||||
const zIndexOffset = sortedLocs.length - idx;
|
||||
|
||||
// Debug: Log for latest location only
|
||||
if (isLatest) {
|
||||
console.log('[Popup Debug] Latest location for', device.name, {
|
||||
speed: loc.speed,
|
||||
speed_type: typeof loc.speed,
|
||||
speed_is_null: loc.speed === null,
|
||||
speed_is_undefined: loc.speed === undefined,
|
||||
condition_result: loc.speed != null,
|
||||
display_time: loc.display_time
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Marker
|
||||
key={`${deviceId}-${loc.timestamp}-${idx}`}
|
||||
|
||||
@@ -376,15 +376,6 @@ export const locationDb = {
|
||||
const batteryValue = loc.battery !== undefined && loc.battery !== null ? Number(loc.battery) : null;
|
||||
const speedValue = loc.speed !== undefined && loc.speed !== null ? Number(loc.speed) : null;
|
||||
|
||||
// Debug log
|
||||
console.log('[DB Insert Debug]', {
|
||||
username: loc.username,
|
||||
speed_in: loc.speed,
|
||||
speed_out: speedValue,
|
||||
battery_in: loc.battery,
|
||||
battery_out: batteryValue
|
||||
});
|
||||
|
||||
const result = stmt.run(
|
||||
loc.latitude,
|
||||
loc.longitude,
|
||||
|
||||
@@ -23,13 +23,11 @@
|
||||
"dependencies": {
|
||||
"@react-email/components": "^0.5.7",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.4",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"bcrypt": "^6.0.0",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"leaflet": "^1.9.4",
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Migration script to add MQTT credentials and ACL tables
|
||||
* This extends the existing database with MQTT provisioning capabilities
|
||||
*/
|
||||
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const dataDir = path.join(__dirname, '..', 'data');
|
||||
const dbPath = path.join(dataDir, 'database.sqlite');
|
||||
|
||||
// Check if database exists
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
console.error('❌ Database not found. Run npm run db:init:app first');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Open database
|
||||
const db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL');
|
||||
|
||||
console.log('Starting MQTT tables migration...\n');
|
||||
|
||||
// Create mqtt_credentials table
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS mqtt_credentials (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT NOT NULL UNIQUE,
|
||||
mqtt_username TEXT NOT NULL UNIQUE,
|
||||
mqtt_password_hash TEXT NOT NULL,
|
||||
enabled INTEGER DEFAULT 1,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
|
||||
FOREIGN KEY (device_id) REFERENCES Device(id) ON DELETE CASCADE,
|
||||
CHECK (enabled IN (0, 1))
|
||||
);
|
||||
`);
|
||||
console.log('✓ Created mqtt_credentials table');
|
||||
|
||||
// Create mqtt_acl_rules table
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS mqtt_acl_rules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT NOT NULL,
|
||||
topic_pattern TEXT NOT NULL,
|
||||
permission TEXT NOT NULL CHECK(permission IN ('read', 'write', 'readwrite')),
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
|
||||
FOREIGN KEY (device_id) REFERENCES Device(id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
console.log('✓ Created mqtt_acl_rules table');
|
||||
|
||||
// Create indexes for performance
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_mqtt_credentials_device
|
||||
ON mqtt_credentials(device_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_mqtt_credentials_username
|
||||
ON mqtt_credentials(mqtt_username);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_mqtt_acl_device
|
||||
ON mqtt_acl_rules(device_id);
|
||||
`);
|
||||
console.log('✓ Created indexes');
|
||||
|
||||
// Create mqtt_sync_status table to track pending changes
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS mqtt_sync_status (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
pending_changes INTEGER DEFAULT 0,
|
||||
last_sync_at TEXT,
|
||||
last_sync_status TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
`);
|
||||
console.log('✓ Created mqtt_sync_status table');
|
||||
|
||||
// Initialize sync status
|
||||
const syncStatus = db.prepare('SELECT * FROM mqtt_sync_status WHERE id = 1').get();
|
||||
if (!syncStatus) {
|
||||
db.prepare(`
|
||||
INSERT INTO mqtt_sync_status (id, pending_changes, last_sync_status)
|
||||
VALUES (1, 0, 'never_synced')
|
||||
`).run();
|
||||
console.log('✓ Initialized sync status');
|
||||
} else {
|
||||
console.log('✓ Sync status already exists');
|
||||
}
|
||||
|
||||
// Get stats
|
||||
const mqttCredsCount = db.prepare('SELECT COUNT(*) as count FROM mqtt_credentials').get();
|
||||
const aclRulesCount = db.prepare('SELECT COUNT(*) as count FROM mqtt_acl_rules').get();
|
||||
|
||||
console.log(`\n✓ MQTT tables migration completed successfully!`);
|
||||
console.log(` MQTT Credentials: ${mqttCredsCount.count}`);
|
||||
console.log(` ACL Rules: ${aclRulesCount.count}`);
|
||||
|
||||
db.close();
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Add parent_user_id column to User table
|
||||
* This enables parent-child relationship for ADMIN -> VIEWER hierarchy
|
||||
*/
|
||||
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
|
||||
const dataDir = path.join(__dirname, '..', 'data');
|
||||
const dbPath = path.join(dataDir, 'database.sqlite');
|
||||
|
||||
console.log('Adding parent_user_id column to User table...');
|
||||
|
||||
const db = new Database(dbPath);
|
||||
|
||||
try {
|
||||
// Check if column already exists
|
||||
const tableInfo = db.prepare("PRAGMA table_info(User)").all();
|
||||
const hasParentColumn = tableInfo.some(col => col.name === 'parent_user_id');
|
||||
|
||||
if (hasParentColumn) {
|
||||
console.log('⚠ Column parent_user_id already exists, skipping...');
|
||||
} else {
|
||||
// Add parent_user_id column
|
||||
db.exec(`
|
||||
ALTER TABLE User ADD COLUMN parent_user_id TEXT;
|
||||
`);
|
||||
console.log('✓ Added parent_user_id column');
|
||||
|
||||
// Create index for faster lookups
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_user_parent ON User(parent_user_id);
|
||||
`);
|
||||
console.log('✓ Created index on parent_user_id');
|
||||
|
||||
// Add foreign key constraint check
|
||||
// Note: SQLite doesn't enforce foreign keys on ALTER TABLE,
|
||||
// but we'll add the constraint in the application logic
|
||||
console.log('✓ Parent-child relationship enabled');
|
||||
}
|
||||
|
||||
db.close();
|
||||
console.log('\n✓ Migration completed successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error during migration:', error);
|
||||
db.close();
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Check admin user and test password verification
|
||||
*/
|
||||
|
||||
const Database = require('better-sqlite3');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = path.join(__dirname, '..', 'data', 'database.sqlite');
|
||||
const db = new Database(dbPath);
|
||||
|
||||
// Get admin user
|
||||
const user = db.prepare('SELECT * FROM User WHERE username = ?').get('admin');
|
||||
|
||||
if (!user) {
|
||||
console.log('❌ Admin user not found!');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Admin user found:');
|
||||
console.log(' ID:', user.id);
|
||||
console.log(' Username:', user.username);
|
||||
console.log(' Email:', user.email);
|
||||
console.log(' Role:', user.role);
|
||||
console.log(' Password Hash:', user.passwordHash.substring(0, 20) + '...');
|
||||
|
||||
// Test password verification
|
||||
const testPassword = 'admin123';
|
||||
console.log('\nTesting password verification...');
|
||||
console.log(' Test password:', testPassword);
|
||||
|
||||
try {
|
||||
const isValid = bcrypt.compareSync(testPassword, user.passwordHash);
|
||||
console.log(' Result:', isValid ? '✅ VALID' : '❌ INVALID');
|
||||
|
||||
if (!isValid) {
|
||||
console.log('\n⚠️ Password verification failed!');
|
||||
console.log('Recreating admin user with fresh hash...');
|
||||
|
||||
const newHash = bcrypt.hashSync(testPassword, 10);
|
||||
db.prepare('UPDATE User SET passwordHash = ? WHERE username = ?').run(newHash, 'admin');
|
||||
|
||||
console.log('✅ Admin password reset successfully');
|
||||
console.log('Try logging in again with: admin / admin123');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(' Error:', error.message);
|
||||
}
|
||||
|
||||
db.close();
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Check user password hash in database
|
||||
* Usage: node scripts/check-user-password.js <username>
|
||||
*/
|
||||
|
||||
const Database = require('better-sqlite3');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = path.join(process.cwd(), 'data', 'database.sqlite');
|
||||
|
||||
async function checkUserPassword(username) {
|
||||
const db = new Database(dbPath, { readonly: true });
|
||||
|
||||
try {
|
||||
const user = db.prepare('SELECT * FROM User WHERE username = ?').get(username);
|
||||
|
||||
if (!user) {
|
||||
console.error(`❌ User "${username}" not found`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\n✓ User found: ${user.username}`);
|
||||
console.log(` ID: ${user.id}`);
|
||||
console.log(` Email: ${user.email || 'N/A'}`);
|
||||
console.log(` Role: ${user.role}`);
|
||||
console.log(` Created: ${user.createdAt}`);
|
||||
console.log(` Updated: ${user.updatedAt}`);
|
||||
console.log(` Last Login: ${user.lastLoginAt || 'Never'}`);
|
||||
console.log(`\n Password Hash: ${user.passwordHash.substring(0, 60)}...`);
|
||||
console.log(` Hash starts with: ${user.passwordHash.substring(0, 7)}`);
|
||||
|
||||
// Check if it's a valid bcrypt hash
|
||||
const isBcrypt = user.passwordHash.startsWith('$2a$') ||
|
||||
user.passwordHash.startsWith('$2b$') ||
|
||||
user.passwordHash.startsWith('$2y$');
|
||||
|
||||
if (isBcrypt) {
|
||||
console.log(` ✓ Hash format: Valid bcrypt hash`);
|
||||
|
||||
// Extract rounds
|
||||
const rounds = parseInt(user.passwordHash.split('$')[2]);
|
||||
console.log(` ✓ Bcrypt rounds: ${rounds}`);
|
||||
} else {
|
||||
console.log(` ❌ Hash format: NOT a valid bcrypt hash!`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
const username = process.argv[2];
|
||||
if (!username) {
|
||||
console.error('Usage: node scripts/check-user-password.js <username>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
checkUserPassword(username);
|
||||
@@ -1,88 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Migration script to fix ACL topic patterns
|
||||
*
|
||||
* Changes: owntracks/owntrack/<device_id> → owntracks/<username>/#
|
||||
*
|
||||
* This script:
|
||||
* 1. Finds all ACL rules with the old pattern
|
||||
* 2. Looks up the correct MQTT username for each device
|
||||
* 3. Updates the topic_pattern to use the username
|
||||
*/
|
||||
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = path.join(__dirname, '..', 'tracker.db');
|
||||
const db = new Database(dbPath);
|
||||
|
||||
console.log('🔧 Fixing ACL topic patterns...\n');
|
||||
|
||||
try {
|
||||
// Get all ACL rules with the old pattern
|
||||
const aclRules = db.prepare(`
|
||||
SELECT id, device_id, topic_pattern, permission
|
||||
FROM mqtt_acl_rules
|
||||
WHERE topic_pattern LIKE 'owntracks/owntrack/%'
|
||||
`).all();
|
||||
|
||||
console.log(`Found ${aclRules.length} ACL rules to fix\n`);
|
||||
|
||||
if (aclRules.length === 0) {
|
||||
console.log('✓ No ACL rules need fixing!');
|
||||
db.close();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let fixed = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const rule of aclRules) {
|
||||
// Look up the MQTT username for this device
|
||||
const credential = db.prepare(`
|
||||
SELECT mqtt_username
|
||||
FROM mqtt_credentials
|
||||
WHERE device_id = ?
|
||||
`).get(rule.device_id);
|
||||
|
||||
if (!credential) {
|
||||
console.log(`⚠ Warning: No MQTT credentials found for device ${rule.device_id}, skipping...`);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const oldPattern = rule.topic_pattern;
|
||||
const newPattern = `owntracks/${credential.mqtt_username}/#`;
|
||||
|
||||
// Update the ACL rule
|
||||
db.prepare(`
|
||||
UPDATE mqtt_acl_rules
|
||||
SET topic_pattern = ?
|
||||
WHERE id = ?
|
||||
`).run(newPattern, rule.id);
|
||||
|
||||
console.log(`✓ Fixed rule for device ${rule.device_id}:`);
|
||||
console.log(` Old: ${oldPattern}`);
|
||||
console.log(` New: ${newPattern}\n`);
|
||||
fixed++;
|
||||
}
|
||||
|
||||
// Mark pending changes for MQTT sync
|
||||
db.prepare(`
|
||||
UPDATE mqtt_sync_status
|
||||
SET pending_changes = pending_changes + 1
|
||||
`).run();
|
||||
|
||||
console.log('─'.repeat(50));
|
||||
console.log(`\n✅ Migration complete!`);
|
||||
console.log(` Fixed: ${fixed}`);
|
||||
console.log(` Failed: ${failed}`);
|
||||
console.log(`\n⚠️ Run MQTT Sync to apply changes to Mosquitto broker`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error during migration:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Migrate devices with NULL ownerId to admin user
|
||||
* Usage: node scripts/migrate-device-ownership.js
|
||||
*/
|
||||
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = path.join(process.cwd(), 'data', 'database.sqlite');
|
||||
|
||||
function migrateDeviceOwnership() {
|
||||
const db = new Database(dbPath);
|
||||
|
||||
try {
|
||||
// Find first admin user
|
||||
const adminUser = db.prepare(`
|
||||
SELECT * FROM User
|
||||
WHERE role = 'ADMIN'
|
||||
ORDER BY createdAt ASC
|
||||
LIMIT 1
|
||||
`).get();
|
||||
|
||||
if (!adminUser) {
|
||||
console.error('❌ No admin user found in database');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`✓ Admin user found: ${adminUser.username} (${adminUser.id})`);
|
||||
|
||||
// Find devices with NULL ownerId
|
||||
const devicesWithoutOwner = db.prepare(`
|
||||
SELECT * FROM Device
|
||||
WHERE ownerId IS NULL
|
||||
`).all();
|
||||
|
||||
console.log(`\n📊 Found ${devicesWithoutOwner.length} devices without owner`);
|
||||
|
||||
if (devicesWithoutOwner.length === 0) {
|
||||
console.log('✓ All devices already have an owner');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update devices to assign to admin
|
||||
const updateStmt = db.prepare(`
|
||||
UPDATE Device
|
||||
SET ownerId = ?, updatedAt = datetime('now')
|
||||
WHERE ownerId IS NULL
|
||||
`);
|
||||
|
||||
const result = updateStmt.run(adminUser.id);
|
||||
|
||||
console.log(`\n✅ Updated ${result.changes} devices`);
|
||||
console.log(` Assigned to: ${adminUser.username} (${adminUser.id})`);
|
||||
|
||||
// Show updated devices
|
||||
const updatedDevices = db.prepare(`
|
||||
SELECT id, name, ownerId FROM Device
|
||||
WHERE ownerId = ?
|
||||
`).all(adminUser.id);
|
||||
|
||||
console.log('\n📋 Devices now owned by admin:');
|
||||
updatedDevices.forEach(device => {
|
||||
console.log(` - ${device.id}: ${device.name}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
migrateDeviceOwnership();
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const bcrypt = require('bcryptjs');
|
||||
const path = require('path');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const dbPath = path.join(process.cwd(), 'data', 'database.sqlite');
|
||||
const db = new Database(dbPath);
|
||||
|
||||
const newPassword = 'joachim123';
|
||||
const passwordHash = bcrypt.hashSync(newPassword, 10);
|
||||
|
||||
const result = db.prepare('UPDATE User SET passwordHash = ? WHERE username = ?').run(passwordHash, 'joachim');
|
||||
|
||||
if (result.changes > 0) {
|
||||
console.log('✓ Password reset successfully for user "joachim"');
|
||||
console.log(` New password: ${newPassword}`);
|
||||
} else {
|
||||
console.log('❌ User "joachim" not found');
|
||||
}
|
||||
|
||||
db.close();
|
||||
@@ -1,89 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Test device access control after security fix
|
||||
* Tests that users can only see devices they own
|
||||
*/
|
||||
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = path.join(__dirname, '..', 'data', 'database.sqlite');
|
||||
const db = new Database(dbPath);
|
||||
|
||||
// Import the getAllowedDeviceIds logic
|
||||
function getAllowedDeviceIds(userId, role, username) {
|
||||
try {
|
||||
// Super admin (username === "admin") can see ALL devices
|
||||
if (username === 'admin') {
|
||||
const allDevices = db.prepare('SELECT id FROM Device WHERE isActive = 1').all();
|
||||
return allDevices.map(d => d.id);
|
||||
}
|
||||
|
||||
// VIEWER users see their parent user's devices
|
||||
if (role === 'VIEWER') {
|
||||
const user = db.prepare('SELECT parent_user_id FROM User WHERE id = ?').get(userId);
|
||||
if (user?.parent_user_id) {
|
||||
const devices = db.prepare('SELECT id FROM Device WHERE ownerId = ? AND isActive = 1').all(user.parent_user_id);
|
||||
return devices.map(d => d.id);
|
||||
}
|
||||
// If VIEWER has no parent, return empty array
|
||||
return [];
|
||||
}
|
||||
|
||||
// Regular ADMIN users see only their own devices
|
||||
if (role === 'ADMIN') {
|
||||
const devices = db.prepare('SELECT id FROM Device WHERE ownerId = ? AND isActive = 1').all(userId);
|
||||
return devices.map(d => d.id);
|
||||
}
|
||||
|
||||
// Default: no access
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('Error in getAllowedDeviceIds:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
console.log('=== Device Access Control Test ===\n');
|
||||
|
||||
// Get all users
|
||||
const users = db.prepare('SELECT id, username, role, parent_user_id FROM User').all();
|
||||
|
||||
// Get all devices
|
||||
const allDevices = db.prepare('SELECT id, name, ownerId FROM Device WHERE isActive = 1').all();
|
||||
|
||||
console.log('All devices in system:');
|
||||
allDevices.forEach(d => {
|
||||
console.log(` - Device ${d.id} (${d.name}) owned by: ${d.ownerId}`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
// Test each user
|
||||
users.forEach(user => {
|
||||
const allowedDevices = getAllowedDeviceIds(user.id, user.role, user.username);
|
||||
|
||||
console.log(`User: ${user.username} (${user.role})`);
|
||||
console.log(` ID: ${user.id}`);
|
||||
if (user.parent_user_id) {
|
||||
const parent = users.find(u => u.id === user.parent_user_id);
|
||||
console.log(` Parent: ${parent?.username || 'unknown'}`);
|
||||
}
|
||||
console.log(` Can see devices: ${allowedDevices.length > 0 ? allowedDevices.join(', ') : 'NONE'}`);
|
||||
|
||||
// Show device names
|
||||
if (allowedDevices.length > 0) {
|
||||
allowedDevices.forEach(deviceId => {
|
||||
const device = allDevices.find(d => d.id === deviceId);
|
||||
console.log(` - ${deviceId}: ${device?.name || 'unknown'}`);
|
||||
});
|
||||
}
|
||||
console.log('');
|
||||
});
|
||||
|
||||
console.log('=== Expected Results ===');
|
||||
console.log('✓ admin: Should see ALL devices (10, 11, 12, 15)');
|
||||
console.log('✓ joachim: Should see only devices 12, 15 (owned by joachim)');
|
||||
console.log('✓ hummel: Should see devices 12, 15 (parent joachim\'s devices)');
|
||||
console.log('✓ joachiminfo: Should see NO devices (doesn\'t own any)');
|
||||
|
||||
db.close();
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const bcrypt = require('bcryptjs');
|
||||
const path = require('path');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const dbPath = path.join(process.cwd(), 'data', 'database.sqlite');
|
||||
const db = new Database(dbPath);
|
||||
|
||||
const joachim = db.prepare('SELECT * FROM User WHERE username = ?').get('joachim');
|
||||
|
||||
if (!joachim) {
|
||||
console.log('❌ User "joachim" not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Testing passwords for joachim:');
|
||||
|
||||
const passwords = ['joachim123', 'joachim', 'password', 'admin123'];
|
||||
for (const pwd of passwords) {
|
||||
const match = bcrypt.compareSync(pwd, joachim.passwordHash);
|
||||
console.log(` '${pwd}': ${match ? '✓ MATCH' : '✗ no match'}`);
|
||||
}
|
||||
|
||||
db.close();
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Test if a password matches a user's stored hash
|
||||
* Usage: node scripts/test-password.js <username> <password>
|
||||
*/
|
||||
|
||||
const Database = require('better-sqlite3');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = path.join(process.cwd(), 'data', 'database.sqlite');
|
||||
|
||||
async function testPassword(username, password) {
|
||||
const db = new Database(dbPath, { readonly: true });
|
||||
|
||||
try {
|
||||
const user = db.prepare('SELECT * FROM User WHERE username = ?').get(username);
|
||||
|
||||
if (!user) {
|
||||
console.error(`❌ User "${username}" not found`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\n✓ User found: ${user.username}`);
|
||||
console.log(` Testing password...`);
|
||||
|
||||
const isValid = await bcrypt.compare(password, user.passwordHash);
|
||||
|
||||
if (isValid) {
|
||||
console.log(` ✅ Password is CORRECT!`);
|
||||
} else {
|
||||
console.log(` ❌ Password is INCORRECT!`);
|
||||
console.log(`\n Debug info:`);
|
||||
console.log(` - Password provided: "${password}"`);
|
||||
console.log(` - Password length: ${password.length}`);
|
||||
console.log(` - Hash in DB: ${user.passwordHash.substring(0, 60)}...`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
const username = process.argv[2];
|
||||
const password = process.argv[3];
|
||||
|
||||
if (!username || !password) {
|
||||
console.error('Usage: node scripts/test-password.js <username> <password>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
testPassword(username, password);
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Test script to verify user visibility restrictions
|
||||
* - Admin can see all users including "admin"
|
||||
* - Non-admin users (like "joachim") cannot see the "admin" user
|
||||
*/
|
||||
|
||||
const baseUrl = 'http://localhost:3001';
|
||||
|
||||
async function login(username, password) {
|
||||
const response = await fetch(`${baseUrl}/api/auth/signin`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
|
||||
const cookies = response.headers.get('set-cookie');
|
||||
return cookies;
|
||||
}
|
||||
|
||||
async function getUsers(cookies) {
|
||||
const response = await fetch(`${baseUrl}/api/users`, {
|
||||
headers: {
|
||||
'Cookie': cookies,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(`Failed to get users: ${error.error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.users;
|
||||
}
|
||||
|
||||
async function testAdminUser() {
|
||||
console.log('\n🔍 Testing with "admin" user:');
|
||||
console.log('================================');
|
||||
|
||||
try {
|
||||
const cookies = await login('admin', 'admin123');
|
||||
const users = await getUsers(cookies);
|
||||
|
||||
console.log(`✓ Admin can see ${users.length} user(s):`);
|
||||
users.forEach(user => {
|
||||
console.log(` - ${user.username} (${user.role})`);
|
||||
});
|
||||
|
||||
const hasAdminUser = users.some(u => u.username === 'admin');
|
||||
if (hasAdminUser) {
|
||||
console.log('✓ Admin can see the "admin" user ✓');
|
||||
} else {
|
||||
console.log('✗ FAIL: Admin cannot see the "admin" user');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`✗ FAIL: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testJoachimUser() {
|
||||
console.log('\n🔍 Testing with "joachim" user:');
|
||||
console.log('================================');
|
||||
|
||||
try {
|
||||
const cookies = await login('joachim', 'joachim123');
|
||||
const users = await getUsers(cookies);
|
||||
|
||||
console.log(`✓ Joachim can see ${users.length} user(s):`);
|
||||
users.forEach(user => {
|
||||
console.log(` - ${user.username} (${user.role})`);
|
||||
});
|
||||
|
||||
const hasAdminUser = users.some(u => u.username === 'admin');
|
||||
if (!hasAdminUser) {
|
||||
console.log('✓ Joachim cannot see the "admin" user ✓');
|
||||
} else {
|
||||
console.log('✗ FAIL: Joachim can see the "admin" user (should be hidden)');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`✗ FAIL: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Testing User Visibility Restrictions');
|
||||
console.log('=====================================\n');
|
||||
console.log('Expected behavior:');
|
||||
console.log(' - Admin user can see all users including "admin"');
|
||||
console.log(' - Non-admin users (joachim) cannot see "admin" user\n');
|
||||
|
||||
await testAdminUser();
|
||||
await testJoachimUser();
|
||||
|
||||
console.log('\n✓ Test completed!');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Update ACL rule permission to readwrite
|
||||
*/
|
||||
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = path.join(process.cwd(), 'data', 'database.sqlite');
|
||||
const db = new Database(dbPath);
|
||||
|
||||
try {
|
||||
// Update ACL rule 11 to readwrite
|
||||
db.prepare('UPDATE mqtt_acl_rules SET permission = ? WHERE id = ?').run('readwrite', 11);
|
||||
|
||||
// Mark pending changes
|
||||
db.prepare(`UPDATE mqtt_sync_status
|
||||
SET pending_changes = pending_changes + 1,
|
||||
updated_at = datetime('now')
|
||||
WHERE id = 1`).run();
|
||||
|
||||
console.log('✓ ACL rule updated to readwrite');
|
||||
|
||||
const updated = db.prepare('SELECT * FROM mqtt_acl_rules WHERE id = ?').get(11);
|
||||
console.log('\nUpdated rule:');
|
||||
console.log(JSON.stringify(updated, null, 2));
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
Reference in New Issue
Block a user