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
|
// Validate required fields
|
||||||
for (const loc of locations) {
|
for (const loc of locations) {
|
||||||
if (!loc.latitude || !loc.longitude || !loc.timestamp) {
|
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();
|
const data: LocationResponse = await response.json();
|
||||||
|
|
||||||
// Debug: Log last 3 locations to see speed/battery values
|
|
||||||
if (data.history && data.history.length > 0) {
|
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
|
// Auto-center to latest location
|
||||||
const latest = data.history[0];
|
const latest = data.history[0];
|
||||||
if (latest && latest.latitude && latest.longitude) {
|
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
|
// Calculate z-index: newer locations get higher z-index
|
||||||
const zIndexOffset = sortedLocs.length - idx;
|
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 (
|
return (
|
||||||
<Marker
|
<Marker
|
||||||
key={`${deviceId}-${loc.timestamp}-${idx}`}
|
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 batteryValue = loc.battery !== undefined && loc.battery !== null ? Number(loc.battery) : null;
|
||||||
const speedValue = loc.speed !== undefined && loc.speed !== null ? Number(loc.speed) : 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(
|
const result = stmt.run(
|
||||||
loc.latitude,
|
loc.latitude,
|
||||||
loc.longitude,
|
loc.longitude,
|
||||||
|
|||||||
@@ -23,13 +23,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-email/components": "^0.5.7",
|
"@react-email/components": "^0.5.7",
|
||||||
"@tailwindcss/postcss": "^4.1.17",
|
"@tailwindcss/postcss": "^4.1.17",
|
||||||
"@types/bcrypt": "^6.0.0",
|
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/leaflet": "^1.9.21",
|
"@types/leaflet": "^1.9.21",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.4",
|
"@types/react": "^19.2.4",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"bcrypt": "^6.0.0",
|
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"better-sqlite3": "^12.4.1",
|
"better-sqlite3": "^12.4.1",
|
||||||
"leaflet": "^1.9.4",
|
"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