@caronte-sdk/node 0.2.3 → 0.2.4

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 CHANGED
@@ -115,6 +115,55 @@ export class TasksController {
115
115
  }
116
116
  ```
117
117
 
118
+ #### Running with `otelCollectorUrl` on NestJS
119
+
120
+ `CaronteModule.forRootAsync()` alone is **not enough** to get request tracing
121
+ in a Nest app, even outside the ESM case above. `NestFactory.create()`
122
+ requires `express` (or `fastify`) internally to build the HTTP adapter
123
+ *before* any module's own providers — including `CaronteModule` — get a
124
+ chance to run. By the time `CaronteModule`'s factory calls `startup()` (and,
125
+ through it, `initOtel()`), the HTTP framework has already finished loading
126
+ unpatched, so auto-instrumentation never attaches to it. This is a Nest
127
+ bootstrap-ordering issue, distinct from the ESM loader-hook issue — it
128
+ affects CommonJS Nest apps too.
129
+
130
+ Fix: call `initOtel()` yourself, **before** `import { NestFactory } from
131
+ '@nestjs/core'`:
132
+
133
+ ```ts
134
+ // main.ts
135
+ import 'reflect-metadata';
136
+
137
+ // Must run before @nestjs/core is required — see explanation above.
138
+ // CaronteModule still calls startup()/initOtel() too (harmless no-op,
139
+ // idempotent), so an app that skips this still gets traces/metrics/logs
140
+ // for everything except the HTTP layer itself.
141
+ import { initOtel } from '@caronte-sdk/node';
142
+ if (process.env.OTEL_COLLECTOR_URL) {
143
+ initOtel({
144
+ otelCollectorUrl: process.env.OTEL_COLLECTOR_URL,
145
+ appId: process.env.APP_ID!,
146
+ realmId: process.env.REALM_ID!,
147
+ });
148
+ }
149
+
150
+ import { NestFactory } from '@nestjs/core';
151
+ import { AppModule } from './app.module';
152
+
153
+ async function bootstrap() {
154
+ const app = await NestFactory.create(AppModule);
155
+ await app.listen(3000);
156
+ }
157
+ bootstrap();
158
+ ```
159
+
160
+ This works because in CommonJS, `require()` calls run in the textual order
161
+ they appear — the same reason `reflect-metadata` is conventionally imported
162
+ first in this ecosystem. It only works if `main.ts` itself is CommonJS
163
+ (no `"type": "module"` in `package.json`, e.g. run via `ts-node`); an ESM
164
+ Nest app needs the `caronte-node` wrapper from the section above instead —
165
+ in that case skip this manual `initOtel()` call, the loader hook handles it.
166
+
118
167
  ### Apollo Server
119
168
 
120
169
  ```ts
@@ -50,6 +50,9 @@ function carontePlugin(client) {
50
50
  }
51
51
  }
52
52
  }
53
+ const operationName = ctx.operationName ?? ctx.operation?.name?.value;
54
+ if (operationName) api.trace.getActiveSpan()?.setAttribute("graphql.operation.name", operationName);
55
+ if (ctx.operation?.operation) api.trace.getActiveSpan()?.setAttribute("graphql.operation.type", ctx.operation.operation);
53
56
  }
54
57
  };
