@caronte-sdk/node 0.2.1 → 0.2.3
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.
- package/README.md +17 -0
- package/bin/caronte-node.js +62 -0
- package/dist/adapters/apollo.cjs +18 -3
- package/dist/adapters/apollo.cjs.map +1 -1
- package/dist/adapters/apollo.d.cts +1 -1
- package/dist/adapters/apollo.d.ts +1 -1
- package/dist/adapters/apollo.js +15 -4
- package/dist/adapters/apollo.js.map +1 -1
- package/dist/adapters/express.cjs +10 -0
- package/dist/adapters/express.cjs.map +1 -1
- package/dist/adapters/express.d.cts +1 -1
- package/dist/adapters/express.d.ts +1 -1
- package/dist/adapters/express.js +7 -1
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fastify.cjs +10 -1
- package/dist/adapters/fastify.cjs.map +1 -1
- package/dist/adapters/fastify.d.cts +1 -1
- package/dist/adapters/fastify.d.ts +1 -1
- package/dist/adapters/fastify.js +10 -1
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/nest/index.cjs +58 -1
- package/dist/adapters/nest/index.cjs.map +1 -1
- package/dist/adapters/nest/index.d.cts +1 -1
- package/dist/adapters/nest/index.d.ts +1 -1
- package/dist/adapters/nest/index.js +12 -2
- package/dist/adapters/nest/index.js.map +1 -1
- package/dist/{chunk-XMA2NAR7.js → chunk-M46BRRWA.js} +50 -3
- package/dist/chunk-M46BRRWA.js.map +1 -0
- package/dist/{client-TcOSwgrn.d.cts → client-CSokWFYn.d.cts} +3 -0
- package/dist/{client-TcOSwgrn.d.ts → client-CSokWFYn.d.ts} +3 -0
- package/dist/index.cjs +48 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +1 -1
- package/package.json +18 -5
- package/dist/chunk-XMA2NAR7.js.map +0 -1
|
@@ -2,7 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
var common = require('@nestjs/common');
|
|
4
4
|
var core = require('@nestjs/core');
|
|
5
|
+
var api = require('@opentelemetry/api');
|
|
5
6
|
var jose = require('jose');
|
|
7
|
+
var sdkNode = require('@opentelemetry/sdk-node');
|
|
8
|
+
var exporterTraceOtlpHttp = require('@opentelemetry/exporter-trace-otlp-http');
|
|
9
|
+
var exporterMetricsOtlpHttp = require('@opentelemetry/exporter-metrics-otlp-http');
|
|
10
|
+
var exporterLogsOtlpHttp = require('@opentelemetry/exporter-logs-otlp-http');
|
|
11
|
+
var sdkMetrics = require('@opentelemetry/sdk-metrics');
|
|
12
|
+
var sdkLogs = require('@opentelemetry/sdk-logs');
|
|
13
|
+
var autoInstrumentationsNode = require('@opentelemetry/auto-instrumentations-node');
|
|
14
|
+
var resources = require('@opentelemetry/resources');
|
|
15
|
+
var semanticConventions = require('@opentelemetry/semantic-conventions');
|
|
16
|
+
var apiLogs = require('@opentelemetry/api-logs');
|
|
6
17
|
|
|
7
18
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
19
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
@@ -59,6 +70,38 @@ var CaronteSyncError = class extends CaronteError {
|
|
|
59
70
|
};
|
|
60
71
|
var CaronteConfigError = class extends CaronteError {
|
|
61
72
|
};
|
|
73
|
+
var started = false;
|
|
74
|
+
function initOtel(options) {
|
|
75
|
+
if (started) return;
|
|
76
|
+
try {
|
|
77
|
+
const base = options.otelCollectorUrl.replace(/\/$/, "");
|
|
78
|
+
const resource = resources.defaultResource().merge(resources.resourceFromAttributes({
|
|
79
|
+
[semanticConventions.ATTR_SERVICE_NAME]: options.appId,
|
|
80
|
+
[semanticConventions.ATTR_SERVICE_NAMESPACE]: options.realmId
|
|
81
|
+
}));
|
|
82
|
+
const sdk = new sdkNode.NodeSDK({
|
|
83
|
+
resource,
|
|
84
|
+
traceExporter: new exporterTraceOtlpHttp.OTLPTraceExporter({ url: `${base}/v1/traces` }),
|
|
85
|
+
metricReaders: [
|
|
86
|
+
new sdkMetrics.PeriodicExportingMetricReader({
|
|
87
|
+
exporter: new exporterMetricsOtlpHttp.OTLPMetricExporter({ url: `${base}/v1/metrics` })
|
|
88
|
+
})
|
|
89
|
+
],
|
|
90
|
+
logRecordProcessors: [
|
|
91
|
+
new sdkLogs.BatchLogRecordProcessor({ exporter: new exporterLogsOtlpHttp.OTLPLogExporter({ url: `${base}/v1/logs` }) })
|
|
92
|
+
],
|
|
93
|
+
instrumentations: [autoInstrumentationsNode.getNodeAutoInstrumentations()]
|
|
94
|
+
});
|
|
95
|
+
sdk.start();
|
|
96
|
+
started = true;
|
|
97
|
+
apiLogs.logs.getLogger("caronte-sdk").emit({
|
|
98
|
+
severityNumber: apiLogs.SeverityNumber.INFO,
|
|
99
|
+
body: `caronte-sdk OTel initialized for app_id=${options.appId} realm_id=${options.realmId}`
|
|
100
|
+
});
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.warn(`[caronte-sdk] OTel initialization skipped: ${err.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
62
105
|
|
|
63
106
|
// src/client.ts
|
|
64
107
|
var CaronteClient = class _CaronteClient {
|
|
@@ -66,6 +109,7 @@ var CaronteClient = class _CaronteClient {
|
|
|
66
109
|
realmId;
|
|
67
110
|
appId;
|
|
68
111
|
secret;
|
|
112
|
+
otelCollectorUrl;
|
|
69
113
|
appToken = null;
|
|
70
114
|
jwks = null;
|
|
71
115
|
operations = [];
|
|
@@ -75,6 +119,7 @@ var CaronteClient = class _CaronteClient {
|
|
|
75
119
|
this.realmId = options.realmId;
|
|
76
120
|
this.appId = options.appId;
|
|
77
121
|
this.secret = options.secret;
|
|
122
|
+
this.otelCollectorUrl = options.otelCollectorUrl;
|
|
78
123
|
}
|
|
79
124
|
get realmBase() {
|
|
80
125
|
return `${this.base}/realms/${this.realmId}`;
|
|
@@ -88,6 +133,9 @@ var CaronteClient = class _CaronteClient {
|
|
|
88
133
|
"No operations registered. Call registerOperation() before startup()."
|
|
89
134
|
);
|
|
90
135
|
}
|
|
136
|
+
if (this.otelCollectorUrl) {
|
|
137
|
+
initOtel({ otelCollectorUrl: this.otelCollectorUrl, appId: this.appId, realmId: this.realmId });
|
|
138
|
+
}
|
|
91
139
|
await this.authenticate();
|
|
92
140
|
this.initJwks();
|
|
93
141
|
await this.syncOperations();
|
|
@@ -221,20 +269,29 @@ exports.CaronteGuard = class CaronteGuard {
|
|
|
221
269
|
if (!meta) return true;
|
|
222
270
|
const { id, level, method } = meta;
|
|
223
271
|
const request = ctx.switchToHttp().getRequest();
|
|
224
|
-
|
|
272
|
+
const span = api.trace.getActiveSpan();
|
|
273
|
+
span?.setAttribute("caronte.operation_id", id);
|
|
274
|
+
if (level === "public") {
|
|
275
|
+
span?.setAttribute("caronte.permission_result", "public");
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
225
278
|
const auth = request.headers["authorization"] ?? "";
|
|
226
279
|
const [scheme, token] = auth.split(" ");
|
|
227
280
|
if (scheme?.toLowerCase() !== "bearer" || !token) {
|
|
281
|
+
span?.setAttribute("caronte.permission_result", "unauthorized");
|
|
228
282
|
throw new common.UnauthorizedException("Bearer token required.");
|
|
229
283
|
}
|
|
230
284
|
let claims;
|
|
231
285
|
try {
|
|
232
286
|
claims = await this.client.validateToken(token);
|
|
233
287
|
} catch {
|
|
288
|
+
span?.setAttribute("caronte.permission_result", "unauthorized");
|
|
234
289
|
throw new common.UnauthorizedException("Invalid token.");
|
|
235
290
|
}
|
|
236
291
|
request.caronteUser = claims;
|
|
292
|
+
span?.setAttribute("caronte.user", claims.sub);
|
|
237
293
|
const allowed = this.client.checkPermission(claims, id, method);
|
|
294
|
+
span?.setAttribute("caronte.permission_result", allowed ? "allowed" : "forbidden");
|
|
238
295
|
if (!allowed) throw new common.ForbiddenException("Insufficient permissions.");
|
|
239
296
|
return true;
|
|
240
297
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/registry.ts","../../../src/adapters/nest/decorator.ts","../../../src/exceptions.ts","../../../src/client.ts","../../../src/adapters/nest/guard.ts","../../../src/adapters/nest/module.ts"],"names":["SetMetadata","createParamDecorator","createRemoteJWKSet","jwtVerify","CaronteGuard","UnauthorizedException","ForbiddenException","Injectable","Reflector","CaronteModule","Module"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { OperationDescriptor } from './models.js';\n\nconst _registry: OperationDescriptor[] = [];\n\n/** Infer operation method from the last segment of the operation id.\n * e.g. \"tasks:list\" → \"read\", \"tasks:create\" → \"write\", \"tasks:delete\" → \"delete\"\n */\nexport function detectMethod(id: string): string {\n const last = (id.split(':').pop() ?? '').toLowerCase();\n if (/^(get|list|fetch|read)/.test(last)) return 'read';\n if (/^(delete|remove|destroy)/.test(last)) return 'delete';\n if (/(stream|subscribe|watch|listen)/.test(last)) return 'stream';\n return 'write';\n}\n\n/** Register an operation in the global registry (used by startup to sync). */\nexport function registerOperation(op: OperationDescriptor): void {\n const exists = _registry.some(\n o => o.identifier === op.identifier && o.method === op.method,\n );\n if (!exists) _registry.push(op);\n}\n\nexport function getRegistry(): OperationDescriptor[] {\n return [..._registry];\n}\n\nexport function clearRegistry(): void {\n _registry.length = 0;\n}","import { createParamDecorator, ExecutionContext, SetMetadata } from '@nestjs/common';\nimport type { TokenClaims } from '../../models.js';\nimport { detectMethod, registerOperation } from '../../registry.js';\n\nexport const OPERATION_KEY = 'caronte:operation';\n\nexport interface OperationMeta {\n id: string;\n level: string;\n method: string;\n}\n\n/**\n * Marks a controller method as a caronte operation.\n *\n * @example\n * ```ts\n * @Get()\n * @Operation('tasks:list', 'private')\n * listTasks(@CaronteUser() user: TokenClaims) { ... }\n * ```\n */\nexport function Operation(id: string, level: string, method?: string): MethodDecorator {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n return SetMetadata<string, OperationMeta>(OPERATION_KEY, { id, level, method: resolvedMethod });\n}\n\n/** Injects the authenticated user's TokenClaims into a parameter. */\nexport const CaronteUser = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext): TokenClaims | undefined =>\n ctx.switchToHttp().getRequest().caronteUser,\n);","export class CaronteError extends Error {\n constructor(message: string) {\n super(message);\n this.name = this.constructor.name;\n }\n}\n\nexport class CaronteAuthError extends CaronteError {}\nexport class CaronteTokenError extends CaronteError {}\nexport class CaronteForbiddenError extends CaronteError {}\nexport class CaronteSyncError extends CaronteError {}\nexport class CaronteConfigError extends CaronteError {}","import { createRemoteJWKSet, jwtVerify, type JWTPayload } from 'jose';\nimport {\n CaronteAuthError, CaronteConfigError, CaronteError,\n CaronteSyncError, CaronteTokenError,\n} from './exceptions.js';\nimport type { OperationDescriptor, OperationWithGroups, TokenClaims } from './models.js';\nimport { getRegistry } from './registry.js';\n\nexport interface CaronteClientOptions {\n authorizerUrl: string;\n realmId: string;\n appId: string;\n secret: string;\n}\n\nexport class CaronteClient {\n private readonly base: string;\n private readonly realmId: string;\n private readonly appId: string;\n private readonly secret: string;\n\n private appToken: string | null = null;\n private jwks: ReturnType<typeof createRemoteJWKSet> | null = null;\n private operations: OperationWithGroups[] = [];\n private registeredOperations: OperationDescriptor[] = [];\n\n constructor(options: CaronteClientOptions) {\n this.base = options.authorizerUrl.replace(/\\/$/, '');\n this.realmId = options.realmId;\n this.appId = options.appId;\n this.secret = options.secret;\n }\n\n private get realmBase(): string {\n return `${this.base}/realms/${this.realmId}`;\n }\n\n registerOperation(op: OperationDescriptor): void {\n this.registeredOperations.push(op);\n }\n\n async startup(): Promise<void> {\n if (this.registeredOperations.length === 0) {\n throw new CaronteConfigError(\n 'No operations registered. Call registerOperation() before startup().',\n );\n }\n await this.authenticate();\n this.initJwks();\n await this.syncOperations();\n await this.fetchOperations();\n }\n\n async authenticate(): Promise<void> {\n const res = await fetch(`${this.realmBase}/apps/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ app_id: this.appId, secret: this.secret }),\n });\n if (!res.ok) {\n throw new CaronteAuthError(\n `Authentication failed: HTTP ${res.status} — ${await res.text()}`,\n );\n }\n const data = await res.json() as { access_token?: string };\n if (!data.access_token) throw new CaronteAuthError('Authorizer returned no access_token.');\n this.appToken = data.access_token;\n }\n\n private initJwks(): void {\n this.jwks = createRemoteJWKSet(\n new URL(`${this.realmBase}/protocol/openid-connect/certs`),\n );\n }\n\n async syncOperations(): Promise<void> {\n this.ensureAppToken();\n const res = await fetch(`${this.realmBase}/apps/operations/sync`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.appToken}`,\n },\n body: JSON.stringify({\n operations: this.registeredOperations.map(op => ({\n identifier: op.identifier,\n method: op.method,\n level: op.level,\n description: op.description ?? null,\n })),\n }),\n });\n if (!res.ok) {\n throw new CaronteSyncError(`Sync failed: HTTP ${res.status} — ${await res.text()}`);\n }\n }\n\n async fetchOperations(): Promise<void> {\n this.ensureAppToken();\n const res = await fetch(`${this.realmBase}/apps/operations`, {\n headers: { Authorization: `Bearer ${this.appToken}` },\n });\n if (!res.ok) {\n throw new CaronteError(\n `Fetch operations failed: HTTP ${res.status} — ${await res.text()}`,\n );\n }\n const data = await res.json() as { operations: any[] };\n this.operations = data.operations.map(op => ({\n operationId: op.operation_id,\n identifier: op.identifier,\n method: op.method,\n level: op.level,\n allowedGroups: op.allowed_groups ?? [],\n }));\n }\n\n async validateToken(token: string): Promise<TokenClaims> {\n if (!this.jwks) this.initJwks();\n try {\n const { payload } = await jwtVerify(token, this.jwks!);\n const p = payload as JWTPayload & Record<string, unknown>;\n const tokenRealm = (p['realm_id'] as string) ?? '';\n\n // As chaves JWKS já são buscadas em /realms/{this.realmId}/... (ver\n // realmBase), então um token de outro realm normalmente já falharia a\n // verificação de assinatura acima. Esta checagem é defesa em\n // profundidade explícita — não depende desse efeito colateral, e falha\n // com uma mensagem clara em vez de erro de assinatura.\n if (tokenRealm.toLowerCase() !== this.realmId.toLowerCase()) {\n throw new CaronteTokenError(\n `Token realm '${tokenRealm}' does not match this app's realm '${this.realmId}'.`,\n );\n }\n\n return {\n sub: payload.sub ?? '',\n realmId: tokenRealm,\n groups: (p['groups'] as string[]) ?? [],\n exp: payload.exp ?? 0,\n raw: p,\n };\n } catch (err) {\n if (err instanceof CaronteTokenError) throw err;\n throw new CaronteTokenError(`Invalid token: ${(err as Error).message}`);\n }\n }\n\n checkPermission(claims: TokenClaims, identifier: string, method: string): boolean {\n const op = this.operations.find(\n o => o.identifier === identifier && o.method === method,\n );\n if (!op) return false;\n if (op.level === 'public') return true;\n if (op.level === 'private') return claims.groups.length > 0;\n if (op.level === 'protected') return claims.groups.some(g => op.allowedGroups.includes(g));\n return false;\n }\n\n /** Load operations from the global registry and bootstrap the client. */\n static async create(options: CaronteClientOptions): Promise<CaronteClient> {\n const client = new CaronteClient(options);\n for (const op of getRegistry()) client.registerOperation(op);\n await client.startup();\n return client;\n }\n\n private ensureAppToken(): void {\n if (!this.appToken) {\n throw new CaronteAuthError('No app token. Call authenticate() or startup() first.');\n }\n }\n}","import {\n CanActivate, ExecutionContext, ForbiddenException,\n Inject, Injectable, UnauthorizedException,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { CaronteClient } from '../../client.js';\nimport type { TokenClaims } from '../../models.js';\nimport { OPERATION_KEY, type OperationMeta } from './decorator.js';\n\n@Injectable()\nexport class CaronteGuard implements CanActivate {\n // @Inject() explícito -- não depender de emitDecoratorMetadata (design:paramtypes)\n // pra resolver os parâmetros: o build desta lib usa tsup/esbuild, que não emite\n // essa metadata (limitação conhecida do esbuild), então a injeção implícita por\n // tipo do Nest falha silenciosamente (reflector fica undefined em runtime,\n // TypeError em qualquer rota guardada por CaronteGuard).\n constructor(\n @Inject(CaronteClient) private readonly client: CaronteClient,\n @Inject(Reflector) private readonly reflector: Reflector,\n ) {}\n\n async canActivate(ctx: ExecutionContext): Promise<boolean> {\n const meta = this.reflector.get<OperationMeta | undefined>(\n OPERATION_KEY,\n ctx.getHandler(),\n );\n\n if (!meta) return true; // no @Operation — pass through\n\n const { id, level, method } = meta;\n const request = ctx.switchToHttp().getRequest<{ headers: Record<string, string>; caronteUser?: TokenClaims }>();\n\n if (level === 'public') return true;\n\n const auth = request.headers['authorization'] ?? '';\n const [scheme, token] = auth.split(' ');\n\n if (scheme?.toLowerCase() !== 'bearer' || !token) {\n throw new UnauthorizedException('Bearer token required.');\n }\n\n let claims: TokenClaims;\n try {\n claims = await this.client.validateToken(token);\n } catch {\n // Token malformado/expirado/inválido -- 401, igual aos adapters express/\n // fastify (que engolem o erro de validateToken pelo mesmo motivo), não\n // deixar virar 500 por exceção não tratada.\n throw new UnauthorizedException('Invalid token.');\n }\n request.caronteUser = claims;\n\n const allowed = this.client.checkPermission(claims, id, method);\n if (!allowed) throw new ForbiddenException('Insufficient permissions.');\n\n return true;\n }\n}","import { DynamicModule, Module } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { CaronteClient, type CaronteClientOptions } from '../../client.js';\nimport { getRegistry } from '../../registry.js';\nimport { CaronteGuard } from './guard.js';\n\nexport interface CaronteModuleOptions extends CaronteClientOptions {}\n\n@Module({})\nexport class CaronteModule {\n /**\n * Register the caronte module globally.\n *\n * Creates the `CaronteClient`, bootstraps it (authenticate + sync + fetch),\n * and provides `CaronteGuard` for injection.\n *\n * @example\n * ```ts\n * // app.module.ts\n * @Module({\n * imports: [\n * CaronteModule.forRootAsync({\n * authorizerUrl: process.env.AUTHORIZER_URL!,\n * realmId: process.env.REALM_ID!,\n * appId: process.env.APP_ID!,\n * secret: process.env.APP_SECRET!,\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRootAsync(options: CaronteModuleOptions): DynamicModule {\n return {\n module: CaronteModule,\n global: true,\n providers: [\n Reflector,\n {\n provide: CaronteClient,\n useFactory: async () => {\n const client = new CaronteClient(options);\n for (const op of getRegistry()) client.registerOperation(op);\n await client.startup();\n return client;\n },\n },\n CaronteGuard,\n ],\n exports: [CaronteClient, CaronteGuard],\n };\n }\n}"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/registry.ts","../../../src/adapters/nest/decorator.ts","../../../src/exceptions.ts","../../../src/otel.ts","../../../src/client.ts","../../../src/adapters/nest/guard.ts","../../../src/adapters/nest/module.ts"],"names":["SetMetadata","createParamDecorator","defaultResource","resourceFromAttributes","ATTR_SERVICE_NAME","ATTR_SERVICE_NAMESPACE","NodeSDK","OTLPTraceExporter","PeriodicExportingMetricReader","OTLPMetricExporter","BatchLogRecordProcessor","OTLPLogExporter","getNodeAutoInstrumentations","logs","SeverityNumber","createRemoteJWKSet","jwtVerify","CaronteGuard","trace","UnauthorizedException","ForbiddenException","Injectable","Reflector","CaronteModule","Module"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { OperationDescriptor } from './models.js';\n\nconst _registry: OperationDescriptor[] = [];\n\n/** Infer operation method from the last segment of the operation id.\n * e.g. \"tasks:list\" → \"read\", \"tasks:create\" → \"write\", \"tasks:delete\" → \"delete\"\n */\nexport function detectMethod(id: string): string {\n const last = (id.split(':').pop() ?? '').toLowerCase();\n if (/^(get|list|fetch|read)/.test(last)) return 'read';\n if (/^(delete|remove|destroy)/.test(last)) return 'delete';\n if (/(stream|subscribe|watch|listen)/.test(last)) return 'stream';\n return 'write';\n}\n\n/** Register an operation in the global registry (used by startup to sync). */\nexport function registerOperation(op: OperationDescriptor): void {\n const exists = _registry.some(\n o => o.identifier === op.identifier && o.method === op.method,\n );\n if (!exists) _registry.push(op);\n}\n\nexport function getRegistry(): OperationDescriptor[] {\n return [..._registry];\n}\n\nexport function clearRegistry(): void {\n _registry.length = 0;\n}","import { createParamDecorator, ExecutionContext, SetMetadata } from '@nestjs/common';\nimport type { TokenClaims } from '../../models.js';\nimport { detectMethod, registerOperation } from '../../registry.js';\n\nexport const OPERATION_KEY = 'caronte:operation';\n\nexport interface OperationMeta {\n id: string;\n level: string;\n method: string;\n}\n\n/**\n * Marks a controller method as a caronte operation.\n *\n * @example\n * ```ts\n * @Get()\n * @Operation('tasks:list', 'private')\n * listTasks(@CaronteUser() user: TokenClaims) { ... }\n * ```\n */\nexport function Operation(id: string, level: string, method?: string): MethodDecorator {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n return SetMetadata<string, OperationMeta>(OPERATION_KEY, { id, level, method: resolvedMethod });\n}\n\n/** Injects the authenticated user's TokenClaims into a parameter. */\nexport const CaronteUser = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext): TokenClaims | undefined =>\n ctx.switchToHttp().getRequest().caronteUser,\n);","export class CaronteError extends Error {\n constructor(message: string) {\n super(message);\n this.name = this.constructor.name;\n }\n}\n\nexport class CaronteAuthError extends CaronteError {}\nexport class CaronteTokenError extends CaronteError {}\nexport class CaronteForbiddenError extends CaronteError {}\nexport class CaronteSyncError extends CaronteError {}\nexport class CaronteConfigError extends CaronteError {}","import { NodeSDK } from '@opentelemetry/sdk-node';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';\nimport { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';\nimport { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';\nimport { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';\nimport { ATTR_SERVICE_NAME, ATTR_SERVICE_NAMESPACE } from '@opentelemetry/semantic-conventions';\nimport { logs, SeverityNumber } from '@opentelemetry/api-logs';\n\nexport interface OtelInitOptions {\n otelCollectorUrl: string;\n appId: string;\n realmId: string;\n}\n\nlet started = false;\n\n/**\n * Best-effort OTel bootstrap: a misconfigured or unreachable collector must\n * never break CaronteClient.startup() (auth/JWKS still have to work), so\n * every failure here is swallowed. Guarded by `started` because NodeSDK is a\n * process-wide singleton — re-running .start() would double-register the\n * auto-instrumentations' require hooks.\n */\nexport function initOtel(options: OtelInitOptions): void {\n if (started) return;\n try {\n const base = options.otelCollectorUrl.replace(/\\/$/, '');\n const resource = defaultResource().merge(resourceFromAttributes({\n [ATTR_SERVICE_NAME]: options.appId,\n [ATTR_SERVICE_NAMESPACE]: options.realmId,\n }));\n\n const sdk = new NodeSDK({\n resource,\n traceExporter: new OTLPTraceExporter({ url: `${base}/v1/traces` }),\n metricReaders: [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({ url: `${base}/v1/metrics` }),\n }),\n ],\n logRecordProcessors: [\n new BatchLogRecordProcessor({ exporter: new OTLPLogExporter({ url: `${base}/v1/logs` }) }),\n ],\n instrumentations: [getNodeAutoInstrumentations()],\n });\n sdk.start();\n started = true;\n\n // Auto-instrumentation only yields traces (and, from real HTTP traffic,\n // metrics) — nothing captures console output as OTel logs. Emit one\n // bootstrap record so the log pipeline (client -> Collector -> Loki) is\n // provably wired even before the app receives its first request.\n logs.getLogger('caronte-sdk').emit({\n severityNumber: SeverityNumber.INFO,\n body: `caronte-sdk OTel initialized for app_id=${options.appId} realm_id=${options.realmId}`,\n });\n } catch (err) {\n console.warn(`[caronte-sdk] OTel initialization skipped: ${(err as Error).message}`);\n }\n}\n","import { createRemoteJWKSet, jwtVerify, type JWTPayload } from 'jose';\nimport {\n CaronteAuthError, CaronteConfigError, CaronteError,\n CaronteSyncError, CaronteTokenError,\n} from './exceptions.js';\nimport type { OperationDescriptor, OperationWithGroups, TokenClaims } from './models.js';\nimport { initOtel } from './otel.js';\nimport { getRegistry } from './registry.js';\n\nexport interface CaronteClientOptions {\n authorizerUrl: string;\n realmId: string;\n appId: string;\n secret: string;\n /** Opt-in: URL of an OTLP collector (e.g. http://localhost:4318). Omit to skip OTel entirely. */\n otelCollectorUrl?: string;\n}\n\nexport class CaronteClient {\n private readonly base: string;\n private readonly realmId: string;\n private readonly appId: string;\n private readonly secret: string;\n private readonly otelCollectorUrl?: string;\n\n private appToken: string | null = null;\n private jwks: ReturnType<typeof createRemoteJWKSet> | null = null;\n private operations: OperationWithGroups[] = [];\n private registeredOperations: OperationDescriptor[] = [];\n\n constructor(options: CaronteClientOptions) {\n this.base = options.authorizerUrl.replace(/\\/$/, '');\n this.realmId = options.realmId;\n this.appId = options.appId;\n this.secret = options.secret;\n this.otelCollectorUrl = options.otelCollectorUrl;\n }\n\n private get realmBase(): string {\n return `${this.base}/realms/${this.realmId}`;\n }\n\n registerOperation(op: OperationDescriptor): void {\n this.registeredOperations.push(op);\n }\n\n async startup(): Promise<void> {\n if (this.registeredOperations.length === 0) {\n throw new CaronteConfigError(\n 'No operations registered. Call registerOperation() before startup().',\n );\n }\n if (this.otelCollectorUrl) {\n initOtel({ otelCollectorUrl: this.otelCollectorUrl, appId: this.appId, realmId: this.realmId });\n }\n await this.authenticate();\n this.initJwks();\n await this.syncOperations();\n await this.fetchOperations();\n }\n\n async authenticate(): Promise<void> {\n const res = await fetch(`${this.realmBase}/apps/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ app_id: this.appId, secret: this.secret }),\n });\n if (!res.ok) {\n throw new CaronteAuthError(\n `Authentication failed: HTTP ${res.status} — ${await res.text()}`,\n );\n }\n const data = await res.json() as { access_token?: string };\n if (!data.access_token) throw new CaronteAuthError('Authorizer returned no access_token.');\n this.appToken = data.access_token;\n }\n\n private initJwks(): void {\n this.jwks = createRemoteJWKSet(\n new URL(`${this.realmBase}/protocol/openid-connect/certs`),\n );\n }\n\n async syncOperations(): Promise<void> {\n this.ensureAppToken();\n const res = await fetch(`${this.realmBase}/apps/operations/sync`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.appToken}`,\n },\n body: JSON.stringify({\n operations: this.registeredOperations.map(op => ({\n identifier: op.identifier,\n method: op.method,\n level: op.level,\n description: op.description ?? null,\n })),\n }),\n });\n if (!res.ok) {\n throw new CaronteSyncError(`Sync failed: HTTP ${res.status} — ${await res.text()}`);\n }\n }\n\n async fetchOperations(): Promise<void> {\n this.ensureAppToken();\n const res = await fetch(`${this.realmBase}/apps/operations`, {\n headers: { Authorization: `Bearer ${this.appToken}` },\n });\n if (!res.ok) {\n throw new CaronteError(\n `Fetch operations failed: HTTP ${res.status} — ${await res.text()}`,\n );\n }\n const data = await res.json() as { operations: any[] };\n this.operations = data.operations.map(op => ({\n operationId: op.operation_id,\n identifier: op.identifier,\n method: op.method,\n level: op.level,\n allowedGroups: op.allowed_groups ?? [],\n }));\n }\n\n async validateToken(token: string): Promise<TokenClaims> {\n if (!this.jwks) this.initJwks();\n try {\n const { payload } = await jwtVerify(token, this.jwks!);\n const p = payload as JWTPayload & Record<string, unknown>;\n const tokenRealm = (p['realm_id'] as string) ?? '';\n\n // As chaves JWKS já são buscadas em /realms/{this.realmId}/... (ver\n // realmBase), então um token de outro realm normalmente já falharia a\n // verificação de assinatura acima. Esta checagem é defesa em\n // profundidade explícita — não depende desse efeito colateral, e falha\n // com uma mensagem clara em vez de erro de assinatura.\n if (tokenRealm.toLowerCase() !== this.realmId.toLowerCase()) {\n throw new CaronteTokenError(\n `Token realm '${tokenRealm}' does not match this app's realm '${this.realmId}'.`,\n );\n }\n\n return {\n sub: payload.sub ?? '',\n realmId: tokenRealm,\n groups: (p['groups'] as string[]) ?? [],\n exp: payload.exp ?? 0,\n raw: p,\n };\n } catch (err) {\n if (err instanceof CaronteTokenError) throw err;\n throw new CaronteTokenError(`Invalid token: ${(err as Error).message}`);\n }\n }\n\n checkPermission(claims: TokenClaims, identifier: string, method: string): boolean {\n const op = this.operations.find(\n o => o.identifier === identifier && o.method === method,\n );\n if (!op) return false;\n if (op.level === 'public') return true;\n if (op.level === 'private') return claims.groups.length > 0;\n if (op.level === 'protected') return claims.groups.some(g => op.allowedGroups.includes(g));\n return false;\n }\n\n /** Load operations from the global registry and bootstrap the client. */\n static async create(options: CaronteClientOptions): Promise<CaronteClient> {\n const client = new CaronteClient(options);\n for (const op of getRegistry()) client.registerOperation(op);\n await client.startup();\n return client;\n }\n\n private ensureAppToken(): void {\n if (!this.appToken) {\n throw new CaronteAuthError('No app token. Call authenticate() or startup() first.');\n }\n }\n}","import {\n CanActivate, ExecutionContext, ForbiddenException,\n Inject, Injectable, UnauthorizedException,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { trace } from '@opentelemetry/api';\nimport { CaronteClient } from '../../client.js';\nimport type { TokenClaims } from '../../models.js';\nimport { OPERATION_KEY, type OperationMeta } from './decorator.js';\n\n@Injectable()\nexport class CaronteGuard implements CanActivate {\n // @Inject() explícito -- não depender de emitDecoratorMetadata (design:paramtypes)\n // pra resolver os parâmetros: o build desta lib usa tsup/esbuild, que não emite\n // essa metadata (limitação conhecida do esbuild), então a injeção implícita por\n // tipo do Nest falha silenciosamente (reflector fica undefined em runtime,\n // TypeError em qualquer rota guardada por CaronteGuard).\n constructor(\n @Inject(CaronteClient) private readonly client: CaronteClient,\n @Inject(Reflector) private readonly reflector: Reflector,\n ) {}\n\n async canActivate(ctx: ExecutionContext): Promise<boolean> {\n const meta = this.reflector.get<OperationMeta | undefined>(\n OPERATION_KEY,\n ctx.getHandler(),\n );\n\n if (!meta) return true; // no @Operation — pass through\n\n const { id, level, method } = meta;\n const request = ctx.switchToHttp().getRequest<{ headers: Record<string, string>; caronteUser?: TokenClaims }>();\n const span = trace.getActiveSpan();\n span?.setAttribute('caronte.operation_id', id);\n\n if (level === 'public') {\n span?.setAttribute('caronte.permission_result', 'public');\n return true;\n }\n\n const auth = request.headers['authorization'] ?? '';\n const [scheme, token] = auth.split(' ');\n\n if (scheme?.toLowerCase() !== 'bearer' || !token) {\n span?.setAttribute('caronte.permission_result', 'unauthorized');\n throw new UnauthorizedException('Bearer token required.');\n }\n\n let claims: TokenClaims;\n try {\n claims = await this.client.validateToken(token);\n } catch {\n // Token malformado/expirado/inválido -- 401, igual aos adapters express/\n // fastify (que engolem o erro de validateToken pelo mesmo motivo), não\n // deixar virar 500 por exceção não tratada.\n span?.setAttribute('caronte.permission_result', 'unauthorized');\n throw new UnauthorizedException('Invalid token.');\n }\n request.caronteUser = claims;\n span?.setAttribute('caronte.user', claims.sub);\n\n const allowed = this.client.checkPermission(claims, id, method);\n span?.setAttribute('caronte.permission_result', allowed ? 'allowed' : 'forbidden');\n if (!allowed) throw new ForbiddenException('Insufficient permissions.');\n\n return true;\n }\n}","import { DynamicModule, Module } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { CaronteClient, type CaronteClientOptions } from '../../client.js';\nimport { getRegistry } from '../../registry.js';\nimport { CaronteGuard } from './guard.js';\n\nexport interface CaronteModuleOptions extends CaronteClientOptions {}\n\n@Module({})\nexport class CaronteModule {\n /**\n * Register the caronte module globally.\n *\n * Creates the `CaronteClient`, bootstraps it (authenticate + sync + fetch),\n * and provides `CaronteGuard` for injection.\n *\n * @example\n * ```ts\n * // app.module.ts\n * @Module({\n * imports: [\n * CaronteModule.forRootAsync({\n * authorizerUrl: process.env.AUTHORIZER_URL!,\n * realmId: process.env.REALM_ID!,\n * appId: process.env.APP_ID!,\n * secret: process.env.APP_SECRET!,\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRootAsync(options: CaronteModuleOptions): DynamicModule {\n return {\n module: CaronteModule,\n global: true,\n providers: [\n Reflector,\n {\n provide: CaronteClient,\n useFactory: async () => {\n const client = new CaronteClient(options);\n for (const op of getRegistry()) client.registerOperation(op);\n await client.startup();\n return client;\n },\n },\n CaronteGuard,\n ],\n exports: [CaronteClient, CaronteGuard],\n };\n }\n}"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CanActivate, ExecutionContext, DynamicModule } from '@nestjs/common';
|
|
2
2
|
import { Reflector } from '@nestjs/core';
|
|
3
|
-
import { C as CaronteClient, a as CaronteClientOptions } from '../../client-
|
|
3
|
+
import { C as CaronteClient, a as CaronteClientOptions } from '../../client-CSokWFYn.cjs';
|
|
4
4
|
|
|
5
5
|
declare const OPERATION_KEY = "caronte:operation";
|
|
6
6
|
interface OperationMeta {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CanActivate, ExecutionContext, DynamicModule } from '@nestjs/common';
|
|
2
2
|
import { Reflector } from '@nestjs/core';
|
|
3
|
-
import { C as CaronteClient, a as CaronteClientOptions } from '../../client-
|
|
3
|
+
import { C as CaronteClient, a as CaronteClientOptions } from '../../client-CSokWFYn.js';
|
|
4
4
|
|
|
5
5
|
declare const OPERATION_KEY = "caronte:operation";
|
|
6
6
|
interface OperationMeta {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { CaronteClient } from '../../chunk-
|
|
1
|
+
import { CaronteClient } from '../../chunk-M46BRRWA.js';
|
|
2
2
|
import '../../chunk-VEDVQOBD.js';
|
|
3
3
|
import { __decorateClass, __decorateParam, getRegistry, detectMethod, registerOperation } from '../../chunk-HV6X5RJY.js';
|
|
4
4
|
import { createParamDecorator, Injectable, Inject, Module, UnauthorizedException, ForbiddenException, SetMetadata } from '@nestjs/common';
|
|
5
5
|
import { Reflector } from '@nestjs/core';
|
|
6
|
+
import { trace } from '@opentelemetry/api';
|
|
6
7
|
|
|
7
8
|
var OPERATION_KEY = "caronte:operation";
|
|
8
9
|
function Operation(id, level, method) {
|
|
@@ -33,20 +34,29 @@ var CaronteGuard = class {
|
|
|
33
34
|
if (!meta) return true;
|
|
34
35
|
const { id, level, method } = meta;
|
|
35
36
|
const request = ctx.switchToHttp().getRequest();
|
|
36
|
-
|
|
37
|
+
const span = trace.getActiveSpan();
|
|
38
|
+
span?.setAttribute("caronte.operation_id", id);
|
|
39
|
+
if (level === "public") {
|
|
40
|
+
span?.setAttribute("caronte.permission_result", "public");
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
37
43
|
const auth = request.headers["authorization"] ?? "";
|
|
38
44
|
const [scheme, token] = auth.split(" ");
|
|
39
45
|
if (scheme?.toLowerCase() !== "bearer" || !token) {
|
|
46
|
+
span?.setAttribute("caronte.permission_result", "unauthorized");
|
|
40
47
|
throw new UnauthorizedException("Bearer token required.");
|
|
41
48
|
}
|
|
42
49
|
let claims;
|
|
43
50
|
try {
|
|
44
51
|
claims = await this.client.validateToken(token);
|
|
45
52
|
} catch {
|
|
53
|
+
span?.setAttribute("caronte.permission_result", "unauthorized");
|
|
46
54
|
throw new UnauthorizedException("Invalid token.");
|
|
47
55
|
}
|
|
48
56
|
request.caronteUser = claims;
|
|
57
|
+
span?.setAttribute("caronte.user", claims.sub);
|
|
49
58
|
const allowed = this.client.checkPermission(claims, id, method);
|
|
59
|
+
span?.setAttribute("caronte.permission_result", allowed ? "allowed" : "forbidden");
|
|
50
60
|
if (!allowed) throw new ForbiddenException("Insufficient permissions.");
|
|
51
61
|
return true;
|
|
52
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/nest/decorator.ts","../../../src/adapters/nest/guard.ts","../../../src/adapters/nest/module.ts"],"names":["Reflector"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/nest/decorator.ts","../../../src/adapters/nest/guard.ts","../../../src/adapters/nest/module.ts"],"names":["Reflector"],"mappings":"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","file":"index.js","sourcesContent":["import { createParamDecorator, ExecutionContext, SetMetadata } from '@nestjs/common';\nimport type { TokenClaims } from '../../models.js';\nimport { detectMethod, registerOperation } from '../../registry.js';\n\nexport const OPERATION_KEY = 'caronte:operation';\n\nexport interface OperationMeta {\n id: string;\n level: string;\n method: string;\n}\n\n/**\n * Marks a controller method as a caronte operation.\n *\n * @example\n * ```ts\n * @Get()\n * @Operation('tasks:list', 'private')\n * listTasks(@CaronteUser() user: TokenClaims) { ... }\n * ```\n */\nexport function Operation(id: string, level: string, method?: string): MethodDecorator {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n return SetMetadata<string, OperationMeta>(OPERATION_KEY, { id, level, method: resolvedMethod });\n}\n\n/** Injects the authenticated user's TokenClaims into a parameter. */\nexport const CaronteUser = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext): TokenClaims | undefined =>\n ctx.switchToHttp().getRequest().caronteUser,\n);","import {\n CanActivate, ExecutionContext, ForbiddenException,\n Inject, Injectable, UnauthorizedException,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { trace } from '@opentelemetry/api';\nimport { CaronteClient } from '../../client.js';\nimport type { TokenClaims } from '../../models.js';\nimport { OPERATION_KEY, type OperationMeta } from './decorator.js';\n\n@Injectable()\nexport class CaronteGuard implements CanActivate {\n // @Inject() explícito -- não depender de emitDecoratorMetadata (design:paramtypes)\n // pra resolver os parâmetros: o build desta lib usa tsup/esbuild, que não emite\n // essa metadata (limitação conhecida do esbuild), então a injeção implícita por\n // tipo do Nest falha silenciosamente (reflector fica undefined em runtime,\n // TypeError em qualquer rota guardada por CaronteGuard).\n constructor(\n @Inject(CaronteClient) private readonly client: CaronteClient,\n @Inject(Reflector) private readonly reflector: Reflector,\n ) {}\n\n async canActivate(ctx: ExecutionContext): Promise<boolean> {\n const meta = this.reflector.get<OperationMeta | undefined>(\n OPERATION_KEY,\n ctx.getHandler(),\n );\n\n if (!meta) return true; // no @Operation — pass through\n\n const { id, level, method } = meta;\n const request = ctx.switchToHttp().getRequest<{ headers: Record<string, string>; caronteUser?: TokenClaims }>();\n const span = trace.getActiveSpan();\n span?.setAttribute('caronte.operation_id', id);\n\n if (level === 'public') {\n span?.setAttribute('caronte.permission_result', 'public');\n return true;\n }\n\n const auth = request.headers['authorization'] ?? '';\n const [scheme, token] = auth.split(' ');\n\n if (scheme?.toLowerCase() !== 'bearer' || !token) {\n span?.setAttribute('caronte.permission_result', 'unauthorized');\n throw new UnauthorizedException('Bearer token required.');\n }\n\n let claims: TokenClaims;\n try {\n claims = await this.client.validateToken(token);\n } catch {\n // Token malformado/expirado/inválido -- 401, igual aos adapters express/\n // fastify (que engolem o erro de validateToken pelo mesmo motivo), não\n // deixar virar 500 por exceção não tratada.\n span?.setAttribute('caronte.permission_result', 'unauthorized');\n throw new UnauthorizedException('Invalid token.');\n }\n request.caronteUser = claims;\n span?.setAttribute('caronte.user', claims.sub);\n\n const allowed = this.client.checkPermission(claims, id, method);\n span?.setAttribute('caronte.permission_result', allowed ? 'allowed' : 'forbidden');\n if (!allowed) throw new ForbiddenException('Insufficient permissions.');\n\n return true;\n }\n}","import { DynamicModule, Module } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { CaronteClient, type CaronteClientOptions } from '../../client.js';\nimport { getRegistry } from '../../registry.js';\nimport { CaronteGuard } from './guard.js';\n\nexport interface CaronteModuleOptions extends CaronteClientOptions {}\n\n@Module({})\nexport class CaronteModule {\n /**\n * Register the caronte module globally.\n *\n * Creates the `CaronteClient`, bootstraps it (authenticate + sync + fetch),\n * and provides `CaronteGuard` for injection.\n *\n * @example\n * ```ts\n * // app.module.ts\n * @Module({\n * imports: [\n * CaronteModule.forRootAsync({\n * authorizerUrl: process.env.AUTHORIZER_URL!,\n * realmId: process.env.REALM_ID!,\n * appId: process.env.APP_ID!,\n * secret: process.env.APP_SECRET!,\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRootAsync(options: CaronteModuleOptions): DynamicModule {\n return {\n module: CaronteModule,\n global: true,\n providers: [\n Reflector,\n {\n provide: CaronteClient,\n useFactory: async () => {\n const client = new CaronteClient(options);\n for (const op of getRegistry()) client.registerOperation(op);\n await client.startup();\n return client;\n },\n },\n CaronteGuard,\n ],\n exports: [CaronteClient, CaronteGuard],\n };\n }\n}"]}
|
|
@@ -1,12 +1,55 @@
|
|
|
1
1
|
import { CaronteConfigError, CaronteAuthError, CaronteSyncError, CaronteError, CaronteTokenError } from './chunk-VEDVQOBD.js';
|
|
2
2
|
import { getRegistry } from './chunk-HV6X5RJY.js';
|
|
3
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
4
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
5
|
+
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
6
|
+
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
|
|
7
|
+
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
8
|
+
import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
|
|
9
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
10
|
+
import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
|
|
11
|
+
import { ATTR_SERVICE_NAMESPACE, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
|
|
12
|
+
import { logs, SeverityNumber } from '@opentelemetry/api-logs';
|
|
3
13
|
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
4
14
|
|
|
15
|
+
var started = false;
|
|
16
|
+
function initOtel(options) {
|
|
17
|
+
if (started) return;
|
|
18
|
+
try {
|
|
19
|
+
const base = options.otelCollectorUrl.replace(/\/$/, "");
|
|
20
|
+
const resource = defaultResource().merge(resourceFromAttributes({
|
|
21
|
+
[ATTR_SERVICE_NAME]: options.appId,
|
|
22
|
+
[ATTR_SERVICE_NAMESPACE]: options.realmId
|
|
23
|
+
}));
|
|
24
|
+
const sdk = new NodeSDK({
|
|
25
|
+
resource,
|
|
26
|
+
traceExporter: new OTLPTraceExporter({ url: `${base}/v1/traces` }),
|
|
27
|
+
metricReaders: [
|
|
28
|
+
new PeriodicExportingMetricReader({
|
|
29
|
+
exporter: new OTLPMetricExporter({ url: `${base}/v1/metrics` })
|
|
30
|
+
})
|
|
31
|
+
],
|
|
32
|
+
logRecordProcessors: [
|
|
33
|
+
new BatchLogRecordProcessor({ exporter: new OTLPLogExporter({ url: `${base}/v1/logs` }) })
|
|
34
|
+
],
|
|
35
|
+
instrumentations: [getNodeAutoInstrumentations()]
|
|
36
|
+
});
|
|
37
|
+
sdk.start();
|
|
38
|
+
started = true;
|
|
39
|
+
logs.getLogger("caronte-sdk").emit({
|
|
40
|
+
severityNumber: SeverityNumber.INFO,
|
|
41
|
+
body: `caronte-sdk OTel initialized for app_id=${options.appId} realm_id=${options.realmId}`
|
|
42
|
+
});
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.warn(`[caronte-sdk] OTel initialization skipped: ${err.message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
5
47
|
var CaronteClient = class _CaronteClient {
|
|
6
48
|
base;
|
|
7
49
|
realmId;
|
|
8
50
|
appId;
|
|
9
51
|
secret;
|
|
52
|
+
otelCollectorUrl;
|
|
10
53
|
appToken = null;
|
|
11
54
|
jwks = null;
|
|
12
55
|
operations = [];
|
|
@@ -16,6 +59,7 @@ var CaronteClient = class _CaronteClient {
|
|
|
16
59
|
this.realmId = options.realmId;
|
|
17
60
|
this.appId = options.appId;
|
|
18
61
|
this.secret = options.secret;
|
|
62
|
+
this.otelCollectorUrl = options.otelCollectorUrl;
|
|
19
63
|
}
|
|
20
64
|
get realmBase() {
|
|
21
65
|
return `${this.base}/realms/${this.realmId}`;
|
|
@@ -29,6 +73,9 @@ var CaronteClient = class _CaronteClient {
|
|
|
29
73
|
"No operations registered. Call registerOperation() before startup()."
|
|
30
74
|
);
|
|
31
75
|
}
|
|
76
|
+
if (this.otelCollectorUrl) {
|
|
77
|
+
initOtel({ otelCollectorUrl: this.otelCollectorUrl, appId: this.appId, realmId: this.realmId });
|
|
78
|
+
}
|
|
32
79
|
await this.authenticate();
|
|
33
80
|
this.initJwks();
|
|
34
81
|
await this.syncOperations();
|
|
@@ -141,6 +188,6 @@ var CaronteClient = class _CaronteClient {
|
|
|
141
188
|
}
|
|
142
189
|
};
|
|
143
190
|
|
|
144
|
-
export { CaronteClient };
|
|
145
|
-
//# sourceMappingURL=chunk-
|
|
146
|
-
//# sourceMappingURL=chunk-
|
|
191
|
+
export { CaronteClient, initOtel };
|
|
192
|
+
//# sourceMappingURL=chunk-M46BRRWA.js.map
|
|
193
|
+
//# sourceMappingURL=chunk-M46BRRWA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/otel.ts","../src/client.ts"],"names":[],"mappings":"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","file":"chunk-M46BRRWA.js","sourcesContent":["import { NodeSDK } from '@opentelemetry/sdk-node';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';\nimport { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';\nimport { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';\nimport { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';\nimport { ATTR_SERVICE_NAME, ATTR_SERVICE_NAMESPACE } from '@opentelemetry/semantic-conventions';\nimport { logs, SeverityNumber } from '@opentelemetry/api-logs';\n\nexport interface OtelInitOptions {\n otelCollectorUrl: string;\n appId: string;\n realmId: string;\n}\n\nlet started = false;\n\n/**\n * Best-effort OTel bootstrap: a misconfigured or unreachable collector must\n * never break CaronteClient.startup() (auth/JWKS still have to work), so\n * every failure here is swallowed. Guarded by `started` because NodeSDK is a\n * process-wide singleton — re-running .start() would double-register the\n * auto-instrumentations' require hooks.\n */\nexport function initOtel(options: OtelInitOptions): void {\n if (started) return;\n try {\n const base = options.otelCollectorUrl.replace(/\\/$/, '');\n const resource = defaultResource().merge(resourceFromAttributes({\n [ATTR_SERVICE_NAME]: options.appId,\n [ATTR_SERVICE_NAMESPACE]: options.realmId,\n }));\n\n const sdk = new NodeSDK({\n resource,\n traceExporter: new OTLPTraceExporter({ url: `${base}/v1/traces` }),\n metricReaders: [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({ url: `${base}/v1/metrics` }),\n }),\n ],\n logRecordProcessors: [\n new BatchLogRecordProcessor({ exporter: new OTLPLogExporter({ url: `${base}/v1/logs` }) }),\n ],\n instrumentations: [getNodeAutoInstrumentations()],\n });\n sdk.start();\n started = true;\n\n // Auto-instrumentation only yields traces (and, from real HTTP traffic,\n // metrics) — nothing captures console output as OTel logs. Emit one\n // bootstrap record so the log pipeline (client -> Collector -> Loki) is\n // provably wired even before the app receives its first request.\n logs.getLogger('caronte-sdk').emit({\n severityNumber: SeverityNumber.INFO,\n body: `caronte-sdk OTel initialized for app_id=${options.appId} realm_id=${options.realmId}`,\n });\n } catch (err) {\n console.warn(`[caronte-sdk] OTel initialization skipped: ${(err as Error).message}`);\n }\n}\n","import { createRemoteJWKSet, jwtVerify, type JWTPayload } from 'jose';\nimport {\n CaronteAuthError, CaronteConfigError, CaronteError,\n CaronteSyncError, CaronteTokenError,\n} from './exceptions.js';\nimport type { OperationDescriptor, OperationWithGroups, TokenClaims } from './models.js';\nimport { initOtel } from './otel.js';\nimport { getRegistry } from './registry.js';\n\nexport interface CaronteClientOptions {\n authorizerUrl: string;\n realmId: string;\n appId: string;\n secret: string;\n /** Opt-in: URL of an OTLP collector (e.g. http://localhost:4318). Omit to skip OTel entirely. */\n otelCollectorUrl?: string;\n}\n\nexport class CaronteClient {\n private readonly base: string;\n private readonly realmId: string;\n private readonly appId: string;\n private readonly secret: string;\n private readonly otelCollectorUrl?: string;\n\n private appToken: string | null = null;\n private jwks: ReturnType<typeof createRemoteJWKSet> | null = null;\n private operations: OperationWithGroups[] = [];\n private registeredOperations: OperationDescriptor[] = [];\n\n constructor(options: CaronteClientOptions) {\n this.base = options.authorizerUrl.replace(/\\/$/, '');\n this.realmId = options.realmId;\n this.appId = options.appId;\n this.secret = options.secret;\n this.otelCollectorUrl = options.otelCollectorUrl;\n }\n\n private get realmBase(): string {\n return `${this.base}/realms/${this.realmId}`;\n }\n\n registerOperation(op: OperationDescriptor): void {\n this.registeredOperations.push(op);\n }\n\n async startup(): Promise<void> {\n if (this.registeredOperations.length === 0) {\n throw new CaronteConfigError(\n 'No operations registered. Call registerOperation() before startup().',\n );\n }\n if (this.otelCollectorUrl) {\n initOtel({ otelCollectorUrl: this.otelCollectorUrl, appId: this.appId, realmId: this.realmId });\n }\n await this.authenticate();\n this.initJwks();\n await this.syncOperations();\n await this.fetchOperations();\n }\n\n async authenticate(): Promise<void> {\n const res = await fetch(`${this.realmBase}/apps/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ app_id: this.appId, secret: this.secret }),\n });\n if (!res.ok) {\n throw new CaronteAuthError(\n `Authentication failed: HTTP ${res.status} — ${await res.text()}`,\n );\n }\n const data = await res.json() as { access_token?: string };\n if (!data.access_token) throw new CaronteAuthError('Authorizer returned no access_token.');\n this.appToken = data.access_token;\n }\n\n private initJwks(): void {\n this.jwks = createRemoteJWKSet(\n new URL(`${this.realmBase}/protocol/openid-connect/certs`),\n );\n }\n\n async syncOperations(): Promise<void> {\n this.ensureAppToken();\n const res = await fetch(`${this.realmBase}/apps/operations/sync`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.appToken}`,\n },\n body: JSON.stringify({\n operations: this.registeredOperations.map(op => ({\n identifier: op.identifier,\n method: op.method,\n level: op.level,\n description: op.description ?? null,\n })),\n }),\n });\n if (!res.ok) {\n throw new CaronteSyncError(`Sync failed: HTTP ${res.status} — ${await res.text()}`);\n }\n }\n\n async fetchOperations(): Promise<void> {\n this.ensureAppToken();\n const res = await fetch(`${this.realmBase}/apps/operations`, {\n headers: { Authorization: `Bearer ${this.appToken}` },\n });\n if (!res.ok) {\n throw new CaronteError(\n `Fetch operations failed: HTTP ${res.status} — ${await res.text()}`,\n );\n }\n const data = await res.json() as { operations: any[] };\n this.operations = data.operations.map(op => ({\n operationId: op.operation_id,\n identifier: op.identifier,\n method: op.method,\n level: op.level,\n allowedGroups: op.allowed_groups ?? [],\n }));\n }\n\n async validateToken(token: string): Promise<TokenClaims> {\n if (!this.jwks) this.initJwks();\n try {\n const { payload } = await jwtVerify(token, this.jwks!);\n const p = payload as JWTPayload & Record<string, unknown>;\n const tokenRealm = (p['realm_id'] as string) ?? '';\n\n // As chaves JWKS já são buscadas em /realms/{this.realmId}/... (ver\n // realmBase), então um token de outro realm normalmente já falharia a\n // verificação de assinatura acima. Esta checagem é defesa em\n // profundidade explícita — não depende desse efeito colateral, e falha\n // com uma mensagem clara em vez de erro de assinatura.\n if (tokenRealm.toLowerCase() !== this.realmId.toLowerCase()) {\n throw new CaronteTokenError(\n `Token realm '${tokenRealm}' does not match this app's realm '${this.realmId}'.`,\n );\n }\n\n return {\n sub: payload.sub ?? '',\n realmId: tokenRealm,\n groups: (p['groups'] as string[]) ?? [],\n exp: payload.exp ?? 0,\n raw: p,\n };\n } catch (err) {\n if (err instanceof CaronteTokenError) throw err;\n throw new CaronteTokenError(`Invalid token: ${(err as Error).message}`);\n }\n }\n\n checkPermission(claims: TokenClaims, identifier: string, method: string): boolean {\n const op = this.operations.find(\n o => o.identifier === identifier && o.method === method,\n );\n if (!op) return false;\n if (op.level === 'public') return true;\n if (op.level === 'private') return claims.groups.length > 0;\n if (op.level === 'protected') return claims.groups.some(g => op.allowedGroups.includes(g));\n return false;\n }\n\n /** Load operations from the global registry and bootstrap the client. */\n static async create(options: CaronteClientOptions): Promise<CaronteClient> {\n const client = new CaronteClient(options);\n for (const op of getRegistry()) client.registerOperation(op);\n await client.startup();\n return client;\n }\n\n private ensureAppToken(): void {\n if (!this.appToken) {\n throw new CaronteAuthError('No app token. Call authenticate() or startup() first.');\n }\n }\n}"]}
|
|
@@ -21,12 +21,15 @@ interface CaronteClientOptions {
|
|
|
21
21
|
realmId: string;
|
|
22
22
|
appId: string;
|
|
23
23
|
secret: string;
|
|
24
|
+
/** Opt-in: URL of an OTLP collector (e.g. http://localhost:4318). Omit to skip OTel entirely. */
|
|
25
|
+
otelCollectorUrl?: string;
|
|
24
26
|
}
|
|
25
27
|
declare class CaronteClient {
|
|
26
28
|
private readonly base;
|
|
27
29
|
private readonly realmId;
|
|
28
30
|
private readonly appId;
|
|
29
31
|
private readonly secret;
|
|
32
|
+
private readonly otelCollectorUrl?;
|
|
30
33
|
private appToken;
|
|
31
34
|
private jwks;
|
|
32
35
|
private operations;
|
|
@@ -21,12 +21,15 @@ interface CaronteClientOptions {
|
|
|
21
21
|
realmId: string;
|
|
22
22
|
appId: string;
|
|
23
23
|
secret: string;
|
|
24
|
+
/** Opt-in: URL of an OTLP collector (e.g. http://localhost:4318). Omit to skip OTel entirely. */
|
|
25
|
+
otelCollectorUrl?: string;
|
|
24
26
|
}
|
|
25
27
|
declare class CaronteClient {
|
|
26
28
|
private readonly base;
|
|
27
29
|
private readonly realmId;
|
|
28
30
|
private readonly appId;
|
|
29
31
|
private readonly secret;
|
|
32
|
+
private readonly otelCollectorUrl?;
|
|
30
33
|
private appToken;
|
|
31
34
|
private jwks;
|
|
32
35
|
private operations;
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var jose = require('jose');
|
|
4
|
+
var sdkNode = require('@opentelemetry/sdk-node');
|
|
5
|
+
var exporterTraceOtlpHttp = require('@opentelemetry/exporter-trace-otlp-http');
|
|
6
|
+
var exporterMetricsOtlpHttp = require('@opentelemetry/exporter-metrics-otlp-http');
|
|
7
|
+
var exporterLogsOtlpHttp = require('@opentelemetry/exporter-logs-otlp-http');
|
|
8
|
+
var sdkMetrics = require('@opentelemetry/sdk-metrics');
|
|
9
|
+
var sdkLogs = require('@opentelemetry/sdk-logs');
|
|
10
|
+
var autoInstrumentationsNode = require('@opentelemetry/auto-instrumentations-node');
|
|
11
|
+
var resources = require('@opentelemetry/resources');
|
|
12
|
+
var semanticConventions = require('@opentelemetry/semantic-conventions');
|
|
13
|
+
var apiLogs = require('@opentelemetry/api-logs');
|
|
4
14
|
|
|
5
15
|
// src/client.ts
|
|
6
16
|
|
|
@@ -21,6 +31,38 @@ var CaronteSyncError = class extends CaronteError {
|
|
|
21
31
|
};
|
|
22
32
|
var CaronteConfigError = class extends CaronteError {
|
|
23
33
|
};
|
|
34
|
+
var started = false;
|
|
35
|
+
function initOtel(options) {
|
|
36
|
+
if (started) return;
|
|
37
|
+
try {
|
|
38
|
+
const base = options.otelCollectorUrl.replace(/\/$/, "");
|
|
39
|
+
const resource = resources.defaultResource().merge(resources.resourceFromAttributes({
|
|
40
|
+
[semanticConventions.ATTR_SERVICE_NAME]: options.appId,
|
|
41
|
+
[semanticConventions.ATTR_SERVICE_NAMESPACE]: options.realmId
|
|
42
|
+
}));
|
|
43
|
+
const sdk = new sdkNode.NodeSDK({
|
|
44
|
+
resource,
|
|
45
|
+
traceExporter: new exporterTraceOtlpHttp.OTLPTraceExporter({ url: `${base}/v1/traces` }),
|
|
46
|
+
metricReaders: [
|
|
47
|
+
new sdkMetrics.PeriodicExportingMetricReader({
|
|
48
|
+
exporter: new exporterMetricsOtlpHttp.OTLPMetricExporter({ url: `${base}/v1/metrics` })
|
|
49
|
+
})
|
|
50
|
+
],
|
|
51
|
+
logRecordProcessors: [
|
|
52
|
+
new sdkLogs.BatchLogRecordProcessor({ exporter: new exporterLogsOtlpHttp.OTLPLogExporter({ url: `${base}/v1/logs` }) })
|
|
53
|
+
],
|
|
54
|
+
instrumentations: [autoInstrumentationsNode.getNodeAutoInstrumentations()]
|
|
55
|
+
});
|
|
56
|
+
sdk.start();
|
|
57
|
+
started = true;
|
|
58
|
+
apiLogs.logs.getLogger("caronte-sdk").emit({
|
|
59
|
+
severityNumber: apiLogs.SeverityNumber.INFO,
|
|
60
|
+
body: `caronte-sdk OTel initialized for app_id=${options.appId} realm_id=${options.realmId}`
|
|
61
|
+
});
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.warn(`[caronte-sdk] OTel initialization skipped: ${err.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
24
66
|
|
|
25
67
|
// src/registry.ts
|
|
26
68
|
var _registry = [];
|
|
@@ -50,6 +92,7 @@ var CaronteClient = class _CaronteClient {
|
|
|
50
92
|
realmId;
|
|
51
93
|
appId;
|
|
52
94
|
secret;
|
|
95
|
+
otelCollectorUrl;
|
|
53
96
|
appToken = null;
|
|
54
97
|
jwks = null;
|
|
55
98
|
operations = [];
|
|
@@ -59,6 +102,7 @@ var CaronteClient = class _CaronteClient {
|
|
|
59
102
|
this.realmId = options.realmId;
|
|
60
103
|
this.appId = options.appId;
|
|
61
104
|
this.secret = options.secret;
|
|
105
|
+
this.otelCollectorUrl = options.otelCollectorUrl;
|
|
62
106
|
}
|
|
63
107
|
get realmBase() {
|
|
64
108
|
return `${this.base}/realms/${this.realmId}`;
|
|
@@ -72,6 +116,9 @@ var CaronteClient = class _CaronteClient {
|
|
|
72
116
|
"No operations registered. Call registerOperation() before startup()."
|
|
73
117
|
);
|
|
74
118
|
}
|
|
119
|
+
if (this.otelCollectorUrl) {
|
|
120
|
+
initOtel({ otelCollectorUrl: this.otelCollectorUrl, appId: this.appId, realmId: this.realmId });
|
|
121
|
+
}
|
|
75
122
|
await this.authenticate();
|
|
76
123
|
this.initJwks();
|
|
77
124
|
await this.syncOperations();
|
|
@@ -194,6 +241,7 @@ exports.CaronteTokenError = CaronteTokenError;
|
|
|
194
241
|
exports.clearRegistry = clearRegistry;
|
|
195
242
|
exports.detectMethod = detectMethod;
|
|
196
243
|
exports.getRegistry = getRegistry;
|
|
244
|
+
exports.initOtel = initOtel;
|
|
197
245
|
exports.registerOperation = registerOperation;
|
|
198
246
|
//# sourceMappingURL=index.cjs.map
|
|
199
247
|
//# sourceMappingURL=index.cjs.map
|