# Kimlik Doğrulama

Her istek HMAC-SHA256 kimlik doğrulama header'larını taşımalıdır.
İmzalanan payload kanonik hale getirilir; bu sayede yakalanmış bir
imza farklı bir endpoint'e, gövdeye veya zaman penceresine karşı
tekrar oynatılamaz.

| Header | Açıklama |
|--------|----------|
| `X-WHost-Key` | API anahtarı kimliği (`/admin/api-keys` üzerinden yapılandırılabilir). |
| `X-WHost-Timestamp` | Mevcut Unix zaman damgası (saniye). |
| `X-WHost-Nonce` | 16–256 karakter, istek başına benzersiz (öneri 32 hex); tekrar edilen nonce reddedilir. |
| `X-WHost-Signature` | `HMAC-SHA256(api_secret, "METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY")` hex digest. |

Agent şu isteklerini reddeder:

- zaman damgası **300 saniyeden** eski olan,
- nonce'u tekrar penceresi içinde (300 sn zaman damgası toleransının iki katı + 60 sn) zaten görülmüş olan,
- imzası sunucu tarafında yeniden hesaplandığında eşleşmeyen,
- path'i `posixpath.normpath` ve matrix-param sıyırma sonrası
  imzalanan path'ten kayan (böylece sondaki `;foo=bar` path
  bağlamasını atlayamaz).

### Hesaba bağlı (bayi) anahtarlar

Bir bayi hesabının client panelden (Settings → API Access) ürettiği anahtar istekleri sunucu anahtarıyla birebir aynı şekilde imzalar, ancak **o bayi olarak** sunulur: yalnızca `/api/v1/client/*` yüzeyine ulaşır ve orada bayinin sahiplik kontrolleri ve ACL planıyla çalışır. Böyle bir anahtarın aldığı yanıtlar:

| İstek | Yanıt |
|---|---|
| `/api/v1/client/` dışındaki herhangi bir yol | `403 SCOPE_DENIED` |
| `/api/v1/client/api-keys*`, `/api/v1/client/profile*` (kimlik bilgisi ve profil yönetimi) | `403 HMAC_FORBIDDEN_FOR_CREDENTIAL_MUTATION` — yalnızca tarayıcı oturumu |
| Bayinin sahibi olmadığı bir alt hesap | `404 ACCOUNT_NOT_FOUND` |
| Bayinin ACL planında `api_access` yok ya da hesap artık bayi değil | `403 PERMISSION_DENIED` |
| Sahip hesap askıda, sonlandırılmış ya da silinmiş; anahtar iptal edilmiş | `401 AUTH_FAILED` |

Anahtarın kapsamı sabittir (`client`) ve genişletilemez; bir bayi en fazla 10 anahtar tutar. Bayinin kendi anahtarları için yönetim uçları: `GET/POST /api/v1/client/api-keys`, `PUT /api/v1/client/api-keys/{key_id}`, `POST /api/v1/client/api-keys/{key_id}/revoke`, `DELETE /api/v1/client/api-keys/{key_id}`, `GET /api/v1/client/api-keys/logs` — yalnızca oturum çerezi ile. Yönetici tüm anahtarları `GET /api/v1/system/api-keys` altında görür; `owner` alanı bayiyi adlandırır (sunucu geneli anahtarlarda `null`).

### Kanonik imzalama payload'ı (v2)

```
METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY
```

`PATH`, agent tarafından görünen path'tir (host'tan sonraki kısım,
`/api/v1/...` prefix'i dahil). `BODY`, `surrogateescape` ile UTF-8
olarak decode edilmiş **ham HTTP gövde byte'larıdır** (ikili yüklemeler
yeniden encode etmeye gerek kalmadan imzalanabilir).

### Referans Python imzalayıcı

```python
import hashlib, hmac, json, secrets, time
from urllib.request import Request, urlopen
import ssl

API_KEY    = "your-api-key"
API_SECRET = "your-api-secret"
BASE_URL   = "https://panel.example.com"

def sign(method: str, path: str, body: bytes = b"") -> dict[str, str]:
    ts    = str(int(time.time()))
    nonce = secrets.token_hex(16)
    body_str = body.decode("utf-8", errors="surrogateescape")
    payload  = f"{method}\n{path}\n{ts}\n{nonce}\n{body_str}"
    sig = hmac.new(
        API_SECRET.encode("utf-8"),
        payload.encode("utf-8", errors="surrogateescape"),
        hashlib.sha256,
    ).hexdigest()
    return {
        "X-WHost-Key":       API_KEY,
        "X-WHost-Timestamp": ts,
        "X-WHost-Nonce":     nonce,
        "X-WHost-Signature": sig,
        "Content-Type":      "application/json",
    }

body = json.dumps({"username": "alice"}).encode("utf-8")
headers = sign("POST", "/api/v1/accounts", body)
req = Request(f"{BASE_URL}/api/v1/accounts", data=body,
              headers=headers, method="POST")
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE  # üretimde kaldırın
print(urlopen(req, context=ctx).read())
```

Diğer dillerdeki imzalayıcılar bu dosyanın yanında bulunur:

- PHP — [`docs/developer/examples/hmac-example.php`](examples/hmac-example.php)
- JavaScript / Node.js — [`docs/developer/examples/hmac-example.js`](examples/hmac-example.js)
- Go — [`docs/developer/examples/hmac-example.go`](examples/hmac-example.go)
- Ruby — [`docs/developer/examples/hmac-example.rb`](examples/hmac-example.rb)
- Bash / POSIX sh — [`docs/developer/examples/hmac-example.sh`](examples/hmac-example.sh)

### Yönetici oturum çerezi (yalnızca tarayıcı)

`/admin/*` paneli ve `/client/*` paneli, `POST /api/v1/auth/login`
(ardından 2FA etkinleştirildiğinde `/api/v1/auth/login/2fa`) üzerinden
`httpOnly` bir oturum çerezi üretir. SDK'lar ve partner entegrasyonları
**her zaman** bunun yerine HMAC kullanmalıdır — oturumlar tarayıcı
bağlamına özgüdür ve her yetki değişikliğinde rotasyona girer.

---