55
58
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/exceptions.ts","../../src/registry.ts","../../src/adapters/apollo.ts"],"names":["trace"],"mappings":"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","file":"apollo.cjs","sourcesContent":["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 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 type { ApolloServerPlugin, BaseContext, GraphQLRequestContext } from '@apollo/server';\nimport { trace } from '@opentelemetry/api';\nimport { CaronteClient } from '../client.js';\nimport { CaronteTokenError } from '../exceptions.js';\nimport type { TokenClaims } from '../models.js';\nimport { detectMethod, registerOperation } from '../registry.js';\n\nexport interface CaronteContext extends BaseContext {\n caronteUser?: TokenClaims;\n}\n\n/**\n * Apollo Server plugin for caronte.\n *\n * Validates the Bearer token on every request and stores the claims in\n * `context.caronteUser`. Use `createGuard(client)` to enforce per-resolver\n * permissions.\n */\nexport function carontePlugin(client: CaronteClient): ApolloServerPlugin<CaronteContext> {\n return {\n async requestDidStart() {\n return {\n async didResolveOperation(ctx: GraphQLRequestContext<CaronteContext>) {\n const httpAuth = ctx.request.http?.headers.get('authorization') ?? '';\n const wsAuth = ((ctx.contextValue as any)?.connectionParams?.Authorization as string | undefined) ?? '';\n const auth = httpAuth || wsAuth;\n\n const [scheme, token] = auth.split(' ');\n if (scheme?.toLowerCase() === 'bearer' && token) {\n try {\n ctx.contextValue.caronteUser = await client.validateToken(token);\n trace.getActiveSpan()?.setAttribute('caronte.user', ctx.contextValue.caronteUser.sub);\n } catch (err) {\n if (err instanceof CaronteTokenError) {\n ctx.contextValue.caronteUser = undefined;\n }\n }\n }\n },\n };\n },\n };\n}\n\n/**\n * Factory that returns a per-resolver guard bound to a `CaronteClient`.\n *\n * @example\n * ```ts\n * const guard = createGuard(client);\n *\n * const resolvers = {\n * Query: {\n * tasks: guard('tasks:list', 'private', async (_parent, _args, context) => {\n * const user = context.caronteUser;\n * return db.tasks.findAll();\n * }),\n * },\n * };\n * ```\n */\nexport function createGuard(client: CaronteClient) {\n return function guard<TParent, TArgs, TContext extends CaronteContext, TReturn>(\n id: string,\n level: string,\n resolver: (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn,\n method?: string,\n ): (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n\n return (parent, args, context, info) => {\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 resolver(parent, args, context, info);\n }\n if (!context.caronteUser) {\n span?.setAttribute('caronte.permission_result', 'unauthorized');\n throw new Error('Unauthorized: Bearer token required.');\n }\n const allowed = client.checkPermission(context.caronteUser, id, resolvedMethod);\n span?.setAttribute('caronte.permission_result', allowed ? 'allowed' : 'forbidden');\n if (!allowed) {\n throw new Error('Forbidden: Insufficient permissions.');\n }\n return resolver(parent, args, context, info);\n };\n };\n}"]}
1
+ {"version":3,"sources":["../../src/exceptions.ts","../../src/registry.ts","../../src/adapters/apollo.ts"],"names":["trace"],"mappings":"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","file":"apollo.cjs","sourcesContent":["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 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 type { ApolloServerPlugin, BaseContext, GraphQLRequestContext } from '@apollo/server';\nimport { trace } from '@opentelemetry/api';\nimport { CaronteClient } from '../client.js';\nimport { CaronteTokenError } from '../exceptions.js';\nimport type { TokenClaims } from '../models.js';\nimport { detectMethod, registerOperation } from '../registry.js';\n\nexport interface CaronteContext extends BaseContext {\n caronteUser?: TokenClaims;\n}\n\n/**\n * Apollo Server plugin for caronte.\n *\n * Validates the Bearer token on every request and stores the claims in\n * `context.caronteUser`. Use `createGuard(client)` to enforce per-resolver\n * permissions.\n */\nexport function carontePlugin(client: CaronteClient): ApolloServerPlugin<CaronteContext> {\n return {\n async requestDidStart() {\n return {\n async didResolveOperation(ctx: GraphQLRequestContext<CaronteContext>) {\n const httpAuth = ctx.request.http?.headers.get('authorization') ?? '';\n const wsAuth = ((ctx.contextValue as any)?.connectionParams?.Authorization as string | undefined) ?? '';\n const auth = httpAuth || wsAuth;\n\n const [scheme, token] = auth.split(' ');\n if (scheme?.toLowerCase() === 'bearer' && token) {\n try {\n ctx.contextValue.caronteUser = await client.validateToken(token);\n trace.getActiveSpan()?.setAttribute('caronte.user', ctx.contextValue.caronteUser.sub);\n } catch (err) {\n if (err instanceof CaronteTokenError) {\n ctx.contextValue.caronteUser = undefined;\n }\n }\n }\n\n // Sem isso, toda requisição GraphQL aparece nas listas de trace\n // como \"POST /graphql\" genérico -- o nome/tipo da operação\n // (query/mutation) só existe aqui dentro do Apollo, nunca chega\n // no span HTTP. O span ativo neste ponto é o span do middleware\n // Express que envolve o handler do Apollo (não o span raiz\n // \"POST /graphql\" -- instrumentation-express cria um span por\n // camada de middleware, todos filhos diretos do span raiz), então\n // o backend precisa pedir mais spans por trace (spss) pra achar\n // esse atributo -- ver tempo.module.ts.\n const operationName = ctx.operationName ?? ctx.operation?.name?.value;\n if (operationName) trace.getActiveSpan()?.setAttribute('graphql.operation.name', operationName);\n if (ctx.operation?.operation) trace.getActiveSpan()?.setAttribute('graphql.operation.type', ctx.operation.operation);\n },\n };\n },\n };\n}\n\n/**\n * Factory that returns a per-resolver guard bound to a `CaronteClient`.\n *\n * @example\n * ```ts\n * const guard = createGuard(client);\n *\n * const resolvers = {\n * Query: {\n * tasks: guard('tasks:list', 'private', async (_parent, _args, context) => {\n * const user = context.caronteUser;\n * return db.tasks.findAll();\n * }),\n * },\n * };\n * ```\n */\nexport function createGuard(client: CaronteClient) {\n return function guard<TParent, TArgs, TContext extends CaronteContext, TReturn>(\n id: string,\n level: string,\n resolver: (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn,\n method?: string,\n ): (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n\n return (parent, args, context, info) => {\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 resolver(parent, args, context, info);\n }\n if (!context.caronteUser) {\n span?.setAttribute('caronte.permission_result', 'unauthorized');\n throw new Error('Unauthorized: Bearer token required.');\n }\n const allowed = client.checkPermission(context.caronteUser, id, resolvedMethod);\n span?.setAttribute('caronte.permission_result', allowed ? 'allowed' : 'forbidden');\n if (!allowed) {\n throw new Error('Forbidden: Insufficient permissions.');\n }\n return resolver(parent, args, context, info);\n };\n };\n}"]}
@@ -21,6 +21,9 @@ function carontePlugin(client) {
21
21
  }
