@generazioneai/gateway-kit
v0.2.1
Published
Il bordo comune dei gateway HTTP di Generazione AI (ADR-014): token OIDC e audience, politica del tenant, firma interna e client NATS, rate limit su Redis condiviso, filtri d'errore, probe e metriche, spegnimento morbido, inventario e proprietari delle ro
Readme
@generazioneai/gateway-kit
Il bordo comune dei gateway HTTP di Generazione AI (ADR-014 «Gateway per prodotto», decisione dell'utente del 2026-10-29,
D1: solo bordo, niente dominio). Estratto dal gateway di Skillera (SKILLGATEWAY-BE) senza cambiarne il comportamento: stessi
default, stesse variabili, stesse chiavi Redis.
| Area | Cosa | Variabili |
|---|---|---|
| Login | JwtStrategy (JWKS di ID, RS256/PS256/ES256, iss), classifyBearer (ID token o access token), isAudienceAllowed | IDP_ISSUER_*, IDP_INTERNAL_URL_BASE, JWT_AUDIENCE, JWT_ALLOWED_AUDIENCES, JWT_AUDIENCE_ENFORCE, JWT_ACCEPT_ID_TOKEN_CLIENTS, JWT_LEGACY_ACCESS_TOKEN_AUDIENCES |
| Azienda | SkipTenant(), markTenantGuard() (il TenantGuard resta del gateway: chiede a ID), routeValidatesTenant, authzTenantOf | TENANT_HEADER_POLICY (legacy/log/enforce) |
| NATS | gatewayNatsClients() (code, resilienza, nkey, firma interna con systemSubject), tagNatsClients, rpcCall (letture a 5 s, nuovi tentativi), pagination: 'all' a pagine, latenze per comando | NATS_URL, NATS_NKEY_SEED, AUTHZ_INTERNAL_AUTH_MODE, RPC_READ_*, NATS_METRICS, NATS_SLOW_MS, GENQUERY_FOLLOW_MAX_ROWS |
| Rate limit | rateLimitProviders() (guard globale + Redis), classi, @RateLimit(), @SkipRateLimit(), RATE_LIMIT_DEVICE_RESOLVER | RATE_LIMIT_MODE, RATE_LIMIT_CLASSES, RATE_LIMIT_LOG_SALT, AUTHZ_REDIS_URL |
| HTTP | trustProxyFn, indirizzo del client, securityHeaders(), corsOriginsFromEnv, mountSwaggerDocs (basic auth) | TRUSTED_PROXY_CIDRS, CORS_ORIGINS, SWAGGER_PASSWORD |
| Errori | filtri (AccessExceptionFilter, NotFoundExceptionFilter, …, ConnectionAbortedExceptionFilter), validationExceptionFactory, TransformInterceptor ({ success, data, meta }) | — |
| Probe | startHealthHttpServer (/health/live, /health/ready, /metrics, /metrics/nats), timedCheck | HEALTH_PORT |
| Spegnimento | InFlightTracker, installGracefulShutdown (readiness giù, stream chiusi, richieste finite, poi app.close()) | SHUTDOWN_TIMEOUT_MS, SHUTDOWN_DRAIN_MS |
| Rotte | scanModuleRoutes, scanControllerRoutes, routeInventory, checkRouteOwnership (una rotta, un gateway) | — |
Modalità: valori finali fissi (0.2, decisione del 2026-11-10)
Niente più modalità di rilascio: RATE_LIMIT_MODE = enforce (429), TENANT_HEADER_POLICY = enforce (solo il tenant validato),
JWT_AUDIENCE_ENFORCE = true (con JWT_ALLOWED_AUDIENCES impostata un aud fuori elenco è un 401; senza elenco nessun controllo e
una riga nel log all'avvio), AUTHZ_INTERNAL_AUTH_MODE = enforce (i client firmati sono sempre firmati, e AUTHZ_REDIS_URL è
obbligatoria fuori dallo sviluppo locale). Solo in sviluppo locale e nei test (SKILLERA_ENV dev/local, oppure NODE_ENV development/test, mai dentro un cluster) contano ancora i valori di prima; altrove un valore diverso si ignora con un avviso al primo uso, mai un errore all'avvio.
Restano parametri: HEALTH_PORT, TRUSTED_PROXY_CIDRS, NATS_METRICS, JWT_ACCEPT_ID_TOKEN_CLIENTS, JWT_LEGACY_ACCESS_TOKEN_AUDIENCES.
Contratti stabili (si cambiano solo con un major)
- Chiavi Redis del rate limit
rl:<classe>:<chiave>:<finestra>: i gateway contano sullo stesso Redis (AUTHZ_REDIS_URL), così il tetto per IP e per persona vale una volta sola anche con più gateway sugli stessi host (D6). - Formato delle risposte
{ success, data, meta }e degli errori{ success: false, statusCode, message, … }. - Inventario delle rotte
METODO /percorso(prefisso globale compreso).
Uso (riassunto)
@Global() @Module({ providers: [...rateLimitProviders(), { provide: RATE_LIMIT_DEVICE_RESOLVER, useExisting: MyDevices }] })
export class RateLimitModule {}
const nats = gatewayNatsClients({ services: ['id', 'ops'], signed: { id: 'skillID', ops: 'skillOps' }, systemSubject: 'skillGateway' });
@Global() @Module({ imports: nats.imports, providers: nats.providers, exports: nats.exports })
export class ConnectorModule {}In main.ts: app.use(inFlight.middleware()), app.set('trust proxy', trustProxyFn()), app.use(securityHeaders()), i filtri,
TransformInterceptor, startHealthHttpServer(health, undefined, natsMetricsEnabled() ? natsMetrics : null) e, dopo listen,
installGracefulShutdown({ app, httpServer, health, tracker: inFlight, probeServer }).
Rifiuti verso Sentinel (spento di serie)
Con DENY_SIGNALS=on e DENY_SIGNALS_SERVICE=<utente NATS> i filtri di 401 e 403, JwtStrategy e RateLimitGuard contano i rifiuti con @generazioneai/authz/signals, senza cambiare una risposta e senza codice nel gateway. Le frasi riconosciute sono in src/signals/deny-http.ts: una frase nuova in un guard vale other. Costo misurato: scripts/misura-rifiuti.cjs, scripts/misura-emit.cjs. Vault «Spec - Sentinel, rifiuti Zero Trust».
Rilascio
Trusted publishing da .github/workflows/publish-npm.yml (pacchetto gateway-kit), come gli altri pacchetti del repo. La prima
volta il nome non esiste su npm: va pubblicato a mano una volta e poi si configura il trusted publisher (vedi CHANGELOG).
