Files
location-mqtt-tracker-app/emails/mqtt-credentials.tsx
Joachim Hummel 31c0e1f572 Fix MQTT topic pattern for OwnTracks and implement privacy isolation
CRITICAL FIX: The OwnTracks app publishes to owntracks/<username>/<device_id>,
not owntracks/owntrack/<device_id>. This was causing data delivery failures
and privacy violations.

Changes:
- Fix ACL topic pattern: owntracks/<username>/# (was: owntracks/owntrack/<device_id>)
- Backend now uses MQTT_ADMIN_USERNAME for global subscription
- Update UI forms and placeholders with correct pattern
- Update email template with correct topic format
- Enable Mosquitto ACL file for user isolation
- Add migration script for existing ACL rules
- Update documentation (README, GEMINI.md)

Privacy & Security:
- Each user isolated at MQTT broker level via ACL
- Backend subscribes with admin credentials to owntracks/+/+
- Web UI filters data by parent_user_id for additional security
- GDPR compliant multi-layer defense in depth

Files changed:
- lib/mqtt-db.ts - Updated createDefaultRule() to use username
- app/api/mqtt/credentials/route.ts - Pass username to ACL creation
- app/admin/mqtt/page.tsx - UI forms and state management
- emails/mqtt-credentials.tsx - Email template topic pattern
- lib/mqtt-subscriber.ts - Use admin credentials from env
- mosquitto/config/mosquitto.conf - Enable ACL enforcement
- README.md, GEMINI.md - Documentation updates
- scripts/fix-acl-topic-patterns.js - Migration script
- MQTT_TOPIC_FIX.md - Detailed implementation guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 21:49:31 +00:00

172 lines
4.6 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { EmailLayout } from './components/email-layout';
import { EmailHeader } from './components/email-header';
import { EmailFooter } from './components/email-footer';
interface MqttCredentialsEmailProps {
deviceName: string;
deviceId: string;
mqttUsername: string;
mqttPassword: string;
brokerUrl: string;
brokerHost?: string;
brokerPort?: string;
}
export const MqttCredentialsEmail = ({
deviceName = 'Device',
deviceId = '10',
mqttUsername = 'user_device10',
mqttPassword = 'password123',
brokerUrl = 'mqtt://localhost:1883',
brokerHost = 'localhost',
brokerPort = '1883',
}: MqttCredentialsEmailProps) => {
return (
<EmailLayout preview="MQTT Device Credentials">
<EmailHeader title="MQTT Device Credentials" />
<Section style={content}>
<Text style={paragraph}>
Your MQTT credentials for device <strong>{deviceName}</strong> (ID: {deviceId}):
</Text>
<Section style={credentialsBox}>
<Text style={credentialLabel}>MQTT Broker:</Text>
<Text style={credentialValue}>{brokerUrl}</Text>
<Text style={credentialLabel}>Host:</Text>
<Text style={credentialValue}>{brokerHost}</Text>
<Text style={credentialLabel}>Port:</Text>
<Text style={credentialValue}>{brokerPort}</Text>
<Text style={credentialLabel}>Username:</Text>
<Text style={credentialValue}>{mqttUsername}</Text>
<Text style={credentialLabel}>Password:</Text>
<Text style={credentialValue}>{mqttPassword}</Text>
<Text style={credentialLabel}>Topic Pattern:</Text>
<Text style={credentialValue}>owntracks/{mqttUsername}/#</Text>
</Section>
<Section style={instructionsBox}>
<Text style={instructionsTitle}>OwnTracks App Setup:</Text>
<Text style={instructionStep}>1. Open OwnTracks app</Text>
<Text style={instructionStep}>2. Go to Settings Connection</Text>
<Text style={instructionStep}>3. Set Mode to "MQTT"</Text>
<Text style={instructionStep}>4. Enter the credentials above:</Text>
<Text style={instructionDetail}> Host: {brokerHost}</Text>
<Text style={instructionDetail}> Port: {brokerPort}</Text>
<Text style={instructionDetail}> Username: {mqttUsername}</Text>
<Text style={instructionDetail}> Password: {mqttPassword}</Text>
<Text style={instructionDetail}> Device ID: {deviceId}</Text>
<Text style={instructionStep}>5. Save settings</Text>
<Text style={instructionStep}>6. The app will connect automatically</Text>
</Section>
<Text style={warningText}>
Keep these credentials secure. Do not share them with unauthorized persons.
</Text>
<Text style={paragraph}>
If you have any questions or need assistance, please contact your administrator.
</Text>
<Text style={paragraph}>
Best regards,
<br />
Location Tracker Team
</Text>
</Section>
<EmailFooter />
</EmailLayout>
);
};
export default MqttCredentialsEmail;
const content = {
padding: '20px 40px',
};
const paragraph = {
color: '#374151',
fontSize: '16px',
lineHeight: '1.6',
margin: '0 0 16px',
};
const credentialsBox = {
backgroundColor: '#f9fafb',
border: '1px solid #e5e7eb',
borderRadius: '6px',
padding: '20px',
margin: '20px 0',
};
const credentialLabel = {
color: '#6b7280',
fontSize: '14px',
fontWeight: '600',
margin: '12px 0 4px',
};
const credentialValue = {
backgroundColor: '#ffffff',
border: '1px solid #d1d5db',
borderRadius: '4px',
color: '#111827',
fontSize: '14px',
fontFamily: 'monospace',
padding: '8px 12px',
display: 'block',
margin: '0 0 8px',
};
const instructionsBox = {
backgroundColor: '#eff6ff',
border: '1px solid #bfdbfe',
borderRadius: '6px',
padding: '20px',
margin: '20px 0',
};
const instructionsTitle = {
color: '#1e40af',
fontSize: '16px',
fontWeight: '600',
margin: '0 0 12px',
};
const instructionStep = {
color: '#1e3a8a',
fontSize: '14px',
lineHeight: '1.8',
margin: '4px 0',
fontWeight: '500',
};
const instructionDetail = {
color: '#3730a3',
fontSize: '13px',
lineHeight: '1.6',
margin: '2px 0',
fontFamily: 'monospace',
};
const warningText = {
backgroundColor: '#fef3c7',
border: '1px solid #fbbf24',
borderRadius: '6px',
color: '#92400e',
fontSize: '14px',
lineHeight: '1.6',
margin: '20px 0',
padding: '12px 16px',
};