22
22
  }
23
23
  }
24
+ const operationName = ctx.operationName ?? ctx.operation?.name?.value;
25
+ if (operationName) trace.getActiveSpan()?.setAttribute("graphql.operation.name", operationName);
26
+ if (ctx.operation?.operation) trace.getActiveSpan()?.setAttribute("graphql.operation.type", ctx.operation.operation);
24
27
  }
25
28
  };
26
29
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/apollo.ts"],"names":[],"mappings":";;;;AAkBO,SAAS,cAAc,MAAA,EAA2D;AACvF,EAAA,OAAO;AAAA,IACL,MAAM,eAAA,GAAkB;AACtB,MAAA,OAAO;AAAA,QACL,MAAM,oBAAoB,GAAA,EAA4C;AACpE,UAAA,MAAM,WAAY,GAAA,CAAI,OAAA,CAAQ,MAAM,OAAA,CAAQ,GAAA,CAAI,eAAe,CAAA,IAAK,EAAA;AACpE,UAAA,MAAM,MAAA,GAAc,GAAA,CAAI,YAAA,EAAsB,gBAAA,EAAkB,aAAA,IAAwC,EAAA;AACxG,UAAA,MAAM,OAAY,QAAA,IAAY,MAAA;AAE9B,UAAA,MAAM,CAAC,MAAA,EAAQ,KAAK,CAAA,GAAI,IAAA,CAAK,MAAM,GAAG,CAAA;AACtC,UAAA,IAAI,MAAA,EAAQ,WAAA,EAAY,KAAM,QAAA,IAAY,KAAA,EAAO;AAC/C,YAAA,IAAI;AACF,cAAA,GAAA,CAAI,YAAA,CAAa,WAAA,GAAc,MAAM,MAAA,CAAO,cAAc,KAAK,CAAA;AAC/D,cAAA,KAAA,CAAM,eAAc,EAAG,YAAA,CAAa,gBAAgB,GAAA,CAAI,YAAA,CAAa,YAAY,GAAG,CAAA;AAAA,YACtF,SAAS,GAAA,EAAK;AACZ,cAAA,IAAI,eAAe,iBAAA,EAAmB;AACpC,gBAAA,GAAA,CAAI,aAAa,WAAA,GAAc,MAAA;AAAA,cACjC;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,OACF;AAAA,IACF;AAAA,GACF;AACF;AAmBO,SAAS,YAAY,MAAA,EAAuB;AACjD,EAAA,OAAO,SAAS,KAAA,CACd,EAAA,EACA,KAAA,EACA,UACA,MAAA,EAC6E;AAC7E,IAAA,MAAM,cAAA,GAAiB,MAAA,IAAU,YAAA,CAAa,EAAE,CAAA;AAChD,IAAA,iBAAA,CAAkB,EAAE,UAAA,EAAY,EAAA,EAAI,MAAA,EAAQ,cAAA,EAAgB,OAAO,CAAA;AAEnE,IAAA,OAAO,CAAC,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,IAAA,KAAS;AACtC,MAAA,MAAM,IAAA,GAAO,MAAM,aAAA,EAAc;AACjC,MAAA,IAAA,EAAM,YAAA,CAAa,wBAAwB,EAAE,CAAA;AAE7C,MAAA,IAAI,UAAU,QAAA,EAAU;AACtB,QAAA,IAAA,EAAM,YAAA,CAAa,6BAA6B,QAAQ,CAAA;AACxD,QAAA,OAAO,QAAA,CAAS,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,IAAI,CAAA;AAAA,MAC7C;AACA,MAAA,IAAI,CAAC,QAAQ,WAAA,EAAa;AACxB,QAAA,IAAA,EAAM,YAAA,CAAa,6BAA6B,cAAc,CAAA;AAC9D,QAAA,MAAM,IAAI,MAAM,sCAAsC,CAAA;AAAA,MACxD;AACA,MAAA,MAAM,UAAU,MAAA,CAAO,eAAA,CAAgB,OAAA,CAAQ,WAAA,EAAa,IAAI,cAAc,CAAA;AAC9E,MAAA,IAAA,EAAM,YAAA,CAAa,2BAAA,EAA6B,OAAA,GAAU,SAAA,GAAY,WAAW,CAAA;AACjF,MAAA,IAAI,CAAC,OAAA,EAAS;AACZ,QAAA,MAAM,IAAI,MAAM,sCAAsC,CAAA;AAAA,MACxD;AACA,MAAA,OAAO,QAAA,CAAS,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,IAAI,CAAA;AAAA,IAC7C,CAAA;AAAA,EACF,CAAA;AACF","file":"apollo.js","sourcesContent":["import type { ApolloServerPlugin, BaseContext, GraphQLRequestContext } from '@apollo/server';\nimport { trace } from '@opentelemetry/api';\nimport { CaronteClient } from '../client.js';\nimport { CaronteTokenError } from '../exceptions.js';\nimport type { TokenClaims } from '../models.js';\nimport { detectMethod, registerOperation } from '../registry.js';\n\nexport interface CaronteContext extends BaseContext {\n caronteUser?: TokenClaims;\n}\n\n/**\n * Apollo Server plugin for caronte.\n *\n * Validates the Bearer token on every request and stores the claims in\n * `context.caronteUser`. Use `createGuard(client)` to enforce per-resolver\n * permissions.\n */\nexport function carontePlugin(client: CaronteClient): ApolloServerPlugin<CaronteContext> {\n return {\n async requestDidStart() {\n return {\n async didResolveOperation(ctx: GraphQLRequestContext<CaronteContext>) {\n const httpAuth = ctx.request.http?.headers.get('authorization') ?? '';\n const wsAuth = ((ctx.contextValue as any)?.connectionParams?.Authorization as string | undefined) ?? '';\n const auth = httpAuth || wsAuth;\n\n const [scheme, token] = auth.split(' ');\n if (scheme?.toLowerCase() === 'bearer' && token) {\n try {\n ctx.contextValue.caronteUser = await client.validateToken(token);\n trace.getActiveSpan()?.setAttribute('caronte.user', ctx.contextValue.caronteUser.sub);\n } catch (err) {\n if (err instanceof CaronteTokenError) {\n ctx.contextValue.caronteUser = undefined;\n }\n }\n }\n },\n };\n },\n };\n}\n\n/**\n * Factory that returns a per-resolver guard bound to a `CaronteClient`.\n *\n * @example\n * ```ts\n * const guard = createGuard(client);\n *\n * const resolvers = {\n * Query: {\n * tasks: guard('tasks:list', 'private', async (_parent, _args, context) => {\n * const user = context.caronteUser;\n * return db.tasks.findAll();\n * }),\n * },\n * };\n * ```\n */\nexport function createGuard(client: CaronteClient) {\n return function guard<TParent, TArgs, TContext extends CaronteContext, TReturn>(\n id: string,\n level: string,\n resolver: (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn,\n method?: string,\n ): (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n\n return (parent, args, context, info) => {\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 resolver(parent, args, context, info);\n }\n if (!context.caronteUser) {\n span?.setAttribute('caronte.permission_result', 'unauthorized');\n throw new Error('Unauthorized: Bearer token required.');\n }\n const allowed = client.checkPermission(context.caronteUser, id, resolvedMethod);\n span?.setAttribute('caronte.permission_result', allowed ? 'allowed' : 'forbidden');\n if (!allowed) {\n throw new Error('Forbidden: Insufficient permissions.');\n }\n return resolver(parent, args, context, info);\n };\n };\n}"]}
1
+ {"version":3,"sources":["../../src/adapters/apollo.ts"],"names":[],"mappings":"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","file":"apollo.js","sourcesContent":["import type { ApolloServerPlugin, BaseContext, GraphQLRequestContext } from '@apollo/server';\nimport { trace } from '@opentelemetry/api';\nimport { CaronteClient } from '../client.js';\nimport { CaronteTokenError } from '../exceptions.js';\nimport type { TokenClaims } from '../models.js';\nimport { detectMethod, registerOperation } from '../registry.js';\n\nexport interface CaronteContext extends BaseContext {\n caronteUser?: TokenClaims;\n}\n\n/**\n * Apollo Server plugin for caronte.\n *\n * Validates the Bearer token on every request and stores the claims in\n * `context.caronteUser`. Use `createGuard(client)` to enforce per-resolver\n * permissions.\n */\nexport function carontePlugin(client: CaronteClient): ApolloServerPlugin<CaronteContext> {\n return {\n async requestDidStart() {\n return {\n async didResolveOperation(ctx: GraphQLRequestContext<CaronteContext>) {\n const httpAuth = ctx.request.http?.headers.get('authorization') ?? '';\n const wsAuth = ((ctx.contextValue as any)?.connectionParams?.Authorization as string | undefined) ?? '';\n const auth = httpAuth || wsAuth;\n\n const [scheme, token] = auth.split(' ');\n if (scheme?.toLowerCase() === 'bearer' && token) {\n try {\n ctx.contextValue.caronteUser = await client.validateToken(token);\n trace.getActiveSpan()?.setAttribute('caronte.user', ctx.contextValue.caronteUser.sub);\n } catch (err) {\n if (err instanceof CaronteTokenError) {\n ctx.contextValue.caronteUser = undefined;\n }\n }\n }\n\n // Sem isso, toda requisição GraphQL aparece nas listas de trace\n // como \"POST /graphql\" genérico -- o nome/tipo da operação\n // (query/mutation) só existe aqui dentro do Apollo, nunca chega\n // no span HTTP. O span ativo neste ponto é o span do middleware\n // Express que envolve o handler do Apollo (não o span raiz\n // \"POST /graphql\" -- instrumentation-express cria um span por\n // camada de middleware, todos filhos diretos do span raiz), então\n // o backend precisa pedir mais spans por trace (spss) pra achar\n // esse atributo -- ver tempo.module.ts.\n const operationName = ctx.operationName ?? ctx.operation?.name?.value;\n if (operationName) trace.getActiveSpan()?.setAttribute('graphql.operation.name', operationName);\n if (ctx.operation?.operation) trace.getActiveSpan()?.setAttribute('graphql.operation.type', ctx.operation.operation);\n },\n };\n },\n };\n}\n\n/**\n * Factory that returns a per-resolver guard bound to a `CaronteClient`.\n *\n * @example\n * ```ts\n * const guard = createGuard(client);\n *\n * const resolvers = {\n * Query: {\n * tasks: guard('tasks:list', 'private', async (_parent, _args, context) => {\n * const user = context.caronteUser;\n * return db.tasks.findAll();\n * }),\n * },\n * };\n * ```\n */\nexport function createGuard(client: CaronteClient) {\n return function guard<TParent, TArgs, TContext extends CaronteContext, TReturn>(\n id: string,\n level: string,\n resolver: (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn,\n method?: string,\n ): (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n\n return (parent, args, context, info) => {\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 resolver(parent, args, context, info);\n }\n if (!context.caronteUser) {\n span?.setAttribute('caronte.permission_result', 'unauthorized');\n throw new Error('Unauthorized: Bearer token required.');\n }\n const allowed = client.checkPermission(context.caronteUser, id, resolvedMethod);\n span?.setAttribute('caronte.permission_result', allowed ? 'allowed' : 'forbidden');\n if (!allowed) {\n throw new Error('Forbidden: Insufficient permissions.');\n }\n return resolver(parent, args, context, info);\n };\n };\n}"]}
@@ -323,6 +323,18 @@ exports.CaronteModule = class CaronteModule {
323
323
  * })
