Files
kashilo/docs/DIRECTUS-SETUP.md

27 KiB
Raw Blame History

Directus Setup für dgray.io

Komplette Anleitung zur Einrichtung von Directus als Backend für die dgray Kleinanzeigen-PWA.

API URL: https://api.dgray.io/


Inhaltsverzeichnis

  1. Data Models (Collections)
  2. User Roles
  3. Access Policies
  4. Flows (Automatisierungen)
  5. Einstellungen
  6. Frontend-Integration
  7. Initiale Daten
  8. Checkliste
  9. Anonyme Authentifizierung (UUID-basiert)
  10. Sicherheitshinweise

Datenmodell-Übersicht

erDiagram
    USERS ||--o{ LISTINGS : creates
    USERS ||--o{ FAVORITES : has
    USERS ||--o{ REPORTS : submits
    
    LISTINGS ||--o{ LISTINGS_FILES : has
    LISTINGS ||--o{ FAVORITES : receives
    LISTINGS }o--|| CATEGORIES : belongs_to
    LISTINGS }o--o| LOCATIONS : located_at
    
    LISTINGS_FILES }o--|| DIRECTUS_FILES : references
    
    CATEGORIES ||--o{ CATEGORIES : has_children
    CATEGORIES ||--o{ CATEGORIES_TRANSLATIONS : has
    
    CONVERSATIONS ||--o{ MESSAGES : contains
    
    LISTINGS {
        uuid id PK
        string status
        string title
        text description
        decimal price
        string currency
        string condition
        datetime expires_at
        string monero_address
    }
    
    CATEGORIES {
        uuid id PK
        string name
        string slug
        string icon
        uuid parent FK
    }
    
    CONVERSATIONS {
        uuid id PK
        uuid listing_id
        string participant_hash_1
        string participant_hash_2
        text public_key_1
        text public_key_2
        string status
    }
    
    MESSAGES {
        uuid id PK
        uuid conversation FK
        string sender_hash
        text content_encrypted
        string nonce
        string type
    }
    
    FAVORITES {
        uuid id PK
        uuid user FK
        uuid listing FK
    }
    
    REPORTS {
        uuid id PK
        uuid reporter FK
        uuid listing FK
        string reason
        string status
    }
    
    LOCATIONS {
        uuid id PK
        string name
        string postal_code
        string country
    }

Privacy-Hinweis: Conversations und Messages haben keine direkten User-Referenzen - nur Hashes!


1. Data Models (Collections)

1.1 listings (Anzeigen)

Die Haupt-Collection für alle Kleinanzeigen.

Feld Typ Interface Einstellungen
id UUID Primary Key, auto-generated
status String Dropdown draft, published, sold, expired, deleted
sort Integer Input Für manuelle Sortierung
user_created User (M2O) User Auto, Read-only
date_created DateTime DateTime Auto, Read-only
user_updated User (M2O) User Auto
date_updated DateTime DateTime Auto
title String Input Required, max 100 chars
slug String Input Unique, auto-generated via Flow
description Text WYSIWYG Required
price Decimal Input Required, precision 10, scale 2
currency String Dropdown XMR, EUR, CHF, USD, GBP, JPY (Default: XMR)
price_mode String Dropdown fiat, xmr (Default: fiat)
price_type String Dropdown fixed, negotiable, free, on_request
category Categories (M2O) Many-to-One Required
condition String Dropdown new, like_new, good, fair, poor
images Files (M2M) Files Junction: listings_files
location Locations (M2O) Many-to-One Optional
shipping Boolean Toggle Versand möglich?
shipping_cost Decimal Input Optional
views Integer Input Default: 0, Read-only
expires_at DateTime DateTime Auto-set via Flow, 30 Tage
monero_address String Input Für Direktzahlung

Erstellen in Directus Admin:

Settings > Data Model > + Create Collection
Name: listings
Primary Key: UUID (auto-generated)

1.2 categories (Kategorien)

Hierarchische Kategorien mit Übersetzungen.

Feld Typ Interface Einstellungen
id UUID Primary Key
status String Dropdown draft, published
sort Integer Input Für Sortierung
name String Input Required (Fallback-Name)
slug String Input Unique
icon String Input Icon-Name (z.B. laptop, car)
parent Categories (M2O) Many-to-One Self-referencing
translations Translations Translations Junction: categories_translations

Translations Fields:

Feld Typ
languages_code String (de, en, fr)
name String
description Text

1.3 conversations (Konversationen) - Metadaten-verschlüsselt

E2E-verschlüsselter Chat mit Zero-Knowledge Metadaten - Server weiß nicht, wer mit wem chattet.

Feld Typ Interface Einstellungen
id UUID Primary Key
date_created DateTime DateTime Auto
date_updated DateTime DateTime Auto
listing_id UUID Input Listing-Referenz (kein FK für Privacy)
participant_hash_1 String Input SHA256(user_uuid + conversation_secret)
participant_hash_2 String Input SHA256(user_uuid + conversation_secret)
public_key_1 Text Textarea X25519 Public Key für E2E
public_key_2 Text Textarea X25519 Public Key für E2E
status String Dropdown active, archived

Privacy-Konzept:

┌─────────────────────────────────────────────────────────────────┐
│  1. Käufer startet Chat zu Listing                              │
│     → generiert conversation_secret (random 32 bytes)           │
│     → berechnet participant_hash = SHA256(own_uuid + secret)    │
│     → sendet public_key für E2E-Encryption                      │
│                                                                 │
│  2. Verkäufer erhält Notification (via Listing-Polling)         │
│     → berechnet eigenen participant_hash mit gleichem secret    │
│     → sendet eigenen public_key                                 │
│                                                                 │
│  3. Server sieht nur:                                           │
│     - Zwei Hashes (nicht welche User)                           │
│     - Verschlüsselte Nachrichten                                │
│     - Kein buyer/seller Bezug!                                  │
└─────────────────────────────────────────────────────────────────┘

1.4 messages (Nachrichten) - E2E-verschlüsselt

Feld Typ Interface Einstellungen
id UUID Primary Key
date_created DateTime DateTime Auto
conversation Conversations (M2O) Many-to-One Required
sender_hash String Input SHA256(user_uuid + conversation_secret)
content_encrypted Text Textarea XChaCha20-Poly1305 verschlüsselt
nonce String Input Unique nonce für Entschlüsselung
type String Dropdown text, offer, system

Keine Klartext-Inhalte auf dem Server!


1.5 favorites (Favoriten/Merkliste)

Feld Typ Interface Einstellungen
id UUID Primary Key
date_created DateTime DateTime Auto
user User (M2O) User Auto (user_created)
listing Listings (M2O) Many-to-One Required

Duplikat-Prüfung: Frontend-seitig vor dem Erstellen prüfen, ob bereits favorisiert.


1.6 reports (Meldungen)

Feld Typ Interface Einstellungen
id UUID Primary Key
date_created DateTime DateTime Auto
reporter directus_users (M2O) Many-to-One Auto (user_created)
listing listings (M2O) Many-to-One Optional
reported_user directus_users (M2O) Many-to-One Optional
reason String Dropdown spam, fraud, inappropriate, illegal, other
details Text Textarea
status String Dropdown pending, reviewed, resolved, dismissed
admin_notes Text Textarea Nur für Admins sichtbar
resolved_by User (M2O) User
resolved_at DateTime DateTime

1.7 locations (Orte)

Feld Typ Interface Einstellungen
id UUID Primary Key
name String Input Stadt/Ort
postal_code String Input PLZ
region String Input Bundesland/Kanton
country String Dropdown Default: DE
latitude Float Input Optional, für Kartenansicht
longitude Float Input Optional

1.8 listings_files (Junction Table)

Verknüpfung Listings ↔ Dateien (für mehrere Bilder pro Anzeige).

Feld Typ
id Integer (auto)
listings_id Listings (M2O)
directus_files_id Files (M2O)
sort Integer

2. User Roles

2.1 Rollen-Übersicht

Rolle Beschreibung App Access Admin Access
Administrator Vollzugriff
Moderator Content-Moderation (eingeschränkt)
User Registrierte Nutzer
Public Nicht angemeldet

2.2 Rollen erstellen

Settings > Access Control > Roles > + Create Role

Administrator

Name: Administrator
Description: Vollzugriff auf alle Funktionen
Icon: shield
Admin Access: ON
App Access: ON

Moderator

Name: Moderator  
Description: Kann Inhalte moderieren und Reports bearbeiten
Icon: eye
Admin Access: ON (mit eingeschränkten Permissions)
App Access: ON

User

Name: User
Description: Registrierte Benutzer
Icon: account_circle
Admin Access: OFF
App Access: ON

3. Access Policies

Konfiguration unter: Settings > Access Control > [Rolle] > [Collection]


3.1 Public (Nicht angemeldet)

Pfad: Settings > Access Control > Public

listings

Aktion Erlaubt Filter
Read Custom status equals published
Create
Update
Delete

Field Permissions (Read): id, title, slug, description, price, currency, price_mode, price_type, category, condition, images, location, shipping, date_created, views

categories

Aktion Erlaubt Filter
Read Custom status equals published

Field Permissions: Alle Felder

locations

Aktion Erlaubt Filter
Read All

directus_files

Aktion Erlaubt Filter
Read All

3.2 User Role Permissions

Pfad: Settings > Access Control > User

listings

Aktion Erlaubt Filter
Create All
Read Custom status equals published OR user_created equals $CURRENT_USER
Update Custom user_created equals $CURRENT_USER
Delete Custom user_created equals $CURRENT_USER AND status in draft, expired

conversations

Aktion Erlaubt Filter
Create All
Read All
Update All

Privacy-Hinweis: Serverseitiges Filtern nicht möglich (Zero-Knowledge). Das Frontend:

  1. Lädt alle Conversations für ein Listing (listing_id Filter)
  2. Prüft client-seitig, ob eigener Hash in participant_hash_1 oder participant_hash_2 ist
  3. Zeigt nur passende Conversations an

messages

Aktion Erlaubt Filter
Create All
Read All

Hinweis: Inhalte sind E2E-verschlüsselt, daher keine serverseitige Filterung nötig.

favorites

Aktion Erlaubt Filter
Create All
Read Custom user equals $CURRENT_USER
Delete Custom user equals $CURRENT_USER

reports

Aktion Erlaubt Filter
Create All
Read
Update

directus_files

Aktion Erlaubt Filter
Create All
Read All
Update Custom uploaded_by equals $CURRENT_USER
Delete Custom uploaded_by equals $CURRENT_USER

directus_users (eigenes Profil)

Aktion Erlaubt Filter
Read Custom id equals $CURRENT_USER
Update Custom id equals $CURRENT_USER

Field Permissions: id, first_name, avatar, status


3.3 Moderator Role Permissions

Pfad: Settings > Access Control > Moderator

Alle User-Permissions plus:

listings

Aktion Erlaubt Filter
Read All
Update All

Field Permissions (Update): nur status, admin_notes

reports

Aktion Erlaubt Filter
Read All
Update All

Field Permissions (Update): status, admin_notes, resolved_by, resolved_at

directus_users (andere User sehen)

Aktion Erlaubt Filter
Read All

Field Permissions (Read): nur id, first_name, avatar, status


4. Flows (Automatisierungen)

4.1 Auto-Slug für Listings

Trigger: items.create auf listings

Flow-Aufbau:

  1. Trigger: items.create auf listings
  2. Operation 1: Run Script (Slug generieren)
  3. Operation 2: Update Data (Slug ins Feld schreiben)
// Operation 1: Run Script
module.exports = async function(data) {
    const title = data.title;
    const slug = title
        .toLowerCase()
        .replace(/[äöüß]/g, match => ({ä:'ae',ö:'oe',ü:'ue',ß:'ss'}[match]))
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/(^-|-$)/g, '');
    
    // Timestamp für Eindeutigkeit
    return { slug: `${slug}-${Date.now().toString(36)}` };
}
// Operation 2: Update Data
Collection: listings
ID: {{$trigger.key}}
Payload: { "slug": "{{$last.slug}}" }

Ergebnis: Title "iPhone 15 Pro!" → Slug iphone-15-pro-m1abc2def


4.2 Auto-Set Expiry Date

Setzt automatisch ein Ablaufdatum 30 Tage in der Zukunft.

Flow-Aufbau:

  1. Trigger: items.create auf listings
  2. Operation 1: Run Script (Datum berechnen)
  3. Operation 2: Update Data (Datum speichern)
// Operation 1: Run Script
module.exports = async function(data) {
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 30);
    return { expires_at: expiresAt.toISOString() };
}
// Operation 2: Update Data
Collection: listings
ID: {{$trigger.key}}
Payload: { "expires_at": "{{$last.expires_at}}" }

Ergebnis: Listing am 01.01. erstellt → expires_at = 31.01.


4.3 Increment View Counter

Erhöht den View-Counter bei jedem Aufruf einer Listing-Detailseite.

Flow-Aufbau:

  1. Trigger: Webhook (vom Frontend aufgerufen)
  2. Operation 1: Run Script (Counter erhöhen)
// Trigger: Webhook
Name: increment-views
Method: POST
URL: /flows/trigger/increment-views
// Operation 1: Run Script
module.exports = async function(data, { database }) {
    if (!data.listing_id) return;
    
    await database('listings')
        .where('id', data.listing_id)
        .increment('views', 1);
    
    return { success: true };
}

Frontend-Aufruf:

fetch('/flows/trigger/increment-views', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ listing_id: 'uuid-here' })
});

