
# Node.js yönetim SDK'sı

`@keenauth/node`, KeenAuth yönetim API'si için TypeScript SDK'sıdır. Node.js 22 veya üzeri bir sürüm gerektirir ve çalışma zamanı bağımlılığı yoktur. Henüz npm'de yayımlanmamıştır.

Paketin şu giriş noktaları vardır:

| İçe aktarma | Kullanım amacı |
|---|---|
| `@keenauth/node` | Yönetim API istemcisi, yönetim token'ları, sayfalama ve hatalar |
| `@keenauth/node/webhooks` | API istemcisini yüklemeden webhook imzalarını doğrulama |
| `@keenauth/node/bff` | Sunucu uygulamaları için oturum açma: [Express](/docs/sdk/express) ve [Fastify](/docs/sdk/fastify) |
| `@keenauth/node/middleware` | Bearer token'larla [API'nizi koruma](/docs/sdk/protect-api) |
| `@keenauth/node/testing` | [Çevrimdışı test kiti](/docs/sdk/testing) |

## İstemci oluşturma

Gizli veya kısıtlı bir API anahtarıyla:

```ts
import { KeenAuth } from "@keenauth/node";

const keenauth = new KeenAuth({
  baseUrl: "https://api.eu.keenauth.com/v1",
  apiKey: process.env.KEENAUTH_API_KEY!,
});

const user = await keenauth.users.getUser({ userId: "usr_01j9zzzzzzzzzzzzzzzzzzzz" });
```

`baseUrl`, bölgenizin API adresidir ve sonuna `/v1` eklenir. `https` kullanmalıdır; düz `http` yalnızca `localhost` için kabul edilir. Anahtar yalnızca `Authorization` başlığında ve yalnızca bu adrese gönderilir. Yönlendirmeler takip edilmez.

## API anahtarı olmadan kimlik doğrulama

`management` tipindeki bir uygulama, statik bir anahtar yerine kısa ömürlü bir token ile API'yi çağırabilir. Uygulama, özel anahtarıyla bir istemci onayı (assertion) imzalar (`private_key_jwt`, RFC 7523) ve SDK bunu ortamınızın token uç noktasında değiştirir.

1. Panelde `management` tipinde bir uygulama ve ihtiyaç duyduğu izinlere sahip bir yönetim istemci kaydı oluşturun (bkz. [Yönetim istemcisi oluşturma](/docs/api/management/management-clients/create)).
2. Bir anahtar çifti üretin ve genel anahtarı uygulamanın bir `public_key` kimlik bilgisi olarak kaydedin. Örneğin ES256 için: `openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out management-key.pem`.
3. Özel anahtarı gizli bilgi deponuzda saklayın. SDK'ya iletin:

```ts
import { readFileSync } from "node:fs";
import { KeenAuth } from "@keenauth/node";

const keenauth = new KeenAuth({
  baseUrl: "https://api.eu.keenauth.com/v1",
  credentials: {
    issuer: "https://acme.eu.keenauth.app", // ortamınızın issuer değeri, birebir
    clientId: "app_01j9zzzzzzzzzzzzzzzzzzzz",
    privateKey: readFileSync("management-key.pem", "utf8"), // PEM, özel JWK veya KeyObject
    scope: ["users:read", "users:write"], // isteğe bağlı; varsayılan: uygulamanın varsayılan kapsamları
  },
});
```

SDK şunları yapar:

- `<issuer>/oauth2/token` adresine `grant_type=client_credentials` gönderir ve `resource` değerini yönetim API tanımlayıcısına ayarlar. Varsayılan, `baseUrl` kökenidir (örneğin `https://api.eu.keenauth.com`); değiştirmek için `credentials.audience` değerini ayarlayın.
- Her istek için yeni bir istemci onayı üretilir: `iss` ve `sub` istemci kimliğidir, `aud` issuer'dır, `jti` rastgele bir değerdir ve onay 120 saniye geçerlidir. `kid` başlığı, anahtarın RFC 7638 parmak izidir; uygulamanın birden çok anahtarı olduğunda (anahtar rotasyonu için) KeenAuth anahtarı bu değerle bulur.
- Algoritma anahtarı izler: RSA anahtarları `RS256` ile imzalanır (RSA-PSS için `alg: "PS256"` ayarlayın), P-256 anahtarları `ES256` ile ve Ed25519 anahtarları `EdDSA` ile imzalanır. RSA anahtarları en az 2048 bit olmalıdır.
- Erişim token'ı önbelleğe alınır ve süresi dolmadan 60 saniye önce yenilenir. Eşzamanlı çağrılar tek bir yenilemeyi paylaşır. API `401` yanıtı verirse önbellekteki token atılır ve bir sonraki çağrı yeni bir token alır.