324
324
  * export class AppModule {}
325
325
  * ```
326
+ *
327
+ * @remarks
328
+ * If `options.otelCollectorUrl` is set, this alone does **not** get you
329
+ * request tracing: `NestFactory.create()` requires express/fastify
330
+ * internally to build the HTTP adapter *before* this module's provider
331
+ * factory (and its `startup()`/`initOtel()` call) ever runs, so
332
+ * auto-instrumentation attaches too late. Call `initOtel()` from
333
+ * `@caronte-sdk/node` yourself, before `import { NestFactory } from
334
+ * '@nestjs/core'` in `main.ts` (CommonJS only — `require()` order is
335
+ * preserved textually there). See the README's "Running with
336
+ * otelCollectorUrl on NestJS" section for the full example. The call
337
+ * this module makes is a harmless idempotent no-op either way.
326
338
  */
327
339
  static forRootAsync(options) {
328
340
  return {
@@ -1 +1 @@
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
+ {"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 * @remarks\n * If `options.otelCollectorUrl` is set, this alone does **not** get you\n * request tracing: `NestFactory.create()` requires express/fastify\n * internally to build the HTTP adapter *before* this module's provider\n * factory (and its `startup()`/`initOtel()` call) ever runs, so\n * auto-instrumentation attaches too late. Call `initOtel()` from\n * `@caronte-sdk/node` yourself, before `import { NestFactory } from\n * '@nestjs/core'` in `main.ts` (CommonJS only — `require()` order is\n * preserved textually there). See the README's \"Running with\n * otelCollectorUrl on NestJS\" section for the full example. The call\n * this module makes is a harmless idempotent no-op either way.\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}"]}
@@ -53,6 +53,18 @@ declare class CaronteModule {
53
53
  * })
54
54
  * export class AppModule {}
55
55
  * ```
