
# 공격 방어 설정 전체 교체

`PUT /v1/attack-protection` · 권한 `attack-protection:write`

공격 방어 객체 전체를 교체합니다(`application/json`). 생략한 섹션과 필드는 기본값으로 돌아갑니다. `If-Match`는 필수입니다. 없으면 `428`, 오래된 `ETag`이면 `412`가 반환됩니다. 같은 객체를 다시 쓰면 아무것도 기록되지 않고 `ETag`도 유지됩니다. 몇 개의 필드만 바꾸려면 `PATCH`를 사용하십시오.

## 요청

| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| `If-Match` | 헤더 | 예 | 마지막으로 읽은 객체의 `ETag`. 없으면 `428`; 오래되었으면 `412`. |
| 본문 | 객체 | 예 | 설정된 객체, 최대 32 KiB. 각 섹션은 선택 사항이며, 섹션 안의 각 필드도 선택 사항입니다. |

| 필드 | 타입 | 범위 또는 값 | 기본값 | 하한 |
|---|---|---|---|---|
| `brute_force.mode` | 텍스트 | `log`, `enforce` | `enforce` | |
| `brute_force.max_failures` | 정수 | 5-100 | 20 | 최대 100 |
| `brute_force.window_minutes` | 정수 | 5-60 | 15 | |
| `brute_force.lock_minutes` | 정수 | 5-60 | 15 | 최소 5 |
| `brute_force.notify_user` | 불리언 | | `true` | |
| `suspicious_ip_throttling.mode` | 텍스트 | `log`, `enforce` | `enforce` | |
| `suspicious_ip_throttling.login_failures_per_ip_per_day` | 정수 | 10-10000 | 100 | |
| `suspicious_ip_throttling.signups_per_ip_per_hour` | 정수 | 1-20 | 20 | 최대 20 |
| `suspicious_ip_throttling.allowlist` | 텍스트 배열 | CIDR 접두사 최대 100개; IPv4는 /8보다, IPv6는 /32보다 넓을 수 없음 | `[]` | |
| `credential_stuffing.mode` | 텍스트 | `log`, `enforce` | `enforce` | |
| `credential_stuffing.attack_mode_auto` | 불리언 | | `true` | |
| `credential_stuffing.attack_mode_duration_minutes` | 정수 | 15-240 | 30 | |
| `bot_protection.mode` | 텍스트 | `off`, `risk_based`, `always` | `risk_based` | |
| `bot_protection.provider` | 텍스트 | `altcha` | `altcha` | |
| `breached_password.on_signup` | 텍스트 | `block` | `block` | `block` |
| `breached_password.on_password_change` | 텍스트 | `block` | `block` | `block` |
| `breached_password.on_login` | 텍스트 | `require_reset`, `notify` | `require_reset` | |
| `breached_password.notify_user` | 불리언 | | `true` | |
| `signup_protection.block_disposable_email` | 불리언 | | `true` | |
| `signup_protection.block_email_subaddress` | 불리언 | | `false` | |
| `signup_protection.max_signups_per_device_30d` | 정수 | 1-50 | 3 | |
| `sms_protection.allowed_countries` | 텍스트 배열 | ISO 3166-1 alpha-2 코드 최대 250개 | `[]` (요금제 기본값) | |
| `sms_protection.daily_cap` | 정수 \| null | 1-1000000; `null`은 요금제 값 | `null` | |
| `sms_protection.conversion_breaker` | 불리언 | | `true` | `true` |
| `risk_policy.mode` | 텍스트 | `log`, `enforce` (`enforce`는 Startup 이상 요금제 필요) | `log` | |
| `risk_policy.challenge_threshold` | 정수 | 3-8 | 4 | |
| `risk_policy.block_threshold` | 정수 \| null | 6-12, `challenge_threshold`보다 커야 함; `null`은 끔 | `null` | |
| `risk_policy.notify_user_on_new_device` | 불리언 | | `true` | |

보안 하한선 아래의 값은 필드의 일반 범위 안에 있더라도 `422 below_security_floor`로 거부됩니다. 하한선은 모든 환경을 보호하며 낮출 수 없습니다.

## 동작

- 변경 사항은 새 로그인에 즉시 적용됩니다. 캐시된 설정은 같은 트랜잭션에서 무효화됩니다.
- 감사 기록에는 변경된 필드 이름과 이전·새 객체가 포함됩니다.

## 응답

[공격 방어 설정 조회](get.md)에서와 같은 객체와 새 `ETag`가 포함된 `200 OK`입니다.

## 오류

| HTTP | 코드 | 발생 시점 |
|---|---|---|
| 400 | `unknown_field` | 섹션 또는 필드가 객체에 없습니다. `errors[]`에 포인터가 나열됩니다. |
| 400 | `malformed_request` | 본문이 올바른 JSON이 아니거나 `If-Match` 헤더가 잘못되었습니다. |
| 401 | `unauthenticated` | 자격 증명이 없거나, 잘못되었거나, 폐기되었습니다. |
| 403 | `insufficient_permissions` | 자격 증명에 `attack-protection:write` 권한이 없습니다. |
| 403 | `plan_feature_unavailable` | `risk_policy.mode`가 `enforce`인데 요금제에 포함되지 않습니다. 본문에 `feature`와 `required_plan`이 포함됩니다. |
| 412 | `precondition_failed` | 읽은 후 객체가 변경되었습니다. 다시 읽고 재시도하십시오. |
| 413 | `payload_too_large` | 객체가 32 KiB보다 큽니다. |
| 422 | `validation_failed` | 값의 타입이 잘못되었거나 범위를 벗어났거나, `block_threshold`가 `challenge_threshold`보다 크지 않습니다. `errors[]`에 포인터가 나열됩니다. |
| 422 | `below_security_floor` | 값이 보안 하한선보다 낮습니다. `errors[]`의 각 항목은 `detail`에 필드와 하한선을 적습니다. |
| 422 | `allowlist_range_too_broad` | 허용 목록 접두사가 IPv4는 /8, IPv6는 /32보다 넓습니다. |
| 428 | `precondition_required` | `If-Match`가 없습니다. |
| 429 | `rate_limited` | 요청 한도를 초과했습니다. `Retry-After` 시간 후에 다시 시도하십시오. |
| 503 | `service_unavailable` | 종속 서비스를 일시적으로 사용할 수 없습니다. `Retry-After` 시간 후에 다시 시도하십시오. |

오류 종류가 여러 개이면 가장 심각한 것만 다음 순서로 반환됩니다: `unknown_field`, `validation_failed`, `below_security_floor`, `allowlist_range_too_broad`, `plan_feature_unavailable`. 입력한 값은 응답에 다시 나타나지 않습니다.

## 예시

```bash
curl -X PUT https://api.eu.keenauth.com/v1/attack-protection \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H 'If-Match: "3f1c9a0b5d7e2a4c6b8d0e1f2a3b4c5d"' \
  -d '{"brute_force": {"max_failures": 10, "lock_minutes": 30}, "bot_protection": {"mode": "always"}}'
```