Başarısız bir token isteği, OAuth `code` (örneğin `invalid_client` veya `invalid_scope`), `description` ve `status` ile birlikte `TokenError` fırlatır. Özel anahtar yerine genel anahtar verilmesi gibi bir yapılandırma hatası ise istemci oluşturulduğunda fırlatılır.

`apiKey` ve `credentials` birlikte kullanılamaz.

## Yeniden deneme

API `429`, `502`, `503` veya `504` yanıtı verdiğinde ya da bağlantı kurulamadığında SDK bir isteği en fazla toplam 3 kez yeniden dener. Yalnızca `GET`, `PUT` ve `DELETE` istekleri ile bir `Idempotency-Key` taşıyan `POST` istekleri yeniden denenir. `If-Match` taşıyan bir `PATCH` yalnızca `429` yanıtında 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.

- Yanıtta `Retry-After` başlığı varsa (saniye veya HTTP tarihi), SDK o kadar süre bekler. 60 saniyeden uzun bir `Retry-After` beklenmez: SDK o yanıtı size döndürür.
- Aksi halde önce 0,5 saniye, sonra 1 saniye beklenir; her beklemeye ±%25 rastgele jitter eklenir.
- `timeoutMs` (varsayılan 30 saniye) yeniden denemeler dahil tüm çağrıyı sınırlar. Bir bekleme bu süreyi aşacaksa SDK bunun yerine son yanıtı döndürür.

SDK, desteklediği her `POST` işlemine rastgele bir `Idempotency-Key` ekler; böylece yeniden denenen bir oluşturma işlemi ikinci bir nesne oluşturmaz. Kendi yeniden denemelerinizi de güvenli kılmak için aynı anahtarı kendiniz iletebilirsiniz:

```ts
await keenauth.users.createUser({
  userCreateRequest: { email: "ada@example.com" },
  idempotencyKey: "signup-7d1f0c2a-0d7e-4c55",
});
```

## Sayfalama

Listeleme uç noktaları tek bir sayfa döndürür: `data` ve `before` ile `after` imleçlerini içeren `listMetadata`. `paginate`, imleçleri sizin yerinize takip eder:

```ts
import { paginate } from "@keenauth/node";

for await (const user of paginate((p) => keenauth.users.listUsers(p), { limit: 100 })) {
  console.log(user.id);
  if (user.email === "ada@example.com") break; // bir sonraki sayfayı yüklemeden durur
}
```

- `before` olmadan `after` ile ileriye doğru ilerler ve imleç `null` olana kadar devam eder. `before` ile geriye doğru ilerler.
- `limit`, sayfa boyutudur (1-100) ve her istekte gönderilir; diğer parametreler de korunur.
- `paginatePages`, öğeler yerine tam sayfalar verir.
- Sunucu aynı imleci iki kez döndürürse döngü `PaginationError` ile durur.

## Hatalar

Bir hata yanıtı, `status`, `code`, `title`, `detail`, `requestId` ve alan bazlı `errors` içeren bir `ProblemError` (RFC 9457) olur. İleti metnini değil, `code` değerini kontrol edin. SDK'nın tanımadığı kodlar dize olarak kullanılabilir kalır.

```ts
import { ProblemError } from "@keenauth/node";

try {
  await keenauth.users.getUser({ userId });
} catch (e) {
  if (e instanceof ProblemError && e.code === "not_found") {
    // …
  }
}
```

Bir proxy'den gelen HTML sayfası gibi, problem belgesi içermeyen bir yanıt ise ham `response` ile birlikte oluşturulmuş `ResponseError` olur.

## Webhook'ları doğrulama

KeenAuth, webhook'ları Standard Webhooks v1 şemasıyla imzalar. İmzayı, isteği ayrıştırmadan önce ham istek gövdesiyle doğrulayın:

```ts
import express from "express";
import { Webhook, WebhookVerificationError } from "@keenauth/node/webhooks";

const webhook = new Webhook(process.env.KEENAUTH_WEBHOOK_SECRET!); // idp_whsec_…

const app = express();
app.post("/webhooks/keenauth", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = webhook.verify(req.body, req.headers);
  } catch (e) {
    if (e instanceof WebhookVerificationError) return res.status(400).send(e.kind);
    throw e;
  }
  // event.type değerini işleyin, ardından hızlıca yanıt verin
  res.sendStatus(204);
});
```