56
+ *
57
+ * @remarks
58
+ * If `options.otelCollectorUrl` is set, this alone does **not** get you
59
+ * request tracing: `NestFactory.create()` requires express/fastify
60
+ * internally to build the HTTP adapter *before* this module's provider
61
+ * factory (and its `startup()`/`initOtel()` call) ever runs, so
62
+ * auto-instrumentation attaches too late. Call `initOtel()` from
63
+ * `@caronte-sdk/node` yourself, before `import { NestFactory } from
64
+ * '@nestjs/core'` in `main.ts` (CommonJS only — `require()` order is
65
+ * preserved textually there). See the README's "Running with
66
+ * otelCollectorUrl on NestJS" section for the full example. The call
67
+ * this module makes is a harmless idempotent no-op either way.
56
68
  */
57
69
  static forRootAsync(options: CaronteModuleOptions): DynamicModule;
58
70
  }
@@ -53,6 +53,18 @@ declare class CaronteModule {
53
53
  * })
54
54
  * export class AppModule {}
55
55
  * ```
56
+ *
57
+ * @remarks
58
+ * If `options.otelCollectorUrl` is set, this alone does **not** get you
59
+ * request tracing: `NestFactory.create()` requires express/fastify
60
+ * internally to build the HTTP adapter *before* this module's provider
61
+ * factory (and its `startup()`/`initOtel()` call) ever runs, so
62
+ * auto-instrumentation attaches too late. Call `initOtel()` from
63
+ * `@caronte-sdk/node` yourself, before `import { NestFactory } from
64
+ * '@nestjs/core'` in `main.ts` (CommonJS only — `require()` order is
65
+ * preserved textually there). See the README's "Running with
66
+ * otelCollectorUrl on NestJS" section for the full example. The call
67
+ * this module makes is a harmless idempotent no-op either way.
56
68
  */
57
69
  static forRootAsync(options: CaronteModuleOptions): DynamicModule;
58
70
  }
@@ -88,6 +88,18 @@ var CaronteModule = class {
88
88
  * })
89
89
  * export class AppModule {}
90
90
  * ```
