Skip to content

Verbinden und Anmelden ​

Mit dem nomos system Controller verbinden und anmelden, über Socket.io und HTTP

Jeder Aufruf an den Controller braucht eine Anmeldung, über Socket.io wie über HTTP. Diese Seite beschreibt, wie die Verbindung zustande kommt, wie lange eine Anmeldung gilt, wie Sie HTTPS sauber nutzen und welche Rechte Ihr Programm bekommt.

Adresse und Ports ​

HTTPHTTPS
Port80443
Socket.iohttp://<controller-ip>/api/v1, Pfad /socket.io-v4https://<controller-ip>/api/v1, Pfad /socket.io-v4
HTTP-APIhttp://<controller-ip>/api/v1/<function>https://<controller-ip>/api/v1/<function>

/api/v1 ist bei Socket.io der Namespace, nicht Teil des Pfads. Verwenden Sie socket.io-client in Version 4 mit dem Pfad /socket.io-v4. Der Pfad /socket.io bedient ältere Clients mit Socket.io 2 und ist für neue Anbindungen nicht gedacht.

Socket.io ​

Ablauf ​

  1. Ihr Client verbindet sich mit dem Namespace /api/v1.
  2. Der Controller sendet das Event stateChange mit {part: 'app', state: 'User Authorization needed'}.
  3. Erst jetzt senden Sie auth mit Benutzername und Kennwort.
  4. Nach erfolgreicher Anmeldung senden Sie init. Die Antwort ist true.
  5. Danach stehen alle Aufrufe zur Verfügung, die Ihr Benutzer verwenden darf.
javascript
const { io } = require('socket.io-client');

const socket = io('http://<controller-ip>/api/v1', { path: '/socket.io-v4' });

socket.on('stateChange', async (state) => {
    if (state.part !== 'app' || state.state !== 'User Authorization needed') return;

    const auth = await socket.timeout(15000).emitWithAck('auth', { username: 'USERNAME', password: 'PASSWORD' });
    if (auth.errorCode) return console.error(auth.errorCode, auth.errorText);

    await socket.timeout(15000).emitWithAck('init', { language: 'en', appName: 'my-integration' });
    // ready: call getAllComponents, componentUpdate, ...
});

WARNING

Warten Sie auf stateChange, bevor Sie auth senden. Der Controller nimmt auth erst an, wenn er die Verbindung geprüft hat. Ein auth direkt nach connect geht verloren, und Ihr Client wartet vergeblich auf eine Antwort. Dasselbe gilt für jeden anderen Aufruf vor der Anmeldung.

Bricht die Verbindung ab und baut der Client sie neu auf, fragt der Controller erneut mit stateChange nach einer Anmeldung. Der Code oben meldet sich dann von selbst wieder an. Lesen Sie danach den aktuellen Zustand neu ein, denn Änderungen während der Unterbrechung haben Sie nicht mitbekommen.

init nimmt Angaben zu Ihrem Client entgegen, etwa language für die Sprache der Fehlertexte und appName. Alle Felder stehen auf apidocs.

Mit gespeicherter Sitzung ​

Statt Benutzername und Kennwort kann Ihr Client eine Sitzungs-ID mitgeben, als Query-Parameter x-nomos-sid:

javascript
const socket = io('http://<controller-ip>/api/v1', {
    path: '/socket.io-v4',
    query: { 'x-nomos-sid': '<session-id>' },
});

socket.on('connect', async () => {
    await socket.timeout(15000).emitWithAck('init', { language: 'en', appName: 'my-integration' });
    // ready
});

Ist die Sitzung gültig, entfällt auth, und Sie senden gleich init. Ist sie abgelaufen, sendet der Controller wie oben stateChange mit User Authorization needed, und Ihr Client meldet sich mit auth an.

Eine Sitzungs-ID erhalten Sie über HTTP mit POST /api/v1/auth (siehe HTTP-API). Die Antwort auf auth über Socket.io enthält zwar das Feld sessionID, doch ein Client ohne Cookies, etwa ein Node.js-Programm, bekommt dort null.

HTTP ​

