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>
This commit is contained in:
@@ -64,6 +64,7 @@ export default function MqttPage() {
|
||||
device_id: "",
|
||||
topic_pattern: "",
|
||||
permission: "readwrite" as 'read' | 'write' | 'readwrite',
|
||||
mqtt_username: "", // Needed for correct topic pattern
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -242,7 +243,7 @@ export default function MqttPage() {
|
||||
await fetchAclRules(aclFormData.device_id);
|
||||
await fetchSyncStatus();
|
||||
setShowAclModal(false);
|
||||
setAclFormData({ device_id: "", topic_pattern: "", permission: "readwrite" });
|
||||
setAclFormData({ device_id: "", topic_pattern: "", permission: "readwrite", mqtt_username: "" });
|
||||
} catch (err: any) {
|
||||
alert(err.message);
|
||||
}
|
||||
@@ -407,8 +408,9 @@ export default function MqttPage() {
|
||||
onClick={() => {
|
||||
setAclFormData({
|
||||
device_id: cred.device_id,
|
||||
topic_pattern: `owntracks/owntrack/${cred.device_id}`,
|
||||
permission: "readwrite"
|
||||
topic_pattern: `owntracks/${cred.mqtt_username}/#`,
|
||||
permission: "readwrite",
|
||||
mqtt_username: cred.mqtt_username
|
||||
});
|
||||
setShowAclModal(true);
|
||||
}}
|
||||
@@ -598,11 +600,11 @@ export default function MqttPage() {
|
||||
value={aclFormData.topic_pattern}
|
||||
onChange={(e) => setAclFormData({ ...aclFormData, topic_pattern: e.target.value })}
|
||||
className="w-full px-3 py-2 border rounded-md"
|
||||
placeholder={`owntracks/owntrack/${aclFormData.device_id || '<DeviceID>'}`}
|
||||
placeholder={`owntracks/${aclFormData.mqtt_username || '<Username>'}/#`}
|
||||
required
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Format: owntracks/owntrack/<DeviceID> (z.B. owntracks/owntrack/10)
|
||||
Format: owntracks/<Username>/# (z.B. owntracks/device_12_4397af93/#)
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
|
||||
Reference in New Issue
Block a user