
# Python sunucu SDK'sı

`keenauth-python`, Python 3.10 ve sonrası için sunucu SDK'sıdır. Yönetim API'sini tipli metotlarla çağırır ve tüm KeenAuth sunucu SDK'larının ortak davranışlarını ekler: API anahtarı ya da `private_key_jwt` makineden makineye token ile kimlik doğrulama, yeniden deneme, imleç ile sayfalama, tipli hatalar, webhook imza denetimleri ve kullanımdan kaldırma uyarıları. Paket henüz PyPI'da yayımlanmamıştır.

Bir web uygulamasında oturum açma için [Django](/docs/sdk/django), [Flask](/docs/sdk/flask) ve [FastAPI](/docs/sdk/fastapi) sayfalarına bakın. Bearer token alan bir API'yi korumak için [API'nizi koruma](/docs/sdk/protect-api) sayfasına bakın.

## Kurulum

```sh
pip install keenauth-python
```

`private_key_jwt` imzalayıcısı `cryptography` paketini gerektirir. Bunu `m2m` ekiyle kurun:

```sh
pip install "keenauth-python[m2m]"
```

## API anahtarı ile yönetim API'sini çağırma

```python
import os
from keenauth import KeenAuth

client = KeenAuth("https://api.eu.keenauth.com/v1", os.environ["KEENAUTH_API_KEY"])

page = client.users.list_users(limit=20)
for user in page.data:
    print(user.id)
```

Her oluşturulmuş API sınıfı bir özellik olarak kullanılabilir: `client.users`, `client.organizations`, `client.api_keys` ve benzerleri. Temel URL `https` kullanmalıdır; düz `http` yalnızca `localhost` için kabul edilir. SDK hiçbir yönlendirmeyi takip etmez; böylece kimlik bilgileriniz başka bir ana bilgisayara gönderilmez.

## private_key_jwt yönetim token'ı kullanma

Bir yönetim uygulaması API anahtarı yerine özel anahtarıyla kimlik doğrulaması yapar. SDK bir istemci iddiası (RFC 7523) imzalar, ortamınızın token uç noktasından bir yönetim API token'ı alır ve süresi dolmadan 60 saniye önce yeniler:

```python
import os
from keenauth import KeenAuth
from keenauth.m2m import ManagementTokenSource

source = ManagementTokenSource(
    issuer="https://acme.eu.keenauth.app",          # your environment's default host
    client_id="app_01j9zzzzzzzzzzzzzzzzzzzz",       # a management application
    private_key=os.environ["KEENAUTH_PRIVATE_KEY"],  # unencrypted PEM
    resource="https://api.eu.keenauth.com",          # the management API of your region
    scopes=["users:read", "users:write"],            # optional; default: the grant's default scopes
)
client = KeenAuth("https://api.eu.keenauth.com/v1", token_source=source)
```

- İddiada `iss` ve `sub` değeri `client_id`, `aud` değeri ise issuer'dır; rastgele bir `jti` ve 60 saniyelik bir ömrü vardır. `kid` değeri, genel anahtarın RFC 7638 parmak izidir.
- Desteklenen anahtarlar: EC P-256 (`ES256`), EC P-384 (`ES384`), Ed25519 (`EdDSA`) ve en az 2048 bit RSA (varsayılan `PS256` ya da `algorithm="RS256"`).
- Genel anahtarı yönetim uygulamasına kaydedin. `keenauth.m2m.load_private_key(pem).public_jwk()` bunu bir JWK olarak döndürür.
- Token önbelleğe alınır. Birden çok iş parçacığı aynı anda yeni token'a ihtiyaç duyduğunda token uç noktasına yalnızca bir istek gönderilir.
- Reddedilen bir istek, `status`, `error` ve `error_description` alanlarıyla `keenauth.m2m.TokenError` fırlatır. Mesaj hiçbir zaman iddiayı ya da token'ı içermez.

## Yeniden denemeler

