Skip to content

Access control — sessions, service key, origin

How the uGo server decides who gets through, since access protection ULS-238 (uGo service 1.0.3/1.0.4, build id 1788703020348). Read from tuersteher.ts, anmeldung.ts, herkunft.ts, dienstschluessel.ts, bremse.ts and the role matrix in api/chassis/rollen.test.ts. BELEGT = taken from code and tests.

Status of this page: 06.09.2026.

The rule in one sentence

Whoever reaches the robot from the network — browser or data — needs credentials. Driving over the API works only behind a session; configuring only as technician. Requests from the device itself (kiosk shell, loopback) are unchanged. The rule is enforced on the device, by the manufacturer — not at the customer's router.

1 Origin: device or network

The origin is read from the socket (remoteAddress of the accepted connection) in server.js, never from a header — X-Forwarded-For, Host, Origin are set by the caller. BELEGT

Origin Addresses Consequence
Gerät (device) 127.0.0.0/8, ::1, ::ffff:127.x.x.x — the kiosk shell on the tablet, the service's self-test, the localproxy of the maintenance tunnel the pre-ULS-238 rules apply (section 3)
Netz (network) everything else, including the device's own LAN address everything needs a session (section 3)
no context at all started via node build/index.js instead of server.js, or vite dev counts as network. ZUGRIFF_ENTWICKLUNG=frei turns it into "device" only when no context exists — under server.js the variable is ignored. Never set it in a build.

VORSICHT A site computer whose systemd unit still starts node build/index.js treats every request as network — including its own kiosk. Start via node server.js (npm start does).

2 Sessions

Point Value Why
carrier cookie ugo_sitzung, httpOnly, sameSite: strict, secure: false, maxAge 8 h the robot speaks HTTP in its own network; secure: true would make the tablet drop the cookie silently
validity 10 minutes after the last access, extended on every access a technician page left open in a corridor would otherwise stay open all day
persistence daten/sitzungen.json, mode 0600, session marks with role and expiry only — no password; rewritten at most once a minute the server is rebuilt several times a morning; a restart must not log the tablet out
password change ends all other sessions changing the technician password is done to throw the previous holder out
roles techniker, bediener — derived from which password matched
throttle area anmeldung: 5 failures per address → 60 s, doubling per further failure, cap 1 h; plus a global counter (20 → 30 s, cap 5 min). Persisted in daten/bremse.json a six-digit password is guessed in seconds if guessing is free

POST /api/anmeldung {kennwort}200 {rolle} · 401 Kennwort stimmt nicht. · 409 Am Server ist kein Kennwort eingerichtet. · 429 Zu viele Fehlversuche. Bitte N Sekunden warten. GET /api/anmeldung{kennwortEingerichtet, angemeldet, rolle}. DELETE ends the session. BELEGT

3 What a path requires (zugangsbedarf)