4.4 Auto-Expire Listings (Scheduled)

Markiert abgelaufene Listings automatisch als expired.

Flow-Aufbau:

  1. Trigger: Schedule (Cron)
  2. Operation 1: Run Script (Abgelaufene updaten)
// Trigger: Schedule
Type: Schedule (Cron)
Cron: 0 0 * * *    (täglich um 00:00 UTC)
// Operation 1: Run Script
module.exports = async function(data, { database }) {
    const now = new Date().toISOString();
    
    const updated = await database('listings')
        .where('expires_at', '<', now)
        .where('status', 'published')
        .update({ status: 'expired' });
    
    return { expired_count: updated };
}

Ergebnis: Alle Listings mit expires_at in der Vergangenheit werden auf status: expired gesetzt.


5. Einstellungen

5.1 Project Settings

Settings > Project Settings

Project Name: dgray.io
Project URL: https://dgray.io
Project Color: #555555

5.2 CORS Settings

Option 1: docker-compose.yml

services:
  directus:
    image: directus/directus:latest
    environment:
      CORS_ENABLED: "true"
      CORS_ORIGIN: "https://dgray.io,https://www.dgray.io,http://localhost:8080"
      CORS_METHODS: "GET,POST,PATCH,DELETE"
      CORS_ALLOWED_HEADERS: "Content-Type,Authorization"
      CORS_CREDENTIALS: "true"