SDK, `429`, `502`, `503` veya `504` ile başarısız olan ya da bağlantı hatası ya da zaman aşımı yaşanan istekleri yeniden dener. Yalnızca `GET`, `PUT`, `DELETE` ve bir `Idempotency-Key` taşıyan `POST` isteklerini yeniden dener. `If-Match` taşıyan bir `PATCH` yalnızca `429` durumunda yeniden denenir: hız sınırlayıcı isteği çalışmadan reddeder ve `If-Match`, bir değişikliğin iki kez uygulanmasını önler. Diğer tüm `PATCH` istekleri hiçbir zaman yeniden denenmez. Toplamda en fazla 3 deneme yapılır.

- Yanıtta `Retry-After` başlığı varsa (saniye ya da HTTP tarihi), SDK o kadar bekler. `Retry-After` 60 saniyeden uzunsa SDK beklemez ve yanıtı size döndürür.
- `Retry-After` yoksa 0,5 saniye, ardından 1 saniye bekler; her beklemeye ±%25 rastgele jitter eklenir.
- SDK, kabul eden her `POST` işlemine rastgele bir UUIDv4 `Idempotency-Key` ekler; ancak kendi `idempotency_key=` değerinizi geçmediğiniz sürece. Yeniden deneme aynı anahtarı gönderir; böylece sunucu işlemi yalnızca bir kez çalıştırır.

## Sayfalama

Liste metotları tek bir sayfa döndürür: `after` ve `before` imleçleriyle birlikte `data` ve `list_metadata`. `paginate` her sayfayı dolaşır ve sonraki sayfayı yalnızca ona ulaştığınızda getirir:

```python
from keenauth import paginate

for user in paginate(client.users.list_users, limit=100, status="active"):
    if user.email == "ada@example.com":
        break  # no further page is requested
```

- Filtreler ve `order` gibi diğer argümanlar her sayfa isteğiyle birlikte gönderilir.
- Bir imleçten geriye doğru gezinmek için `before=` geçin. `after` ve `before` birlikte kullanılamaz.
- `pages(...)` aynı argümanları alır ve öğeler yerine tüm sayfaları döndürür.

## Hatalar

`application/problem+json` (RFC 9457) biçimindeki bir hata yanıtı `keenauth.ProblemError` fırlatır. Alanlarından `code`, `status`, `detail`, `request_id` ve `errors` değerlerini kullanın. Bu SDK sürümünün bilmediği bir `code` yine de metin olarak döndürülür; böylece yeni sunucu hata kodları kodunuzu bozmaz.

```python
from keenauth import ProblemError

try:
    client.users.get_user("usr_01j9zzzzzzzzzzzzzzzzzzzz")
except ProblemError as e:
    if e.code == "not_found":
        ...
    print(e.request_id)
```

## Webhook'ları doğrulama

KeenAuth webhook'larını Standard Webhooks şemasıyla imzalar. `WebhookVerifier`, `webhook-id`, `webhook-timestamp` ve `webhook-signature` başlıklarını uç noktanızın `idp_whsec_` sırrına karşı denetler:

```python
import os
from flask import Flask, request
from keenauth.webhooks import WebhookVerificationError, WebhookVerifier

app = Flask(__name__)
verifier = WebhookVerifier(os.environ["KEENAUTH_WEBHOOK_SECRET"])

@app.post("/webhooks/keenauth")
def keenauth_webhook():
    try:
        verifier.verify(request.get_data(), request.headers)
    except WebhookVerificationError as e:
        return {"error": e.reason}, 400
    event = request.get_json()
    ...
    return "", 204
```