Über HTTP schicken Sie die Anmeldung mit jeder Anfrage mit, entweder als Header auth-username und auth-password oder als Sitzungs-ID im Header x-nomos-sid:

bash
curl -X POST -H "x-nomos-sid: <session-id>" http://<controller-ip>/api/v1/getVersion

Einzelheiten stehen auf der Seite HTTP-API.

Sitzungen ​

  • Eine Sitzung gilt 24 Stunden ab der letzten Nutzung. Melden Sie sich über HTTP mit dem Header auth-persistent: true an, gilt sie zwei Jahre ab der letzten Nutzung.
  • Jede Nutzung verlängert die Sitzung.
  • logout beendet die Sitzung sofort.

Eine dauerhafte Sitzung ist so viel wert wie das Kennwort. Speichern Sie die Sitzungs-ID entsprechend geschützt.

HTTPS und Zertifikat ​

Auf Port 443 verwendet der Controller ein selbstsigniertes Zertifikat. Es enthält keinen Hostnamen und keine IP-Adresse. Das hat zwei Folgen:

  • Ohne weitere Angaben lehnt jeder Client das Zertifikat ab, weil keine bekannte Zertifizierungsstelle es ausgestellt hat.
  • Auch wenn Sie dem Zertifikat vertrauen, scheitert die übliche Prüfung des Hostnamens, weil das Zertifikat keinen Namen nennt.

Schalten Sie die Prüfung deshalb nicht ab (curl -k, Node.js rejectUnauthorized: false). Damit nähme Ihr Client jedes beliebige Zertifikat an. Vertrauen Sie stattdessen genau diesem Zertifikat.

Zertifikat abrufen ​

bash
openssl s_client -connect <controller-ip>:443 </dev/null 2>/dev/null \
  | openssl x509 -out nomos-controller.pem
openssl x509 -in nomos-controller.pem -noout -fingerprint -sha256

Rufen Sie das Zertifikat in einem Netz ab, dem Sie vertrauen, und bewahren Sie die Datei bei Ihrem Programm auf.

Node.js ​

Node.js prüft die Kette gegen das abgerufene Zertifikat (ca). Anstelle des Hostnamens vergleicht checkServerIdentity den Fingerabdruck des Zertifikats:

javascript
const fs = require('fs');
const crypto = require('crypto');
const https = require('https');
const { io } = require('socket.io-client');

const ca = fs.readFileSync('nomos-controller.pem');
const fingerprint = new crypto.X509Certificate(ca).fingerprint256;

// Trust exactly this certificate. It names no host, so the
// certificate itself is compared instead of the host name.
const tlsOptions = {
    ca,
    checkServerIdentity: (host, cert) =>
        cert.fingerprint256 === fingerprint ? undefined : new Error(`unexpected certificate from ${host}`),
};

// HTTP API over HTTPS
https.request({
    host: '<controller-ip>', port: 443, path: '/api/v1/getVersion', method: 'POST',
    headers: { 'x-nomos-sid': '<session-id>' },
    ...tlsOptions,
}, (res) => res.pipe(process.stdout)).end();

// Socket.io over HTTPS: only the websocket transport passes checkServerIdentity on
const socket = io('https://<controller-ip>/api/v1', {
    path: '/socket.io-v4',
    transports: ['websocket'],
    ...tlsOptions,
});

Bei Socket.io ist transports: ['websocket'] nötig: Der Polling-Transport von socket.io-client gibt checkServerIdentity nicht weiter, die Verbindung scheitert dann mit xhr poll error. Legt der Controller ein anderes Zertifikat vor, bricht die Verbindung ab (websocket error, bei HTTPS self-signed certificate).

curl ​

curl --cacert nomos-controller.pem reicht hier nicht. curl prüft immer auch den Hostnamen und bricht ab (Fehler 60), weil das Zertifikat keinen nennt. Eine Option, nur diese Prüfung auszulassen, hat curl nicht. Nutzen Sie für curl HTTP in einem Netz, dem Sie vertrauen, oder für HTTPS ein Programm wie oben.

Rechte ​

Ihr Programm hat genau die Rechte des Benutzers, mit dem es sich anmeldet. Benutzer und ihre Berechtigungen legen Sie in der Konfigurationsoberfläche an, siehe Benutzer im Integratoren-Handbuch.