91
+ *
92
+ * @remarks
93
+ * If `options.otelCollectorUrl` is set, this alone does **not** get you
94
+ * request tracing: `NestFactory.create()` requires express/fastify
95
+ * internally to build the HTTP adapter *before* this module's provider
96
+ * factory (and its `startup()`/`initOtel()` call) ever runs, so
97
+ * auto-instrumentation attaches too late. Call `initOtel()` from
98
+ * `@caronte-sdk/node` yourself, before `import { NestFactory } from
99
+ * '@nestjs/core'` in `main.ts` (CommonJS only — `require()` order is
100
+ * preserved textually there). See the README's "Running with
101
+ * otelCollectorUrl on NestJS" section for the full example. The call
102
+ * this module makes is a harmless idempotent no-op either way.
91
103
  */
92
104
  static forRootAsync(options) {
93
105
  return {
@@ -1 +1 @@
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
+ {"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 * @remarks\n * If `options.otelCollectorUrl` is set, this alone does **not** get you\n * request tracing: `NestFactory.create()` requires express/fastify\n * internally to build the HTTP adapter *before* this module's provider\n * factory (and its `startup()`/`initOtel()` call) ever runs, so\n * auto-instrumentation attaches too late. Call `initOtel()` from\n * `@caronte-sdk/node` yourself, before `import { NestFactory } from\n * '@nestjs/core'` in `main.ts` (CommonJS only — `require()` order is\n * preserved textually there). See the README's \"Running with\n * otelCollectorUrl on NestJS\" section for the full example. The call\n * this module makes is a harmless idempotent no-op either way.\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}"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caronte-sdk/node",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Node.js client for the Argos authorizer (OIDC + RBAC)",
5
5
  "license": "CC0-1.0",
6
6
  "author": "Lucas Ribeiro <lucasribeiro.sec@gmail.com>",