Origin Condition /service/** /fahrtenbuch everything else
device no password configured at all open open open
device password configured technician session — or open when the operator is set to "frei" open
network no technician password (factory state) einrichtung einrichtung einrichtung — except the allow-list below
network technician password configured technician session (also when the operator is "frei") session

Always open from the network (allow-list, not deny-list): /anmeldung, /api/anmeldung, /einrichtung, /api/kennwort, /api/leben, /_app/**, /favicon*, /schrift/**, /gesicht/**, /urg-logo.svg. The path is decodeURIComponent-ed before comparison (the router decodes too); a path that does not decode, or contains ./.. segments, matches nothing and is therefore closed. BELEGT

/api/flotte/** is deliberately not on the list: the fleet talks UDP with its own key; the HTTP endpoints are information for humans and name serial numbers.

Answers when the requirement is not met

Requirement Page (303) API (/api/**)
einrichtung /einrichtung 401 {"fehler":"Zuerst das Servicekennwort einrichten."}
session, none present device: → /anmeldung · network: → /anmeldung?zurueck=<path+query> 401 {"fehler":"Nicht angemeldet."}
technician, but operator session /fahrtenbuch (no login loop) 403 {"fehler":"Ändern darf nur der Techniker."}

The login page accepts zurueck only as an absolute path of this server (starts with /, not //).

Factory state

Factory state = no technician password (neither in daten/kennwoerter.json nor SERVICE_KENNWORT_TECHNIKER in the environment). An operator password alone does not end it. A damaged password file counts as "technician set" and is therefore not factory state — a broken device must not open the first-password path to whoever broke it. POST /api/kennwort sets the first technician password without a session only while no password is effective; the reset with a code from the cloud ({ruecksetzcode, kennwort}) also works without a session and is throttled in its own area. BELEGT

4 The service key (x-urg-dienstschluessel)

For automatons — site computer, building service, test scripts — that cannot type a password.

Point Value
header x-urg-dienstschluessel: urg-<64 hex> (whitespace at the ends is tolerated)
creation technician page → Device → Service key; POST /api/dienstschluessel returns the clear text once; stored as SHA-256 in daten/dienstschluessel.json (0600) with erzeugt timestamp
count exactly one per robot; "renew" replaces, the old key is void everywhere immediately; DELETE /api/dienstschluessel revokes
rights the request runs as an operator session (AsyncLocalStorage, valid for that one request, never stored, never extended)
precedence a valid cookie wins; an invalid cookie does not fall back to the key (a browser with a stale cookie must see that, not silently continue with the key's rights); no cookie + valid key = operator
throttle own area dienstschluessel, per address, no global counter: a valid key is compared first (constant time) and never touches the throttle; a wrong key: 429 when the address is blocked, else 401 {"fehler":"Der Dienstschlüssel stimmt nicht."} and one failure counted

GET /api/dienstschluessel{vorhanden, erzeugt?} — never the key. POST{vorhanden, erzeugt, schluessel}. DELETE{vorhanden:false}. All three: technician only — 403 also for the key itself (a key that creates keys could never be revoked). BELEGT

curl -s -H "x-urg-dienstschluessel: urg-<64 hex>" \
     -H "content-type: application/json" \
     -d '{"aktion":"taster","kennung":"ruf-labor-2"}' \
     http://192.168.0.10:5301/api/regeln/aktion

5 Roles per route — driving yes, configuring no

The matrix from api/chassis/rollen.test.ts (20 paths × 4 roles) and the per-route guards. "Session" means operator, technician or service key. From the device with no password configured, everything is open (B7).

Chassis pass-through /api/chassis/<path>

Method + path No session (network) Operator / service key Technician
GET * 401 passes passes
POST chassis/moves, POST chassis/moves/ 401 from the network — no route guard (the stop button on the device needs no password) passes — a move is created passes
PATCH chassis/moves/<n>, PATCH chassis/moves/<n>/ as above passes — a move is cancelled passes
PATCH/POST chassis/pose 401 403 passes
PATCH/POST chassis/current-map 401 403 passes
POST mappings/, PATCH mappings/<n> 401 403 passes
POST maps/, PATCH maps/<n> 401 403 passes
POST services/localization/enable, PATCH chassis/services, PATCH chassis/auto-charge 401 403 passes
POST chassis/moves/<n>, PATCH chassis/moves, POST chassis/movesx, POST chassis/moves/../pose, PATCH chassis/moves/abc 401 403 — only the two exact drive shapes pass passes
DELETE maps/<n>, DELETE mappings/<n> 401 403 passes

chassis/pose is deliberately not on the drive list: the operator interface never sets the pose; a pose set from the network sends the robot into a wall on its next move. "Passes" for the technician means: the answer is not 401/403 — behind the guard stands the chassis, and without one the answer is 502. BELEGT

Own routes

Route Driving / operating (session incl. service key) Configuring (technician only)
POST /api/touren/lauf (start, stop, pause, fortsetzen, bestaetigen) passes
POST /api/regeln/aktion (ausloesen, taster, alarm, weiter, entfernen, alles-anhalten) passes
GET of touren, regeln, einstellungen, fahrtenbuch, medien, nachbarn, funk, flotte, sprache passes
PUT /api/touren, PUT /api/regeln, POST /api/einstellungen, POST /api/fahrwerte, POST /api/wlan, POST /api/kennwort (with session), POST /api/medien, POST /api/zonen, POST /api/kartenarchiv, POST /api/karte-aus-aufnahme, POST /api/sprache 403 passes
POST /api/wolke (anmelden with join code) 403 passes
/api/gebaeude/**all methods, GET included 403 passes
/api/dienstschluessel (GET, POST, DELETE) 403 passes
/api/servicezugang (GET, POST, DELETE) 403 passes
/api/uiot/<path> (read-only pass-through) 403 passes
GET /api/protokolle, /api/protokolle/zeilen passes (session)
GET /api/leben open — no session, no key

6 GET /api/leben — liveness

For monitoring, install scripts and the self-test of a site computer. Always open, also in factory state, also on the maintenance tunnel. Contains nothing confidential: no chassis state, no serial number, no address, no password state. BELEGT

{ "ok": true, "fassung": "1.0.4" }

fassung is UGO_FASSUNG from the service environment, null if unset. There is no other health endpoint; the state reporter pushes to the cloud and is not polled.

7 /api/servicezugang — adb and remote support with a time window

Pass-through from the technician page to the network gate on the device (SCHRANKE_BASIS, default http://127.0.0.1:5302, 3 s). Technician only — the service key must not open a remote access. Not reachable through the maintenance tunnel. BELEGT

Method Body Gate call Response
GET GET /zustand the gate's zustand.json unchanged, incl. betriebsart and servicezugang.{adb,fernwartung}.{offen,restSekunden,quelle}
POST {"was":"adb"\|"fernwartung","minuten":15\|60\|240} POST /servicezugang with the same body the gate's answer unchanged — 200 {offen:true, was, restSekunden} or its refusal
DELETE {"was":…} or empty (= everything) DELETE /servicezugang the gate's answer unchanged — 200 {offen:false[, was]}
Status Body
400 Zu öffnen ist entweder „adb" oder „fernwartung". · Die Dauer ist 15, 60 oder 240 Minuten. — checked before the gate is asked
502 Netzschranke nicht erreichbar – Zugang bleibt wie er ist. — no answer, slower than 3 s, or no JSON; nothing has changed
400 / 403 / 404 / 405 / 409 the gate's own refusals, passed through unchanged (403 Aufrufer nicht prüfbar … when the gate cannot determine the caller UID; 409 without device owner; 405/404 from a gate older than 1.1.0)

POST /neustart of the gate (device reboot) exists on the gate side and is not exposed by the uGo server in 1.0.4. OFFEN

8 What a technician has to do after the update

  1. Set the service password before the robot joins the customer network (/einrichtung from the network, or the technician page at the kiosk).
  2. Set an operator password if anybody is to see the operator interface or the logbook from the network (site-computer setup with a tablet: mandatory — from the network there is no login without a password, even with the operator set to "frei").
  3. Generate the service key and enter it into the site computer, test scripts and building services — it is shown once.
  4. Site computer: systemd unit on node server.js; monitoring on /api/leben.
  5. Everything that used to call the server without a login (POST /api/regeln/aktion, POST /api/touren/lauf, Invoke-RestMethod probes) sends the header x-urg-dienstschluessel — without it, 401.