Option 2: .env Datei + Docker Compose

# docker-compose.yml
services:
  directus:
    env_file: .env
# .env
CORS_ENABLED=true
CORS_ORIGIN=https://dgray.io,https://www.dgray.io,http://localhost:8080
CORS_METHODS=GET,POST,PATCH,DELETE
CORS_ALLOWED_HEADERS=Content-Type,Authorization
CORS_CREDENTIALS=true

5.3 Auth Settings

Konfiguration wie CORS via docker-compose.yml oder .env Datei:

# docker-compose.yml
environment:
  ACCESS_TOKEN_TTL: "15m"
  REFRESH_TOKEN_TTL: "7d"
  AUTH_PASSWORD_POLICY: "/^.{36,}$/"   # UUID = 36 chars
  PUBLIC_REGISTRATION: "true"

Oder in .env:

ACCESS_TOKEN_TTL=15m
REFRESH_TOKEN_TTL=7d
AUTH_PASSWORD_POLICY=/^.{36,}$/
PUBLIC_REGISTRATION=true

5.4 File Storage

Konfiguration via docker-compose.yml oder .env Datei:

Option 1: Lokal (einfach)

# docker-compose.yml
environment:
  STORAGE_LOCATIONS: "local"
  STORAGE_LOCAL_ROOT: "./uploads"
