diff --git a/docs/DIRECTUS-SCHEMA.md b/docs/DIRECTUS-SCHEMA.md index d3bbf0f..c4e4ccc 100644 --- a/docs/DIRECTUS-SCHEMA.md +++ b/docs/DIRECTUS-SCHEMA.md @@ -215,8 +215,8 @@ Meldungen von Anzeigen. | `categories_translations` | ✓ | - | - | - | Für i18n | | `locations` | ✓ | ✓ | - | - | User kann neue Orte anlegen | | `languages` | ✓ | - | - | - | Für Sprach-Auswahl | -| `conversations` | ✓ | ✓ | ✓ | - | Filter via API-Query mit `participant_hash`, Update nur `status` | -| `messages` | ✓ | ✓ | - | - | Filter via `conversation` ID | +| `conversations` | - | - | - | - | **Nur User-Rolle** (s.u.) | +| `messages` | - | - | - | - | **Nur User-Rolle** (s.u.) | | `favorites` | ✓ | ✓ | - | ✓ | Nur eigene | | `reports` | - | ✓ | - | - | Nur erstellen | @@ -288,3 +288,35 @@ module.exports = async function(data) { ``` **Hinweis:** Ohne diese Absicherung könnte jeder `views` auf beliebige Werte setzen. + +--- + +## User Role Permissions (Chat) + +Conversations und Messages sind **nicht** über die Public-Rolle zugänglich. Nur eingeloggte User (User-Rolle) dürfen diese Collections nutzen. + +### conversations (User-Rolle) + +| Aktion | Erlaubt | Filter / Einschränkung | +|--------|:-------:|------------------------| +| Read | ✓ | Alle (Client filtert via `participant_hash`) | +| Create | ✓ | Keine Einschränkung | +| Update | ✓ | Nur `status` Feld, kein Row-Filter | +| Delete | - | Nicht erlaubt | + +**Hinweis:** Im Zero-Knowledge-Design gibt es kein server-verifizierbares Feld, das beide Teilnehmer identifiziert (`participant_hash` ist nicht an einen Directus-User gebunden). Deshalb kann kein `$CURRENT_USER`-Filter für Read/Update gesetzt werden. Die Absicherung erfolgt durch: (1) Authentifizierungspflicht (Public hat keinen Zugriff), (2) Client-seitige Filterung via `participant_hash`, (3) Update nur auf `status`-Feld beschränkt, (4) Conversation-UUIDs sind nicht erratbar. + +### messages (User-Rolle) + +| Aktion | Erlaubt | Filter / Einschränkung | +|--------|:-------:|------------------------| +| Read | ✓ | Alle (Inhalte sind E2E-verschlüsselt) | +| Create | ✓ | Keine Einschränkung | +| Update | - | Nicht erlaubt | +| Delete | - | Nicht erlaubt | + +**Sicherheitsmodell:** +- Nachrichten sind client-seitig E2E-verschlüsselt — Server sieht nur Ciphertext +- Ohne Private Key kann niemand Nachrichten entschlüsseln +- Public-Rolle hat **keinen** Zugriff auf `conversations` und `messages` +- Nur eingeloggte User können Conversations erstellen und Nachrichten senden diff --git a/js/components/chat-widget.js b/js/components/chat-widget.js index 76ad3a7..eed092e 100644 --- a/js/components/chat-widget.js +++ b/js/components/chat-widget.js @@ -6,6 +6,7 @@ import { t, i18n } from '../i18n.js' import { conversationsService } from '../services/conversations.js' import { cryptoService } from '../services/crypto.js' +import { keyPinningService } from '../services/key-pinning.js' import { escapeHTML } from '../utils/helpers.js' import { reputationService } from '../services/reputation.js' @@ -25,6 +26,7 @@ class ChatWidget extends HTMLElement { this.deal = null this.hasRated = false this.mySecretKey = null + this.keyWarning = false } connectedCallback() { @@ -72,6 +74,16 @@ class ChatWidget extends HTMLElement { this.render() return } + + const pinStatus = keyPinningService.check(this.listingId, this.sellerPublicKey) + if (pinStatus === 'changed') { + this.keyWarning = true + this.loading = false + this.render() + this.setupEventListeners() + return + } + this.conversation = await conversationsService.startOrGetConversation(this.listingId, this.sellerPublicKey) this.mySecretKey = await conversationsService.getMySecretKeyForConversation(this.conversation) await this.loadMessages() @@ -85,6 +97,14 @@ class ChatWidget extends HTMLElement { this.setupEventListeners() } + async acceptKeyChange() { + keyPinningService.acceptChange(this.listingId, this.sellerPublicKey) + this.keyWarning = false + this.loading = true + this.render() + await this.initConversation() + } + async loadMessages() { if (!this.conversation) return this.messages = await conversationsService.getMessages( @@ -141,6 +161,22 @@ class ChatWidget extends HTMLElement { return } + if (this.keyWarning) { + this.innerHTML = /* html */` +
+ ` + return + } + this.innerHTML = /* html */`