SDK di gestione per Node.js

@keenauth/node è l'SDK TypeScript per la management API di KeenAuth. Richiede Node.js 22 o successivo e non ha dipendenze di runtime. Non è ancora pubblicato su npm.

Il pacchetto ha questi entry point:

Import Usalo per
@keenauth/node Il client della management API, i token di gestione, la paginazione e gli errori
@keenauth/node/webhooks Verificare le firme dei webhook, senza caricare il client API
@keenauth/node/bff Accesso per le app server: Express e Fastify
@keenauth/node/middleware Proteggere la tua API con bearer token
@keenauth/node/testing Il kit di test offline

Crea un client

Con una chiave API secret o restricted:

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 è l'indirizzo API della tua regione con /v1. Deve usare https; http semplice è accettato solo per localhost. La chiave viene inviata solo nell'header Authorization e solo a quell'indirizzo. I redirect non vengono seguiti.

Autenticati senza una chiave API

Un'applicazione di tipo management può chiamare l'API con un token a breve durata invece di una chiave statica. L'applicazione firma un client assertion con la propria chiave privata (private_key_jwt, RFC 7523), e l'SDK lo scambia all'endpoint dei token del tuo ambiente.

  1. Nella dashboard, crea un'applicazione di tipo management e un record di client di gestione con le autorizzazioni di cui ha bisogno (vedi Crea un client di gestione).
  2. Genera una coppia di chiavi e registra la chiave pubblica come credenziale public_key dell'applicazione. Per esempio, per ES256: openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out management-key.pem.
  3. Conserva la chiave privata nel tuo secret store. Passala all'SDK:
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", // your environment's issuer, exactly
    clientId: "app_01j9zzzzzzzzzzzzzzzzzzzz",
    privateKey: readFileSync("management-key.pem", "utf8"), // PEM, a private JWK or a KeyObject
    scope: ["users:read", "users:write"], // optional; default: the application's default scopes
  },
});

Cosa fa l'SDK:

Una richiesta di token non riuscita genera TokenError con il code OAuth (per esempio invalid_client o invalid_scope), description e status. Un errore di configurazione, come una chiave pubblica al posto di una privata, viene generato al momento della creazione del client.

apiKey e credentials non possono essere usati insieme.

Retry

L'SDK ritenta una richiesta al massimo 3 volte in totale quando l'API risponde 429, 502, 503 o 504, oppure quando la connessione fallisce. Ritenta solo richieste GET, PUT e DELETE e richieste POST che includono un Idempotency-Key. Una PATCH che include If-Match viene ritentata solo su 429: il rate limiter rifiuta la richiesta prima che venga eseguita, e If-Match evita che una modifica venga applicata due volte. Qualsiasi altra PATCH non viene mai ritentata.

L'SDK aggiunge un Idempotency-Key casuale a ogni POST la cui operazione lo accetta, così una creazione ritentata non crea un secondo oggetto. Per rendere sicuri anche i tuoi retry, passa tu la stessa chiave:

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

Paginazione

Gli endpoint di lista restituiscono una pagina: data e listMetadata con i cursori before e after. paginate segue i cursori al posto tuo:

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; // stops without loading the next page
}

Errori

Una risposta di errore diventa un ProblemError (RFC 9457) con status, code, title, detail, requestId e gli errors dei singoli campi. Controlla code, non il messaggio. I codici che l'SDK non conosce restano disponibili come stringhe.

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

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

Una risposta senza documento di problema, come una pagina HTML di un proxy, diventa il ResponseError generato con la response grezza.

Verifica i webhook

KeenAuth firma i webhook con lo schema Standard Webhooks v1. Verifica la firma con il corpo grezzo della richiesta, prima di analizzarlo:

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;
  }
  // handle event.type, then answer quickly
  res.sendStatus(204);
});

I webhook possono arrivare più di una volta. Usa webhook-id per ignorare i duplicati.

Eventi tipizzati e claim

@keenauth/node/events ha un tipo per ogni evento cliente, generato dagli schemi pubblici degli eventi. parseEvent controlla un corpo verificato e restituisce KeenAuthEvent | UnknownEvent. Dopo isKnownEvent, switch (event.type) restringe l'evento a un tipo specifico. verifyEvent in @keenauth/node/webhooks esegue entrambi i passaggi:

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

const event = verifyEvent(webhook, req.body, req.headers); // WebhookVerificationError or EventParseError
if (!isKnownEvent(event)) {
  // A type this SDK version does not know yet. event.type and event.data.object.id are still typed.
  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;
}

@keenauth/node/claims tipizza i claim di un token verificato: parseIdTokenClaims (OpenID Connect Core) e parseAccessTokenClaims (RFC 9068). Controllano i tipi dei claim ma non verificano nulla, quindi chiamali sui claims restituiti dal verificatore:

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: your own claims

Un claim che non corrisponde genera ClaimsParseError con il suo nome.

Avvisi di deprecazione

Quando una risposta contiene un header Deprecation (RFC 9745) o Sunset (RFC 8594), l'SDK scrive un avviso per ogni endpoint e valore dell'header per tutta la vita del processo. L'avviso indica il metodo, il percorso con gli ID sostituiti da {id} e i valori dell'header; non contiene mai token, query string o dati utente. Non genera mai eccezioni.

Per impostazione predefinita l'avviso va a process.emitWarning con il tipo KeenAuthDeprecationWarning. Per inviarlo al tuo logger:

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

Header di telemetria

Ogni richiesta contiene Idp-Client: keenauth-ts/<version> (node/<version>). Non contiene dati personali e non può essere disattivato; KeenAuth lo usa per il supporto e per avvisarti delle versioni dell'SDK deprecate.