Onboarding & Authentifizierung
Setup-Code einlösen — POST /api/v1/setup/redeem
Nicht authentifiziert (IP-Rate-Limit). Tauscht einen Einmal-Setup-Code gegen Zugangsdaten ein.
Setup-Code-Format: ^(MK|MD)-[A-Z2-9]{4}-[A-Z2-9]{4}$
Alphabet 23456789ABCDEFGHJKMNPQRSTUVWXYZ (31 Zeichen, ohne 0/1/I/L/O — geringes Verwechslungsrisiko).
Das Präfix ist frei wählbar (MK = Prod, MD = Dev in der Referenzimplementierung). Der Server speichert nur den SHA-256-Hash des Codes (code_hash), niemals den Klartext.
Request:
{ "code": "MK-7X3F-9K2P", "hostname": "PRACTICE-PC-01" }
hostname ist optional (druckbares ASCII, ≤255).
Response 200:
{
"success": true,
"data": {
"apiKey": "mk_live_1a2b…(64 hex)",
"host": "https://myopia.kids",
"encryptionKey": "e3f1…(64 hex)"
}
}
apiKeywird im Klartext nur hier, einmalig zurückgegeben (danach nicht mehr abrufbar).encryptionKey= per-Account-Schlüssel, der ausschließlich für das DB-Passphrase-Backup verwendet wird (siehe unten), nicht für Importdaten.
Fehler:
| Code | HTTP | Bedeutung |
|---|---|---|
INVALID_CODE | 400 | Ungültiges Format oder unbekannter Code |
INVALID_HOSTNAME | 400 | hostname kein druckbares ASCII / >255 |
ALREADY_REDEEMED | 409 | Code bereits eingelöst |
CODE_EXPIRED | 410 | Code abgelaufen (expires_at < now) |
RATE_LIMIT_EXCEEDED | 429 | >10 Versuche/h/IP (mit Retry-After) |
INVALID_REQUEST | 400 | JSON nicht parsierbar |
INTERNAL_ERROR | 500 | Serverfehler |
API-Key & Authentifizierung
- Format:
mk_live_+ 64 Hex-Zeichen (32 Zufallsbytes). Das Präfix ist kosmetisch — für das eigene Produkt frei wählbar (z.B.mc_live_). - Serverseitige Speicherung: Nur der SHA-256-Hash (
key_hash, hex) plus ein gekürztes Anzeigepräfix (key_prefix). Der Klartext-Key wird nie gespeichert. - Eingehende Requests validieren: Bearer-Token extrahieren →
SHA-256berechnen →SELECT … FROM api_keys WHERE key_hash = $1 AND revoked_at IS NULL. Kein Treffer →401 INVALID_API_KEY. Ein Treffer liefert dieaccount_id(Tenant) für den weiteren Request. - Nebeneffekt: Bei jedem authentifizierten Request
last_used_at(und die Telemetrie-Header) fire-and-forget aktualisieren — darf die Response nicht blockieren.
Verschlüsselungsschlüssel abrufen — GET /api/v1/config
Authentifiziert. Gibt denselben encryptionKey erneut zurück (z.B. nach einer Neuinstallation) sowie den aktiven Update-Kanal.
Response 200:
{ "success": true, "data": { "encryptionKey": "e3f1…", "updateChannel": "stable" } }
updateChannel = stable | canary (aus dem canary_consent-Flag des Keys; der Header X-DATEYE-Canary-Consent überschreibt ihn für die aktuelle Response).
DB-Passphrase-Backup (mit dem encryptionKey)
Der DATEYE-Client verschlüsselt lokale Patientendaten in seiner eigenen DB mit einer Passphrase. Zur Wiederherstellung nach Geräteverlust kann er diese Passphrase clientseitig verschlüsselt beim Server hinterlegen. Der Server speichert nur einen undurchsichtigen Blob und kann ihn nicht entschlüsseln.
Verschlüsselungsformat (AES-256-GCM):
iv_hex : authTag_hex : ciphertext_hex
- Algorithmus
aes-256-gcm, Schlüssel = die 32 Bytes ausencryptionKey(hex-dekodiert) iv= 16 Zufallsbytes,authTag= 16 Bytes (GCM), alle Teile hex, getrennt durch:- Dies ist exakt das Format, das der Client selbst erzeugt und erwartet.
POST /api/v1/setup/db-key — Blob speichern (einmalig)
{ "encryptedDbKey": "iv_hex:authTag_hex:ciphertext_hex" }
Wird nur gespeichert, wenn noch kein Blob existiert (… WHERE dateye_db_key IS NULL). Ein zweiter Aufruf → 409 KEY_ALREADY_SET (Signal an den Client: zuerst GET und lokal vergleichen). Response { "success": true }.
GET /api/v1/setup/db-key — Blob abrufen
{ "success": true, "data": { "encryptedDbKey": "iv_hex:authTag_hex:ciphertext_hex" } }
Kein Blob gespeichert → 404 KEY_NOT_FOUND. Der Client entschlüsselt lokal mit encryptionKey.
Historisch existiert ein zweiter Pfad
/api/v1/config/db-key, bei dem der Server ver-/entschlüsselt und überschreibbar speichert. Für die OEM-Integration ist der/setup/db-key-Pfad (undurchsichtiger Blob, keine serverseitige Entschlüsselung) der autoritative, datenschutzkonforme Weg./config/db-keykann weggelassen werden.