
# Aggiorna le impostazioni dell'ambiente

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

Aggiorna le impostazioni dell'ambiente con un JSON merge patch (`application/merge-patch+json`, RFC 7396). `If-Match` è obbligatorio: se manca restituisce `428`, se la versione è obsoleta restituisce `412`. Se le impostazioni non sono mai state scritte, la versione è `"0"` e la prima scrittura crea la riga. Una patch che non cambia nulla non scrive nulla e mantiene la versione.

## Richiesta

| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
| `If-Match` | header | sì | Il `ETag` delle impostazioni così come letto l'ultima volta (`"0"` se non sono mai state scritte). Se manca: `428`; se obsoleto: `412`. |
| `locales.enabled` | string[] | no | Codici lingua abilitati (lingue registrate), da 1 a 64 voci, senza duplicati. Non può essere `null`. |
| `locales.default` | string | no | Lingua predefinita; deve essere una di `locales.enabled`. Non può essere `null`. |
| `default_zoneinfo` | string | no | Nome del fuso orario IANA (predefinito `UTC`). Non può essere `null`. |
| `session.idle_timeout_s` | integer | no | Timeout di inattività in secondi, da 60 a 8640000 (predefinito 259200); non maggiore di `session.absolute_timeout_s`. Non può essere `null`. |
| `session.absolute_timeout_s` | integer | no | Durata assoluta in secondi, da 60 a 31536000 (predefinito 2592000). Non può essere `null`. |
| `session.max_concurrent` | integer | no | Sessioni simultanee per utente, da 0 a 500; `0` significa il limite di sistema (predefinito). Non può essere `null`. |
| `session.concurrent_mode` | string | no | `evict_oldest` (predefinito) o `deny_new`. Non può essere `null`. |
| `session.anomaly_action` | string | no | `enforce` (predefinito) o `log`. Non può essere `null`. |
| `signup.mode` | string | no | `open` (predefinito), `invite_only` o `closed`. Non può essere `null`. |
| `signup.email_domain_mode` | string | no | `off` (predefinito), `allowlist` o `denylist`. Non può essere `null`. |
| `signup.email_domains` | string[] \| null | no | Al massimo 1000 domini; sostituisce l'intera lista (RFC 7396 non unisce gli array). `null` o `[]` la svuotano. Le voci sono normalizzate (minuscole, A-label IDNA), i duplicati vengono eliminati e la lista è memorizzata ordinata. Viene mantenuta mentre la modalità è `off`. |
| `security_notifications.enabled` | boolean | no | Interruttore generale (predefinito `true`). Non può essere `null`. |
| `security_notifications.events` | string[] \| null | no | Sottoinsieme dei 12 eventi di sicurezza (`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`); sostituisce l'intera lista e viene restituita nell'ordine canonico. `null` o `[]` la svuotano (nessun evento). |
| `account_recovery.mode` | string | no | `self_service_waiting` (predefinito), `admin_only` o `disabled`. Non può essere `null`. |
| `account_recovery.waiting_hours` | integer | no | Da 24 a 168 ore (predefinito 72); memorizzato in ogni modalità. Non può essere `null`. |
| `account_recovery.support_url` | string | no | Vuoto oppure un URL `https://` di al massimo 2048 byte, senza informazioni utente o frammento. Mostrato quando il recupero è `disabled`. Non può essere `null`. |
| `mfa.email_second_factor_enabled` | boolean | no | Secondo fattore via email attivo o disattivo (predefinito `false`). Non può essere `null`. |
| `mfa.email_second_factor_acr` | string | no | Livello di garanzia attribuito al fattore email: `1fa` (predefinito) o `mfa`. Non può essere `null`. |
| `mfa.totp_issuer_label` | string | no | Etichetta dell'emittente TOTP, al massimo 64 caratteri; vuota significa il nome visualizzato dell'ambiente. Nessun carattere di controllo, `:` o spazi iniziali/finali. Non può essere `null`. |
| `mfa.recovery_codes_enabled` | boolean | no | Codici di recupero attivi o disattivi (predefinito `true`). Non può essere `null`. |
| `mfa.recovery_code_count` | integer | no | Numero di codici di recupero, da 8 a 16 (predefinito 10). Non può essere `null`. |
| `mfa.required` | boolean | no | Richiede un secondo fattore a ogni accesso nell'ambiente (predefinito `false`). Combinato con OR con i requisiti dell'applicazione, dell'organizzazione e della richiesta; una sessione SSO esistente al di sotto di questo livello viene portata a un livello superiore (`interaction_required` con `prompt=none`). Non può essere `null`. |
| `mfa.min_acr` | string | no | Livello di garanzia minimo che ogni accesso nell'ambiente deve raggiungere: `1fa` (predefinito, nessun effetto), `mfa` o `phr`. Si applica la soglia più alta tra quelle di ambiente, applicazione, organizzazione e richiesta, per i nuovi accessi e per il riuso della sessione SSO. Non può essere `null`. |
| `mfa.enrollment_grace_days` | integer | no | Giorni in cui un utente senza secondo fattore può continuare ad accedere senza registrarsi, contati dalla prima volta in cui gli viene richiesta la registrazione, da 0 a 14 (predefinito `0`, nessuna tolleranza). La tolleranza posticipa solo ciò che richiedono gli assi di ambiente e organizzazione; il `min_acr` di un'applicazione e i `acr_values` richiesti non vengono mai posticipati. Un valore fuori intervallo restituisce `422 validation_failed` con `out_of_range`. Ridurlo ha effetto immediato. Non può essere `null`. |
| `frontchannel_logout_enabled` | boolean | no | Front-Channel Logout attivo o disattivo (predefinito `false`); disattivarlo mantiene gli URI registrati. Non può essere `null`. |
| `allowed_first_factors` | string[] | no | Primi fattori consentiti: almeno uno tra `password`, `passkey`, `email_otp`, `magic_link`, `sms_otp`, `social`, `enterprise`, senza duplicati. Sostituisce l'intera lista e viene restituita in questo ordine. Predefiniti `password`, `passkey`, `social`, `enterprise`; `email_otp`, `magic_link` e `sms_otp` sono attivi solo se aggiunti esplicitamente. Una lista vuota restituisce `422 required` (un ambiente in cui nessuno potrebbe accedere). Non può essere `null`. |