volumes:
  - ./uploads:/directus/uploads

Option 2: Cloudflare R2 / S3 (empfohlen für Produktion)

# docker-compose.yml
environment:
  STORAGE_LOCATIONS: "s3"
  STORAGE_S3_DRIVER: "s3"
  STORAGE_S3_KEY: "your-access-key"
  STORAGE_S3_SECRET: "your-secret-key"
  STORAGE_S3_BUCKET: "dgray-files"
  STORAGE_S3_REGION: "auto"
  STORAGE_S3_ENDPOINT: "https://xxx.r2.cloudflarestorage.com"

5.5 Rate Limiting

Konfiguration via docker-compose.yml oder .env Datei:

# docker-compose.yml
environment:
  RATE_LIMITER_ENABLED: "true"
  RATE_LIMITER_STORE: "memory"      # oder "redis" für Multi-Instance
  RATE_LIMITER_POINTS: "100"        # Max Requests
  RATE_LIMITER_DURATION: "60"       # Pro Minute

Oder in .env:

RATE_LIMITER_ENABLED=true
RATE_LIMITER_STORE=memory
RATE_LIMITER_POINTS=100
RATE_LIMITER_DURATION=60

Hinweis: Keine E-Mail-Konfiguration nötig - dgray.io nutzt keine E-Mails (Privacy by Design).