- İşlenmemiş istek gövdesini, JSON ayrıştırmasından önce geçin; değişen tek bir bayt bile denetimi başarısız kılar.
- Zaman damgası, sunucunuzun saatiyle her iki yönde de 5 dakika içinde olmalıdır.
- Sırrı döndürürken ikisini birden geçin: `WebhookVerifier([new_secret, old_secret])`. Başlıktaki herhangi bir imza herhangi bir sırla eşleştiğinde istek kabul edilir.
- Hatalar tiplidir: `WebhookHeadersError`, `WebhookTimestampError`, `WebhookSignatureError` ve `WebhookSecretError` (hatalı biçimli sır; doğrulayıcı oluşturulduğunda fırlatılır). Her birinin bir `reason` alanı vardır.
- Karşılaştırma sabit sürede yapılır; hata mesajı hiçbir zaman sırrı ya da imzayı içermez.

## Tipli olaylar ve iddialar

`keenauth.events`, genel olay schema'larından üretilen her müşteri olay türü için dondurulmuş bir dataclass içerir. `parse_event`, doğrulanmış bir gövdeyi kontrol eder ve türünün sınıfını döndürür. `keenauth.webhooks.verify_event` doğrulama ve ayrıştırmayı tek çağrıda yapar. `match` ya da `isinstance` ile daraltın:

```python
from keenauth.events import EmailSendingSuspendedEvent, UnknownEvent, UserCreatedEvent
from keenauth.webhooks import verify_event

event = verify_event(verifier, request.get_data(), request.headers)
match event:
    case UserCreatedEvent():
        print(event.data.object.id)
    case EmailSendingSuspendedEvent():
        print(event.data.object.reason)
    case UnknownEvent():
        pass  # a type this SDK version does not know yet; event.type and event.raw are still available
```

- `parse_event` bayt, metin ya da zaten çözümlenmiş bir eşleme kabul eder. Zarfı ve türün schema'sının tanımladığı alanları denetler. Eksik ya da yanlış türde bir alan, `data.object.reason` gibi yolunu içeren bir `EventParseError` fırlatır.
- Bilinmeyen bir tür ya da bu SDK'nın modellemediği bir `schema_version` değerine sahip bilinen bir tür, bir `UnknownEvent` olur. Bu bir hata değildir.
- Schema'nın tanımlamadığı alanlar `raw` içinde saklanır (olayda ve `data.object` üzerinde).
- Diziler tuple olarak temsil edilir. Sabit listeli alanların değerleri bilinen değerlere karşı denetlenmez.
- Modeller `mypy --strict` denetimini geçer.

`keenauth.claims`, doğrulanmış bir token'ın iddialarını tiplendirir: `parse_id_token_claims` (OpenID Connect Core) ve `parse_access_token_claims` (RFC 9068). Bunlar iddia türlerini denetler ama hiçbir şeyi doğrulamaz; bu yüzden doğrulayıcının döndürdüğü `claims` üzerinde çağırın:

```python
from keenauth.claims import parse_access_token_claims

at = verifier.verify_access_token(token)
claims = parse_access_token_claims(at.claims)
claims.client_id     # str
claims.org_roles     # tuple[str, ...] | None
claims.custom        # your own claims
```

`aud` her zaman bir tuple'dır. Uymayan bir iddia, adıyla birlikte `ClaimsParseError` fırlatır.

## Kullanımdan kaldırma uyarıları

Bir yanıtta `Deprecation` (RFC 9745) ya da `Sunset` (RFC 8594) başlığı bulunduğunda SDK, süreç başına bir uyarıyı standart `logging` modülüyle `keenauth` logger'ına yazar. Uyarı metodu, kaynağı ve başlık değerlerini belirtir; hiçbir zaman kimlik, sorgu dizesi ya da kimlik bilgisi içermez. İstek kendisi etkilenmez.

```python
import logging

logging.getLogger("keenauth").setLevel(logging.WARNING)
```

## Telemetri başlığı

Her istek `Idp-Client: keenauth-python/<version> (python/<version>)` başlığını taşır. Kişisel veri içermez ve kapatılamaz; KeenAuth bunu destek ve kullanımdan kaldırma bildirimleri için kullanır.
