
# Umgebungseinstellungen aktualisieren

`PATCH /v1/environment-settings` · Berechtigung `environment-settings:write`

Aktualisiert die Umgebungseinstellungen mit einem JSON Merge Patch (`application/merge-patch+json`, RFC 7396). `If-Match` ist erforderlich: Fehlt der Header, wird `428` zurückgegeben; ist die Version veraltet, lautet die Antwort `412`. Wurden die Einstellungen noch nie geschrieben, ist die Version `"0"`, und der erste Schreibvorgang legt die Zeile an. Ein Patch, der nichts ändert, schreibt nichts und behält die Version bei.

## Anfrage

| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| `If-Match` | Header | ja | Das `ETag` der Einstellungen, wie zuletzt gelesen (`"0"`, falls nie geschrieben). Fehlt der Header: `428`; veraltet: `412`. |
| `locales.enabled` | string[] | nein | Aktivierte Sprachcodes (registrierte Sprachen), 1 bis 64 Einträge, keine Duplikate. Darf nicht `null` sein. |
| `locales.default` | string | nein | Standardsprache; muss einer der Werte in `locales.enabled` sein. Darf nicht `null` sein. |
| `default_zoneinfo` | string | nein | Name der IANA-Zeitzone (Standard `UTC`). Darf nicht `null` sein. |
| `session.idle_timeout_s` | integer | nein | Leerlauf-Timeout in Sekunden, 60 bis 8640000 (Standard 259200); nicht größer als `session.absolute_timeout_s`. Darf nicht `null` sein. |
| `session.absolute_timeout_s` | integer | nein | Absolute Lebensdauer in Sekunden, 60 bis 31536000 (Standard 2592000). Darf nicht `null` sein. |
| `session.max_concurrent` | integer | nein | Gleichzeitige Sitzungen pro Benutzer, 0 bis 500; `0` bedeutet das Systemlimit (Standard). Darf nicht `null` sein. |
| `session.concurrent_mode` | string | nein | `evict_oldest` (Standard) oder `deny_new`. Darf nicht `null` sein. |
| `session.anomaly_action` | string | nein | `enforce` (Standard) oder `log`. Darf nicht `null` sein. |
| `signup.mode` | string | nein | `open` (Standard), `invite_only` oder `closed`. Darf nicht `null` sein. |
| `signup.email_domain_mode` | string | nein | `off` (Standard), `allowlist` oder `denylist`. Darf nicht `null` sein. |
| `signup.email_domains` | string[] \| null | nein | Höchstens 1000 Domains; ersetzt die gesamte Liste (RFC 7396 führt keine Arrays zusammen). `null` oder `[]` leert die Liste. Einträge werden normalisiert (Kleinbuchstaben, IDNA-A-Label), Duplikate werden entfernt und die Liste wird sortiert gespeichert. Bleibt erhalten, solange der Modus `off` ist. |
| `security_notifications.enabled` | boolean | nein | Hauptschalter (Standard `true`). Darf nicht `null` sein. |
| `security_notifications.events` | string[] \| null | nein | Teilmenge der 12 Sicherheitsereignisse (`password_changed`, `password_reset`, `email_changed`, `phone_changed`, `authenticator_added`, `authenticator_removed`, `recovery_code_used`, `recovery_codes_regenerated`, `account_locked`, `breached_password_detected`, `new_device_sign_in`, `account_recovery_started`); ersetzt die gesamte Liste und wird in kanonischer Reihenfolge zurückgegeben. `null` oder `[]` leert die Liste (kein Ereignis). |
| `account_recovery.mode` | string | nein | `self_service_waiting` (Standard), `admin_only` oder `disabled`. Darf nicht `null` sein. |
| `account_recovery.waiting_hours` | integer | nein | 24 bis 168 Stunden (Standard 72); wird in jedem Modus gespeichert. Darf nicht `null` sein. |
| `account_recovery.support_url` | string | nein | Leer oder eine `https://`-URL mit höchstens 2048 Byte, ohne Benutzerinformationen oder Fragment. Wird angezeigt, wenn die Wiederherstellung `disabled` ist. Darf nicht `null` sein. |
| `mfa.email_second_factor_enabled` | boolean | nein | Zweiter Faktor per E-Mail ein oder aus (Standard `false`). Darf nicht `null` sein. |
| `mfa.email_second_factor_acr` | string | nein | Sicherheitsniveau, das der E-Mail-Faktor erreicht: `1fa` (Standard) oder `mfa`. Darf nicht `null` sein. |
| `mfa.totp_issuer_label` | string | nein | Aussteller-Bezeichnung für TOTP, höchstens 64 Zeichen; leer bedeutet der Anzeigename der Umgebung. Keine Steuerzeichen, kein `:` und keine führenden oder nachgestellten Leerzeichen. Darf nicht `null` sein. |
| `mfa.recovery_codes_enabled` | boolean | nein | Wiederherstellungscodes ein oder aus (Standard `true`). Darf nicht `null` sein. |
| `mfa.recovery_code_count` | integer | nein | Anzahl der Wiederherstellungscodes, 8 bis 16 (Standard 10). Darf nicht `null` sein. |
| `mfa.required` | boolean | nein | Zweiten Faktor bei jeder Anmeldung in der Umgebung verlangen (Standard `false`). Wird mit den Anforderungen von Anwendung, Organisation und Anfrage per ODER kombiniert; eine bestehende SSO-Sitzung unterhalb dieses Niveaus wird zur Heraufstufung aufgefordert (`interaction_required` mit `prompt=none`). Darf nicht `null` sein. |
| `mfa.min_acr` | string | nein | Niedrigstes Sicherheitsniveau, das jede Anmeldung in der Umgebung erreichen muss: `1fa` (Standard, ohne Wirkung), `mfa` oder `phr`. Es gilt der höchste Schwellenwert aus Umgebung, Anwendung, Organisation und Anfrage, für neue Anmeldungen und für die Wiederverwendung von SSO-Sitzungen. Darf nicht `null` sein. |
| `mfa.enrollment_grace_days` | integer | nein | Tage, an denen ein Benutzer ohne zweiten Faktor sich weiter anmelden darf, ohne ihn einzurichten; gezählt ab dem ersten Zeitpunkt, zu dem die Einrichtung von ihm verlangt wird, 0 bis 14 (Standard `0`, keine Schonfrist). Die Schonfrist verschiebt nur, was Umgebung und Organisation verlangen; `min_acr` einer Anwendung und die angeforderten `acr_values` werden nie verschoben. Ein Wert außerhalb des Bereichs liefert `422 validation_failed` mit `out_of_range`. Eine Verkürzung wirkt sofort. Darf nicht `null` sein. |
| `frontchannel_logout_enabled` | boolean | nein | Front-Channel Logout ein oder aus (Standard `false`); beim Ausschalten bleiben die registrierten URIs erhalten. Darf nicht `null` sein. |
| `allowed_first_factors` | string[] | nein | Erlaubte erste Faktoren: mindestens einer von `password`, `passkey`, `email_otp`, `magic_link`, `sms_otp`, `social`, `enterprise`, keine Duplikate. Ersetzt die gesamte Liste und wird in dieser Reihenfolge zurückgegeben. Standard sind `password`, `passkey`, `social`, `enterprise`; `email_otp`, `magic_link` und `sms_otp` sind nur aktiv, wenn sie ausdrücklich hinzugefügt werden. Eine leere Liste liefert `422 required` (eine Umgebung, in der sich niemand anmelden könnte). Darf nicht `null` sein. |