5.6 Währungsumrechnung & Preismodus

Implementiert im Frontend-Service: js/services/currency.js

Features:

  • Kraken API für Echtzeit-Kurse
  • 5 Minuten Client-Cache
  • Unterstützte Währungen: XMR, EUR, CHF, USD, GBP, JPY
  • Zwei Preismodi: fiat (Fiat-fix) und xmr (XMR-fix)

Nutzung:

import { getXmrRates, formatPrice } from './services/currency.js';

// Kurse laden
const rates = await getXmrRates();

// Preis formatieren
const listing = { price: 100, currency: 'EUR', price_mode: 'fiat' };
const display = formatPrice(listing, rates);
// → { primary: "€ 100,00", secondary: "≈ 0.6667 XMR", xmrAmount: 0.6667 }

Preismodi:

Modus price_mode Verhalten
Fiat-fix fiat Preis bleibt z.B. 100 EUR, XMR-Äquivalent ändert sich mit Kurs
XMR-fix xmr Preis bleibt z.B. 0.5 XMR, Fiat-Äquivalent ändert sich mit Kurs

Beispiele (bei 1 XMR = 150 EUR):

Listing Anzeige
100 EUR, mode=fiat € 100,00 ≈ 0.6667 XMR
0.5 XMR, mode=xmr 0.5000 XMR ≈ € 75,00

6. Frontend-Integration

6.1 Service einbinden

import { directus, DirectusError } from './services/directus.js';

// Login
try {
    await directus.login('user@example.com', 'password');
    console.log('Logged in!');
} catch (error) {
    if (error instanceof DirectusError) {
        console.error('Login failed:', error.message);
    }
}

// Listings laden
const { items, meta } = await directus.getListings({
    limit: 20,
    page: 1
});

// Listing erstellen
const newListing = await directus.createListing({
    title: 'iPhone 15 Pro',
    description: 'Neuwertig, OVP',
    price: 0.5,
    currency: 'XMR',
    category: 'electronics-id',
    condition: 'like_new'
});

6.2 Bilder anzeigen

// Thumbnail URL
const thumbUrl = directus.getThumbnailUrl(fileId, 300);

// Optimiertes Bild
const imageUrl = directus.getFileUrl(fileId, {
    width: 800,
    height: 600,
    fit: 'cover',
    quality: 80,
    format: 'webp'
});

6.3 Auth State prüfen

if (directus.isAuthenticated()) {
    const user = await directus.getCurrentUser();
    console.log('Logged in as:', user.email);
} else {
    // Redirect to login
}

7. Initiale Daten

7.1 Kategorien (Beispiel)

[
  { "name": "Elektronik", "slug": "elektronik", "icon": "devices" },
  { "name": "Fahrzeuge", "slug": "fahrzeuge", "icon": "directions_car" },
  { "name": "Immobilien", "slug": "immobilien", "icon": "home" },
  { "name": "Mode & Accessoires", "slug": "mode", "icon": "checkroom" },
  { "name": "Haus & Garten", "slug": "haus-garten", "icon": "yard" },
  { "name": "Freizeit & Hobby", "slug": "freizeit", "icon": "sports_esports" },
  { "name": "Jobs", "slug": "jobs", "icon": "work" },
  { "name": "Dienstleistungen", "slug": "dienstleistungen", "icon": "handyman" },
  { "name": "Sonstiges", "slug": "sonstiges", "icon": "more_horiz" }
]

7.2 Sub-Kategorien Elektronik

[
  { "name": "Smartphones", "slug": "smartphones", "parent": "elektronik-id" },
  { "name": "Computer & Laptops", "slug": "computer", "parent": "elektronik-id" },
  { "name": "TV & Audio", "slug": "tv-audio", "parent": "elektronik-id" },
  { "name": "Foto & Video", "slug": "foto-video", "parent": "elektronik-id" },
  { "name": "Gaming", "slug": "gaming", "parent": "elektronik-id" },
  { "name": "Zubehör", "slug": "zubehoer", "parent": "elektronik-id" }
]