## Comportamento

- I gruppi si uniscono ricorsivamente: invia solo i campi che cambi all'interno di `locales`, `session`, `signup`, `security_notifications`, `account_recovery` e `mfa`. Un gruppo stesso non può essere `null`.
- Ridurre la durata di una sessione, abbassare il limite di sessioni simultanee, restringere la registrazione o il recupero dell'account, abbassare il secondo fattore via email da `mfa` a `1fa` e rimuovere un fattore da `allowed_first_factors` si applicano subito a nuove sessioni e nuovi accessi; le sessioni esistenti mantengono la loro durata.
- Gli errori di validazione riportano il puntatore JSON del campo, per esempio `/session/idle_timeout_s` con codice `out_of_range`. Un errore in una voce di lista punta alla voce: `/allowed_first_factors/1` con `unknown_value` (valore sconosciuto) o `duplicate` (valore ripetuto).
- `allowed_first_factors` viene intersecato con le policy dell'applicazione e dell'organizzazione: possono solo restringere questo insieme.
- Se il nuovo insieme non lascia alcun metodo in comune con un'applicazione che ha un proprio `allowed_first_factors`, la richiesta restituisce `422 policy_conflict`; modifica prima l'insieme dell'applicazione.
- Il record di audit e l'evento di modifica riportano i nomi dei campi modificati, mai i loro valori. La protezione dagli attacchi è una risorsa separata e non fa parte di questa.

## Risposta

`200 OK` con le impostazioni aggiornate e il nuovo `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"
}
```

## Errori

| HTTP | code | Quando |
|---|---|---|
| 400 | `unknown_field` | La patch contiene un campo sconosciuto o di sola lettura, oppure non è un oggetto JSON (`malformed_request`). |
| 412 | `precondition_failed` | `If-Match` non corrisponde alla versione corrente. |
| 415 | `unsupported_media_type` | Il content type non è `application/merge-patch+json`. |
| 422 | `validation_failed` | Un valore non è valido (il puntatore indica il campo), oppure un campo che non può essere `null` è stato impostato a `null` (`code: invalid`). |
| 422 | `policy_conflict` | Il nuovo `allowed_first_factors` non lascia alcun metodo in comune con un'applicazione che ha un insieme proprio. |
| 428 | `precondition_required` | L'header `If-Match` manca. |
| 401 | `unauthenticated` | La credenziale manca, non è valida o è stata revocata. |
| 403 | `insufficient_permissions` | La credenziale non ha l'autorizzazione richiesta. |
| 429 | `rate_limited` | È stato superato il limite di richieste; riprova dopo il periodo indicato in `Retry-After`. |
| 503 | `service_unavailable` | Una dipendenza è temporaneamente non disponibile; riprova dopo il periodo indicato in `Retry-After`. |

## Esempio

Attiva i magic link via email e disattiva le password:

```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"]}'
```

Impostazioni di sessione e registrazione:

```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"]}}'
```