## Verhalten

- Gruppen werden rekursiv zusammengeführt: Senden Sie innerhalb von `locales`, `session`, `signup`, `security_notifications`, `account_recovery` und `mfa` nur die Felder, die Sie ändern. Eine Gruppe selbst darf nicht `null` sein.
- Eine Verkürzung der Sitzungsdauer, eine Senkung des Limits für gleichzeitige Sitzungen, eine Einschränkung der Registrierung oder der Kontowiederherstellung, eine Absenkung des E-Mail-Zweitfaktors von `mfa` auf `1fa` sowie das Entfernen eines Faktors aus `allowed_first_factors` gelten sofort für neue Sitzungen und Anmeldungen; bestehende Sitzungen behalten ihre eigene Lebensdauer.
- Validierungsfehler enthalten den JSON-Pointer des Feldes, zum Beispiel `/session/idle_timeout_s` mit dem Code `out_of_range`. Ein Fehler in einem Listeneintrag verweist auf diesen Eintrag: `/allowed_first_factors/1` mit `unknown_value` (unbekannter Wert) oder `duplicate` (wiederholter Wert).
- `allowed_first_factors` wird mit den Richtlinien von Anwendung und Organisation geschnitten: Diese können die Menge nur einschränken.
- Hat die neue Menge keine Methode gemeinsam mit einer Anwendung, die eine eigene `allowed_first_factors`-Menge besitzt, antwortet die Anfrage mit `422 policy_conflict`. Ändern Sie zuerst die Menge der Anwendung.
- Der Audit-Eintrag und das Änderungsereignis enthalten die Namen der geänderten Felder, niemals deren Werte. Der Angriffsschutz ist eine eigene Ressource und nicht Teil dieser Ressource.