8. Checkliste

  • Collections erstellen (listings, categories, etc.)
  • Felder konfigurieren (Typen, Validierung)
  • Rollen anlegen (Admin, Moderator, User)
  • Access Policies für jede Rolle setzen
  • Public Access für Listings/Categories aktivieren
  • Flows für Auto-Slug, Expiry, etc. erstellen
  • CORS für Frontend-Domain konfigurieren
  • Email-Transport einrichten
  • Kategorien importieren
  • Test-User erstellen
  • Frontend mit directus.js Service verbinden

9. Anonyme Authentifizierung (UUID-basiert)

Für maximale Privatsphäre nutzt dgray.io ein UUID-basiertes Login-System ohne echte E-Mail-Adressen.

9.1 Konzept

┌─────────────────────────────────────────────────────────────┐
│  User klickt "Account erstellen"                            │
│                    ↓                                        │
│  Client generiert UUID v4:                                  │
│  f47ac10b-58cc-4372-a567-0e02b2c3d479                       │
│                    ↓                                        │
│  Directus erhält:                                           │
│  • E-Mail:    f47ac10b-58cc-4372-a567-0e02b2c3d479@dgray.io │
│  • Passwort:  f47ac10b-58cc-4372-a567-0e02b2c3d479          │
│                    ↓                                        │
│  User speichert UUID → fertig                               │
└─────────────────────────────────────────────────────────────┘

9.2 Vorteile

Aspekt Beschreibung
Privatsphäre Keine echten E-Mail-Adressen, keine persönlichen Daten
Einfachheit User merkt sich nur eine UUID
Sicherheit 122 bit Entropie, Argon2-Hashing durch Directus
Anonymität Kein Bezug zur Identität möglich

9.3 Tradeoffs

Pro Contra
Keine echten Daten nötig UUID verloren = Account verloren
Nicht rückverfolgbar Kein Passwort-Reset möglich
Simples UX (1 Secret) User muss UUID sicher aufbewahren

9.4 Frontend-Implementation

// Account erstellen
async function createAnonymousAccount() {
    const uuid = crypto.randomUUID();
    const email = `${uuid}@dgray.io`;
    const password = uuid;
    
    // Bei Directus registrieren
    await directus.register(email, password);
    
    // UUID dem User anzeigen zum Speichern
    showUuidToUser(uuid);
    
    return uuid;
}

// Login
async function login(uuid) {
    const email = `${uuid}@dgray.io`;
    await directus.login(email, uuid);
}

9.5 UX-Flow

  1. Registrierung:

    • User klickt "Account erstellen"
    • System generiert UUID, zeigt sie prominent an
    • User kopiert UUID (Button "Kopieren")
    • Hinweis: "Speichere diese ID sicher - sie ist dein einziger Zugang!"
  2. Login:

    • User gibt UUID ein (ein Feld)
    • System baut E-Mail + Passwort daraus
    • Fertig
  3. Recovery:

    • Nicht möglich (by design)
    • Optional: QR-Code zum Offline-Speichern anbieten

9.6 Directus-Konfiguration

# Public Registration erlauben
PUBLIC_REGISTRATION=true

# Keine E-Mail-Verifizierung (fake E-Mails)
AUTH_EMAIL_VERIFY=false

# Kein Passwort-Reset (nicht möglich mit fake E-Mails)
AUTH_PASSWORD_RESET=false

10. Sicherheits- & Privacy-Hinweise

Privacy by Design

Aspekt Umsetzung
Authentifizierung UUID-basiert, keine echten E-Mails
Kommunikation Nur verschlüsselter Chat, keine E-Mail-Option
Chat-Metadaten Zero-Knowledge (participant_hash statt user_id)
Chat-Inhalte E2E-verschlüsselt (XChaCha20-Poly1305)
Währungen XMR (primär) + EUR, CHF, USD, GBP, JPY
Kontakt Ausschließlich über internen Chat

Technische Sicherheit

  1. Monero-Adressen werden nur authentifizierten Nutzern angezeigt
  2. Rate Limiting für alle API-Endpunkte aktivieren
  3. Bilder-Upload auf max. 10MB und erlaubte Typen beschränken
  4. XSS-Schutz für WYSIWYG-Felder in Directus aktivieren
  5. UUID-Warnung bei Registrierung: User muss UUID sicher speichern
  6. Kein Passwort-Reset möglich (by design)
  7. Keine Server-Logs mit User-Bezug speichern

Was der Server NICHT weiß

  • Echte Identität der User (nur UUIDs)
  • Wer mit wem chattet (nur Hashes)
  • Chat-Inhalte (nur verschlüsselt)
  • E-Mail-Adressen (existieren nicht)