@babelpeak/babel-suite-server-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
1
+ import { type JWTPayload } from 'jose';
2
+ export interface PermissionDefinition {
3
+ key: string;
4
+ description?: string;
5
+ /** Ruoli di sistema che ricevono il permesso di default; admin lo riceve sempre. */
6
+ defaultRoles?: ('admin' | 'user')[];
7
+ }
8
+ export interface SuiteClientOptions {
9
+ /** URL dell'issuer di Suite (es. https://suite.babelpeak.com). */
10
+ issuer: string;
11
+ /** Chiave del servizio: è l'`aud` atteso nei JWT. */
12
+ service: string;
13
+ /** Service key per le route /internal/* (llm-config, permessi, introspect). */
14
+ serviceKey?: string;
15
+ /** Base URL delle route interne, se Suite le serve su un listener separato. Default: issuer. */
16
+ internalUrl?: string;
17
+ /** Credenziali del client OIDC del servizio, per il token exchange fra servizi. */
18
+ clientId?: string;
19
+ clientSecret?: string;
20
+ /** TTL della cache della config LLM, in secondi (l'invalidazione arriva via webhook). */
21
+ llmConfigTtl?: number;
22
+ clockTolerance?: number;
23
+ fetch?: typeof fetch;
24
+ }
25
+ export interface SuiteAuth {
26
+ userId: string;
27
+ orgId: string;
28
+ permissions: string[];
29
+ token: string;
30
+ claims: JWTPayload;
31
+ }
32
+ export interface LlmConfig {
33
+ org_id: string;
34
+ service: string;
35
+ provider_keys: {
36
+ provider: string;
37
+ base_url: string | null;
38
+ api_key: string;
39
+ rotated_at: string;
40
+ }[];
41
+ enabled_models: {
42
+ provider: string;
43
+ model_id: string;
44
+ }[];
45
+ task_mapping: {
46
+ task_type: string;
47
+ model_id: string;
48
+ fallback_model_id: string | null;
49
+ }[];
50
+ }
51
+ export interface OrgMember {
52
+ user_id: string;
53
+ email: string;
54
+ name: string | null;
55
+ role: string;
56
+ permissions: string[];
57
+ }
58
+ export declare class SuiteError extends Error {
59
+ readonly status?: number | undefined;
60
+ readonly body?: unknown | undefined;
61
+ constructor(message: string, status?: number | undefined, body?: unknown | undefined);
62
+ }
63
+ export declare class TokenError extends Error {
64
+ }
65
+ interface Discovery {
66
+ issuer: string;
67
+ jwks_uri: string;
68
+ token_endpoint: string;
69
+ }
70
+ /**
71
+ * Client di Suite indipendente dal framework: validazione JWT con JWKS in cache,
72
+ * config LLM con cache e invalidazione, registrazione permessi e webhook, token exchange.
73
+ */
74
+ export declare class SuiteClient {
75
+ private readonly opts;
76
+ readonly issuer: string;
77
+ readonly service: string;
78
+ private readonly internalUrl;
79
+ private readonly doFetch;
80
+ private discovery?;
81
+ private jwks?;
82
+ private readonly cache;
83
+ private readonly inflight;
84
+ private webhookSecret?;
85
+ constructor(opts: SuiteClientOptions);
86
+ /** Discovery da /.well-known, una volta sola (ritentata se fallisce). */
87
+ getDiscovery(): Promise<Discovery>;
88
+ private getJwks;
89
+ /** Valida firma, issuer, `aud` = servizio e `typ` at+jwt. Nessuna chiamata a Suite per richiesta. */
90
+ verifyToken(token: string): Promise<SuiteAuth>;
91
+ private internalHeaders;
92
+ private internal;
93
+ /** Upsert idempotente dei permessi del servizio: si chiama a ogni avvio. */
94
+ registerPermissions(permissions: PermissionDefinition[]): Promise<{
95
+ registered: number;
96
+ }>;
97
+ /** Registra l'URL di invalidazione; il segreto HMAC resta in memoria. */
98
+ registerWebhook(url: string): Promise<{
99
+ secret: string;
100
+ }>;
101
+ /** Verifica firma e freschezza di una notifica di Suite. Ritorna l'org da invalidare. */
102
+ verifyWebhook(headers: Record<string, string | string[] | undefined>, rawBody: string): {
103
+ event: string;
104
+ org_id: string;
105
+ service: string;
106
+ reason: string;
107
+ };
108
+ /** Config LLM dell'organizzazione, con cache e deduplica delle richieste concorrenti. */
109
+ llmConfig(orgId: string): Promise<LlmConfig>;
110
+ /** Invalida la cache (di un'org o tutta). */
111
+ invalidate(orgId?: string): void;
112
+ /** Modello per un tipo di task, con fallback. */
113
+ modelFor(orgId: string, taskType: string): Promise<{
114
+ modelId: string;
115
+ fallbackModelId: string | null;
116
+ } | undefined>;
117
+ introspect(token: string): Promise<{
118
+ active: boolean;
119
+ sub?: string;
120
+ org_id?: string;
121
+ permissions?: string[];
122
+ }>;
123
+ members(orgId: string): Promise<OrgMember[]>;
124
+ /**
125
+ * Token exchange (RFC 8693) per chiamare un altro servizio per conto dell'utente:
126
+ * un JWT con aud di questo servizio diventa un JWT con aud del servizio di destinazione.
127
+ */
128
+ exchangeToken(input: {
129
+ subjectToken: string;
130
+ audience: string;
131
+ orgId?: string;
132
+ }): Promise<{
133
+ accessToken: string;
134
+ expiresIn: number;
135
+ }>;
136
+ }
137
+ export {};
package/dist/client.js ADDED
@@ -0,0 +1,213 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ import { createRemoteJWKSet, customFetch, jwtVerify } from 'jose';
3
+ export class SuiteError extends Error {
4
+ status;
5
+ body;
6
+ constructor(message, status, body) {
7
+ super(message);
8
+ this.status = status;
9
+ this.body = body;
10
+ }
11
+ }
12
+ export class TokenError extends Error {
13
+ }
14
+ const WEBHOOK_TOLERANCE_SEC = 300;
15
+ /**
16
+ * Client di Suite indipendente dal framework: validazione JWT con JWKS in cache,
17
+ * config LLM con cache e invalidazione, registrazione permessi e webhook, token exchange.
18
+ */
19
+ export class SuiteClient {
20
+ opts;
21
+ issuer;
22
+ service;
23
+ internalUrl;
24
+ doFetch;
25
+ discovery;
26
+ jwks;
27
+ cache = new Map();
28
+ inflight = new Map();
29
+ webhookSecret;
30
+ constructor(opts) {
31
+ this.opts = opts;
32
+ this.issuer = opts.issuer.replace(/\/$/, '');
33
+ this.service = opts.service;
34
+ this.internalUrl = (opts.internalUrl ?? this.issuer).replace(/\/$/, '');
35
+ this.doFetch = opts.fetch ?? fetch;
36
+ }
37
+ /** Discovery da /.well-known, una volta sola (ritentata se fallisce). */
38
+ getDiscovery() {
39
+ this.discovery ??= (async () => {
40
+ const res = await this.doFetch(`${this.issuer}/.well-known/openid-configuration`);
41
+ if (!res.ok)
42
+ throw new SuiteError('discovery fallita', res.status);
43
+ return (await res.json());
44
+ })().catch((err) => {
45
+ this.discovery = undefined;
46
+ throw err;
47
+ });
48
+ return this.discovery;
49
+ }
50
+ async getJwks() {
51
+ if (!this.jwks) {
52
+ const d = await this.getDiscovery();
53
+ // jose gestisce cache e rotazione: su un kid sconosciuto riscarica il JWKS.
54
+ this.jwks = createRemoteJWKSet(new URL(d.jwks_uri), {
55
+ cooldownDuration: 30_000,
56
+ cacheMaxAge: 10 * 60_000,
57
+ ...(this.opts.fetch ? { [customFetch]: this.opts.fetch } : {}),
58
+ });
59
+ }
60
+ return this.jwks;
61
+ }
62
+ /** Valida firma, issuer, `aud` = servizio e `typ` at+jwt. Nessuna chiamata a Suite per richiesta. */
63
+ async verifyToken(token) {
64
+ let payload;
65
+ try {
66
+ ({ payload } = await jwtVerify(token, await this.getJwks(), {
67
+ issuer: this.issuer,
68
+ audience: this.service,
69
+ typ: 'at+jwt',
70
+ clockTolerance: this.opts.clockTolerance ?? 5,
71
+ }));
72
+ }
73
+ catch (err) {
74
+ throw new TokenError(`token non valido: ${err.message}`);
75
+ }
76
+ if (!payload.sub || typeof payload.org_id !== 'string') {
77
+ throw new TokenError('token senza subject o organizzazione');
78
+ }
79
+ const permissions = Array.isArray(payload.permissions)
80
+ ? payload.permissions.filter((p) => typeof p === 'string')
81
+ : [];
82
+ return { userId: payload.sub, orgId: payload.org_id, permissions, token, claims: payload };
83
+ }
84
+ internalHeaders() {
85
+ if (!this.opts.serviceKey)
86
+ throw new SuiteError('serviceKey non configurata');
87
+ return {
88
+ 'x-suite-service': this.service,
89
+ 'x-suite-service-key': this.opts.serviceKey,
90
+ };
91
+ }
92
+ async internal(method, path, body) {
93
+ const headers = this.internalHeaders();
94
+ if (body !== undefined)
95
+ headers['content-type'] = 'application/json';
96
+ const res = await this.doFetch(`${this.internalUrl}/internal${path}`, {
97
+ method,
98
+ headers,
99
+ body: body === undefined ? undefined : JSON.stringify(body),
100
+ });
101
+ const text = await res.text();
102
+ const parsed = text ? JSON.parse(text) : undefined;
103
+ if (!res.ok)
104
+ throw new SuiteError(`Suite ${method} ${path}: HTTP ${res.status}`, res.status, parsed);
105
+ return parsed;
106
+ }
107
+ /** Upsert idempotente dei permessi del servizio: si chiama a ogni avvio. */
108
+ registerPermissions(permissions) {
109
+ return this.internal('POST', '/permissions/register', {
110
+ service: this.service,
111
+ permissions,
112
+ });
113
+ }
114
+ /** Registra l'URL di invalidazione; il segreto HMAC resta in memoria. */
115
+ async registerWebhook(url) {
116
+ const res = await this.internal('POST', '/llm-config/webhook', { url });
117
+ this.webhookSecret = res.secret;
118
+ return res;
119
+ }
120
+ /** Verifica firma e freschezza di una notifica di Suite. Ritorna l'org da invalidare. */
121
+ verifyWebhook(headers, rawBody) {
122
+ if (!this.webhookSecret)
123
+ throw new TokenError('webhook non registrato');
124
+ const ts = String(headers['x-suite-timestamp'] ?? '');
125
+ const sig = String(headers['x-suite-signature'] ?? '').replace(/^sha256=/, '');
126
+ const age = Math.abs(Date.now() / 1000 - Number(ts));
127
+ if (!ts || !Number.isFinite(age) || age > WEBHOOK_TOLERANCE_SEC) {
128
+ throw new TokenError('timestamp del webhook non valido');
129
+ }
130
+ const expected = createHmac('sha256', this.webhookSecret).update(`${ts}.${rawBody}`).digest();
131
+ const given = Buffer.from(sig, 'hex');
132
+ if (given.length !== expected.length || !timingSafeEqual(given, expected)) {
133
+ throw new TokenError('firma del webhook non valida');
134
+ }
135
+ return JSON.parse(rawBody);
136
+ }
137
+ /** Config LLM dell'organizzazione, con cache e deduplica delle richieste concorrenti. */
138
+ async llmConfig(orgId) {
139
+ const hit = this.cache.get(orgId);
140
+ if (hit && hit.expires > Date.now())
141
+ return hit.value;
142
+ const pending = this.inflight.get(orgId);
143
+ if (pending)
144
+ return pending;
145
+ const p = this.internal('GET', `/llm-config?org_id=${encodeURIComponent(orgId)}`)
146
+ .then((value) => {
147
+ this.cache.set(orgId, {
148
+ value,
149
+ expires: Date.now() + (this.opts.llmConfigTtl ?? 300) * 1000,
150
+ });
151
+ return value;
152
+ })
153
+ .finally(() => this.inflight.delete(orgId));
154
+ this.inflight.set(orgId, p);
155
+ return p;
156
+ }
157
+ /** Invalida la cache (di un'org o tutta). */
158
+ invalidate(orgId) {
159
+ if (orgId)
160
+ this.cache.delete(orgId);
161
+ else
162
+ this.cache.clear();
163
+ }
164
+ /** Modello per un tipo di task, con fallback. */
165
+ async modelFor(orgId, taskType) {
166
+ const cfg = await this.llmConfig(orgId);
167
+ const m = cfg.task_mapping.find((t) => t.task_type === taskType);
168
+ if (!m)
169
+ return undefined;
170
+ return { modelId: m.model_id, fallbackModelId: m.fallback_model_id };
171
+ }
172
+ introspect(token) {
173
+ return this.internal('POST', '/introspect', { token });
174
+ }
175
+ members(orgId) {
176
+ return this.internal('GET', `/orgs/${encodeURIComponent(orgId)}/members`);
177
+ }
178
+ /**
179
+ * Token exchange (RFC 8693) per chiamare un altro servizio per conto dell'utente:
180
+ * un JWT con aud di questo servizio diventa un JWT con aud del servizio di destinazione.
181
+ */
182
+ async exchangeToken(input) {
183
+ if (!this.opts.clientId || !this.opts.clientSecret) {
184
+ throw new SuiteError('clientId/clientSecret necessari per il token exchange');
185
+ }
186
+ const d = await this.getDiscovery();
187
+ const body = new URLSearchParams({
188
+ grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
189
+ subject_token: input.subjectToken,
190
+ subject_token_type: input.subjectToken.split('.').length === 3
191
+ ? 'urn:ietf:params:oauth:token-type:jwt'
192
+ : 'urn:ietf:params:oauth:token-type:access_token',
193
+ audience: input.audience,
194
+ });
195
+ if (input.orgId)
196
+ body.set('org_id', input.orgId);
197
+ const basic = Buffer.from(`${encodeURIComponent(this.opts.clientId)}:${encodeURIComponent(this.opts.clientSecret)}`).toString('base64');
198
+ const res = await this.doFetch(d.token_endpoint, {
199
+ method: 'POST',
200
+ headers: {
201
+ 'content-type': 'application/x-www-form-urlencoded',
202
+ authorization: `Basic ${basic}`,
203
+ },
204
+ body,
205
+ });
206
+ const json = (await res.json());
207
+ if (!res.ok || !json.access_token) {
208
+ throw new SuiteError(`token exchange fallito: ${json.error ?? res.status}`, res.status, json);
209
+ }
210
+ return { accessToken: json.access_token, expiresIn: json.expires_in ?? 0 };
211
+ }
212
+ }
213
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { suiteAuth, type SuiteAuthOptions } from './plugin.js';
2
+ export { SuiteClient, SuiteError, TokenError, type LlmConfig, type OrgMember, type PermissionDefinition, type SuiteAuth, type SuiteClientOptions, } from './client.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { suiteAuth } from './plugin.js';
2
+ export { SuiteClient, SuiteError, TokenError, } from './client.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAyB,MAAM,aAAa,CAAC;AAC/D,OAAO,EACL,WAAW,EACX,UAAU,EACV,UAAU,GAMX,MAAM,aAAa,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { FastifyPluginAsync, preHandlerAsyncHookHandler } from 'fastify';
2
+ import { SuiteClient, type PermissionDefinition, type SuiteAuth, type SuiteClientOptions } from './client.js';
3
+ export interface SuiteAuthOptions extends SuiteClientOptions {
4
+ /** Permessi del servizio, registrati all'avvio con upsert idempotente. */
5
+ permissions?: PermissionDefinition[];
6
+ /**
7
+ * URL pubblico (raggiungibile da Suite) dell'endpoint di invalidazione. Se valorizzato,
8
+ * all'avvio il plugin lo registra presso Suite (la route su `webhookPath` è sempre montata).
9
+ */
10
+ webhookUrl?: string;
11
+ /**
12
+ * Path locale dell'endpoint di invalidazione, montato sempre dal plugin
13
+ * (risponde 401 finché il webhook non è registrato). Default: /internal/config-changed.
14
+ * `false` per non montarlo.
15
+ */
16
+ webhookPath?: string | false;
17
+ /** Se false, un errore di registrazione (permessi/webhook) viene solo loggato. Default: true. */
18
+ failOnRegisterError?: boolean;
19
+ }
20
+ declare module 'fastify' {
21
+ interface FastifyInstance {
22
+ suite: SuiteClient;
23
+ /** preHandler: valida il bearer JWT e popola `req.auth`. */
24
+ authenticate: preHandlerAsyncHookHandler;
25
+ /** preHandler: autentica e richiede il permesso indicato (nel token scoped del servizio). */
26
+ requirePermission: (...permissions: string[]) => preHandlerAsyncHookHandler;
27
+ }
28
+ interface FastifyRequest {
29
+ auth: SuiteAuth;
30
+ }
31
+ }
32
+ export declare const suiteAuth: FastifyPluginAsync<SuiteAuthOptions>;
package/dist/plugin.js ADDED
@@ -0,0 +1,80 @@
1
+ import fp from 'fastify-plugin';
2
+ import { SuiteClient, TokenError, } from './client.js';
3
+ function unauthorized(reply, message) {
4
+ return reply
5
+ .code(401)
6
+ .header('www-authenticate', 'Bearer error="invalid_token"')
7
+ .send({ error: 'unauthorized', message });
8
+ }
9
+ const plugin = async (app, opts) => {
10
+ const client = new SuiteClient(opts);
11
+ app.decorate('suite', client);
12
+ app.decorateRequest('auth', null);
13
+ async function authenticate(req, reply) {
14
+ const header = req.headers.authorization;
15
+ if (!header?.startsWith('Bearer '))
16
+ return unauthorized(reply, 'bearer token mancante');
17
+ try {
18
+ req.auth = await client.verifyToken(header.slice(7));
19
+ }
20
+ catch (err) {
21
+ if (err instanceof TokenError)
22
+ return unauthorized(reply, err.message);
23
+ throw err;
24
+ }
25
+ }
26
+ app.decorate('authenticate', authenticate);
27
+ app.decorate('requirePermission', (...required) => {
28
+ return async function (req, reply) {
29
+ if (!req.auth) {
30
+ await authenticate(req, reply);
31
+ if (reply.sent)
32
+ return;
33
+ }
34
+ const missing = required.filter((p) => !req.auth.permissions.includes(p));
35
+ if (missing.length) {
36
+ return reply
37
+ .code(403)
38
+ .send({ error: 'forbidden', message: `permesso richiesto: ${missing.join(', ')}` });
39
+ }
40
+ };
41
+ });
42
+ if (opts.webhookPath !== false) {
43
+ const path = opts.webhookPath ?? '/internal/config-changed';
44
+ await app.register(async (child) => {
45
+ // La firma HMAC è calcolata sul body grezzo.
46
+ child.addContentTypeParser('application/json', { parseAs: 'string' }, (_req, body, done) => done(null, body));
47
+ child.post(path, async (req, reply) => {
48
+ try {
49
+ const event = client.verifyWebhook(req.headers, String(req.body ?? ''));
50
+ client.invalidate(event.org_id);
51
+ return reply.code(204).send();
52
+ }
53
+ catch (err) {
54
+ if (err instanceof TokenError || err instanceof SyntaxError) {
55
+ return reply.code(401).send({ error: 'unauthorized', message: err.message });
56
+ }
57
+ throw err;
58
+ }
59
+ });
60
+ });
61
+ }
62
+ // Registrazione all'avvio: permessi e webhook. Senza service key non c'è nulla da registrare.
63
+ app.addHook('onReady', async () => {
64
+ if (!opts.serviceKey)
65
+ return;
66
+ try {
67
+ if (opts.permissions?.length)
68
+ await client.registerPermissions(opts.permissions);
69
+ if (opts.webhookUrl)
70
+ await client.registerWebhook(opts.webhookUrl);
71
+ }
72
+ catch (err) {
73
+ if (opts.failOnRegisterError ?? true)
74
+ throw err;
75
+ app.log.warn({ err }, 'registrazione presso Suite fallita');
76
+ }
77
+ });
78
+ };
79
+ export const suiteAuth = fp(plugin, { name: '@babelpeak/babel-suite-server-sdk', fastify: '5.x' });
80
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@babelpeak/babel-suite-server-sdk",
3
+ "version": "0.1.0",
4
+ "description": "SDK Node per integrare un servizio con Babel Suite: auth JWT, permessi, config LLM",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Babel-Peak/babel-suite.git",
9
+ "directory": "packages/suite-server"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "type": "module",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "@babel-suite/source": "./src/index.ts",
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.build.json",
29
+ "typecheck": "tsc --noEmit",
30
+ "test": "vitest run"
31
+ },
32
+ "peerDependencies": {
33
+ "fastify": "^5.0.0"
34
+ },
35
+ "dependencies": {
36
+ "fastify-plugin": "^6.0.0",
37
+ "jose": "^6.2.12"
38
+ },
39
+ "devDependencies": {
40
+ "fastify": "^5.12.5"
41
+ }
42
+ }