## Antwort

`200 OK` mit den aktualisierten Einstellungen und dem neuen `ETag`.

```json
{
  "locales": {"enabled": ["en", "tr"], "default": "tr"},
  "default_zoneinfo": "Europe/Istanbul",
  "session": {"idle_timeout_s": 3600, "absolute_timeout_s": 2592000, "max_concurrent": 5, "concurrent_mode": "evict_oldest", "anomaly_action": "enforce"},
  "signup": {"mode": "invite_only", "email_domain_mode": "allowlist", "email_domains": ["example.com"]},
  "security_notifications": {"enabled": true, "events": ["password_changed", "password_reset"]},
  "account_recovery": {"mode": "self_service_waiting", "waiting_hours": 72, "support_url": ""},
  "mfa": {"email_second_factor_enabled": false, "email_second_factor_acr": "1fa", "totp_issuer_label": "Acme", "recovery_codes_enabled": true, "recovery_code_count": 10, "required": false, "min_acr": "1fa", "enrollment_grace_days": 0},
  "frontchannel_logout_enabled": false,
  "allowed_first_factors": ["password", "passkey", "magic_link", "enterprise"],
  "updated_at": "2026-10-05T09:30:00.123456Z"
}
```

## Fehler

| HTTP | Code | Wann |
|---|---|---|
| 400 | `unknown_field` | Der Patch enthält ein unbekanntes oder schreibgeschütztes Feld oder ist kein JSON-Objekt (`malformed_request`). |
| 412 | `precondition_failed` | `If-Match` stimmt nicht mit der aktuellen Version überein. |
| 415 | `unsupported_media_type` | Der Content-Type ist nicht `application/merge-patch+json`. |
| 422 | `validation_failed` | Ein Wert ist ungültig (der Pointer nennt das Feld), oder ein Feld, das nicht `null` sein darf, wurde auf `null` gesetzt (`code: invalid`). |
| 422 | `policy_conflict` | Das neue `allowed_first_factors` hat keine Methode gemeinsam mit einer Anwendung, die eine eigene Menge besitzt. |
| 428 | `precondition_required` | Der Header `If-Match` fehlt. |
| 401 | `unauthenticated` | Die Anmeldedaten fehlen, sind ungültig oder widerrufen. |
| 403 | `insufficient_permissions` | Die Anmeldedaten haben nicht die erforderliche Berechtigung. |
| 429 | `rate_limited` | Das Ratenlimit wurde überschritten; wiederholen Sie die Anfrage nach der Zeit aus `Retry-After`. |
| 503 | `service_unavailable` | Eine Abhängigkeit ist vorübergehend nicht verfügbar; wiederholen Sie die Anfrage nach der Zeit aus `Retry-After`. |

## Beispiel

E-Mail-Magic-Links aktivieren und Passwörter deaktivieren:

```bash
curl -X PATCH https://api.eu.keenauth.com/v1/environment-settings \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/merge-patch+json" \
  -H 'If-Match: "0"' \
  -d '{"allowed_first_factors":["passkey","magic_link","social","enterprise"]}'
```

Einstellungen für Sitzungen und Registrierung:

```bash
curl -X PATCH https://api.eu.keenauth.com/v1/environment-settings \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/merge-patch+json" \
  -H 'If-Match: "0"' \
  -d '{"session":{"idle_timeout_s":3600},"signup":{"mode":"invite_only","email_domain_mode":"allowlist","email_domains":["example.com"]}}'
```