Admin und normaler Benutzer ​

  • Gruppe „admin“: darf jeden Aufruf verwenden.
  • Alle anderen Benutzer: dürfen nur eine feste Auswahl an Aufrufen verwenden. Dazu gehören das Lesen von Komponenten, Räumen, Bereichen und Szenen, das Schalten mit componentUpdate, das Ausführen von Szenen mit executeScene sowie Anmelden, Abmelden und das eigene Kennwort. Alles, was den Controller konfiguriert, braucht einen Admin.

Ruft ein normaler Benutzer etwas außerhalb dieser Auswahl auf, antwortet der Controller mit:

json
{"errorCode": 117, "errorText": "Not allowed"}

Über HTTP kommt diese Antwort mit dem Status 400.

Räume und Szenen ​

Die Berechtigungen aus der Konfigurationsoberfläche gelten auch über die API:

BerechtigungWirkung in der API
Raum „Aus“Komponenten des Raums fehlen in den Listen, Änderungen ihrer Werte werden nicht gemeldet, componentUpdate liefert für sie false.
Raum „Nur Anzeige“Komponenten des Raums sind lesbar, componentUpdate liefert für sie false.
Szene „Aus“Die Szene fehlt in getScenes, executeScene antwortet mit Fehler 117.

Empfehlung ​

Legen Sie für jedes Fremdsystem einen eigenen Benutzer an, in der Gruppe „user“ und nur mit den Räumen und Szenen, die es braucht. Dann trifft eine Sperre nach Fehlversuchen nur dieses Konto, Sie können die Anbindung einzeln abschalten, und ein abgegriffenes Kennwort öffnet nicht die ganze Anlage. Einen Admin braucht eine Anbindung nur, wenn sie den Controller konfigurieren soll.

Sperre nach Fehlversuchen ​

Der Controller bremst das Erraten von Kennwörtern, über Socket.io wie über HTTP:

  • Gezählt werden fehlgeschlagene Anmeldungen, getrennt nach Benutzername und nach Absenderadresse. Es gilt die strengere Sperre.
  • Fünf Fehlversuche bleiben folgenlos. Ab dem sechsten sperrt der Controller weitere Anmeldungen: zuerst für 1 Sekunde, nach jedem weiteren Fehlversuch doppelt so lange, höchstens 15 Minuten.
  • Eine erfolgreiche Anmeldung setzt den Zähler zurück. Nach 15 Minuten ohne Fehlversuch beginnt er von vorn.

Während einer Sperre antwortet der Controller mit Fehler 109. retryAfter nennt die Wartezeit in Sekunden:

json
{"errorCode": 109, "errorText": "Too many authorization attempts", "retryAfter": 8}

Weitere Antworten beim Anmelden:

errorCodeBedeutung
106Benutzername oder Kennwort fehlt, oder über HTTP ist die Sitzung abgelaufen
110Benutzername oder Kennwort falsch
122Benutzer ist deaktiviert
137Für diesen Benutzer ist der Fernzugriff nicht freigegeben

Ein Programm, das sich bei Fehler 110 sofort erneut anmeldet, sperrt sich und, über die Absenderadresse, alle anderen Konten von demselben Rechner. Melden Sie Fehler 110 und 122 deshalb, statt es weiter zu versuchen.

Fernzugriff ​

Ist der Fernzugriff eingeschaltet, erreicht die nomos App den Controller auch von unterwegs. Für Anfragen über diesen Weg gilt zusätzlich:

  • Der Controller verlangt neben der Anmeldung ein Fernzugriffs-Token. Dieses Token stellt er nur im lokalen Netz aus.
  • Anmelden kann sich nur, wer die Berechtigung „Fernzugriff“ hat. Ohne sie antwortet der Controller mit Fehler 137, auch bei richtigem Kennwort.
  • Fehlversuche zählen dort nur je Benutzername, nicht je Absenderadresse.
  • Einige Funktionen sind über den Fernzugriff gesperrt, darunter MCP und Node-RED.

Betreiben Sie eigene Anbindungen im lokalen Netz.