- SDK, `webhook-id`, `webhook-timestamp` ve `webhook-signature` başlıklarını kontrol eder. Zaman damgası, sunucunuzun saatiyle her iki yönde de 5 dakika içinde olmalıdır.
- Gizli anahtar döndürme sırasında her iki anahtarı da iletin: `new Webhook([newSecret, oldSecret])`. Herhangi bir `v1` imzası herhangi bir anahtarla eşleştiğinde istek kabul edilir.
- İmzalar sabit sürede karşılaştırılır.
- `e.kind`, neyin başarısız olduğunu belirtir: `headers` (bir başlık eksik), `timestamp` (sayı değil veya tolerans dışında), `signature` (hiçbir imza eşleşmiyor) veya `secret` (yapılandırılan gizli anahtar geçerli bir `idp_whsec_` değeri değil; `new Webhook` tarafından fırlatılan bir yapılandırma hatası). Her türün `WebhookSignatureError` gibi kendi sınıfı da vardır.
- `verify`, ayrıştırılmış JSON gövdesini döndürür. `verifyRaw`, doğrulanmış baytları döndürür.

Webhook'lar birden fazla kez gelebilir. Yinelenenleri yok saymak için `webhook-id` değerini kullanın.

## Tipli olaylar ve talepler

`@keenauth/node/events`, herkese açık olay şemalarından üretilen her müşteri olayı için bir tip içerir. `parseEvent`, doğrulanmış bir gövdeyi kontrol eder ve `KeenAuthEvent | UnknownEvent` döndürür. `isKnownEvent` sonrasında `switch (event.type)`, olayı tek bir tipe daraltır. `@keenauth/node/webhooks` içindeki `verifyEvent` her iki adımı birden yapar:

```ts
import { verifyEvent } from "@keenauth/node/webhooks";
import { isKnownEvent } from "@keenauth/node/events";

const event = verifyEvent(webhook, req.body, req.headers); // WebhookVerificationError veya EventParseError
if (!isKnownEvent(event)) {
  // Bu SDK sürümünün henüz bilmediği bir tip. event.type ve event.data.object.id yine de tiplidir.
  return res.sendStatus(204);
}
switch (event.type) {
  case "user.created":
    console.log(event.data.object.id);
    break;
  case "email_sending.suspended":
    console.log(event.data.object.reason); // "bounce_rate" | "complaint_rate" | (string & {})
    break;
}
```

- `parseEvent`, JSON metnini, baytları veya zaten çözülmüş bir değeri kabul eder. Zarfı ve tipin şemasının tanımladığı üyeleri kontrol eder. Eksik veya yanlış tipte bir üye, `data.object.reason` gibi yolunu içeren bir `EventParseError` fırlatır.
- Bilinmeyen bir tip veya bu SDK'nın modellemediği bir `schema_version` değerine sahip bilinen bir tip, bir `UnknownEvent` olur. Bu bir hata değildir.
- Şemada tanımlanmayan üyeler nesne üzerinde kalır.
- Sayılandırılmış alanlar, bilinen değerler artı `string` olarak tiplenir; böylece sunucudan gelen yeni bir değer yine tip denetiminden geçer ve ayrıştırılır.

`@keenauth/node/claims`, doğrulanmış bir token'ın taleplerini tiplendirir: `parseIdTokenClaims` (OpenID Connect Core) ve `parseAccessTokenClaims` (RFC 9068). Talep tiplerini kontrol ederler ancak hiçbir şeyi doğrulamazlar; bu yüzden doğrulayıcının döndürdüğü `claims` üzerinde çağırın:

```ts
import { parseAccessTokenClaims } from "@keenauth/node/claims";

const at = await verifier.verifyAccessToken(token);
const claims = parseAccessTokenClaims(at.claims);
claims.client_id; // string
claims.org_roles; // string[] | undefined
claims["tenant_tier"]; // unknown: kendi talepleriniz
```

Uygun olmayan bir talep, adını içeren bir `ClaimsParseError` fırlatır.

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

Bir yanıt `Deprecation` (RFC 9745) veya `Sunset` (RFC 8594) başlığı taşıdığında SDK, süreç yaşadığı sürece her uç nokta ve başlık değeri için bir uyarı yazar. Uyarı, yöntemi, kimlikler yerine `{id}` konmuş yolu ve başlık değerlerini içerir; token, sorgu dizesi veya kullanıcı verisi asla içermez. Hiçbir zaman hata fırlatmaz.

Varsayılan olarak uyarı, `KeenAuthDeprecationWarning` tipiyle `process.emitWarning` fonksiyonuna gönderilir. Uyarıyı bunun yerine kendi günlükçünüze göndermek için:

```ts
const keenauth = new KeenAuth({
  baseUrl: "https://api.eu.keenauth.com/v1",
  apiKey: process.env.KEENAUTH_API_KEY!,
  onWarning: (message) => logger.warn(message),
});
```

## Telemetri başlığı

Her istek `Idp-Client: keenauth-ts/<version> (node/<version>)` başlığını taşır. Kişisel veri içermez ve kapatılamaz; KeenAuth bunu destek amacıyla ve kullanımdan kaldırılmış SDK sürümleri hakkında sizi bilgilendirmek için kullanır.
