@conduitplatform/module-tools 0.17.0-alpha.8 → 0.17.0-alpha.9

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/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { ConduitSchema, DatabaseProvider, ConduitModel, ConduitSchemaOptions, Query, FindOneOptions, FindManyOptions, AuthzOptions, PopulateAuthzOptions, CountDocumentsOptions, ConduitGrpcSdk, HealthCheckStatus, GrpcRequest, HealthCheckRequest, GrpcCallback, HealthCheckResponse, SetConfigRequest, GrpcResponse, SetConfigResponse, GetExportableResourcesRequest, GetExportableResourcesResponse, ExportResourcesRequest, ExportResourcesResponse, ImportResourcesRequest, ImportResourcesResponse, PeerWatchEdge, TYPE, ConduitNumberValidation, ConduitStringValidation, Params, Context, Cookies, ParsedRouterRequest, UnparsedRouterResponse, ConduitSocketParamTypes, ConduitRouteReturnDefinition, ParsedSocketRequest, UnparsedSocketResponse, Indexable, Router, Admin, ConduitMiddlewareOptions, ConduitRouteOptions, ConduitSocketOptions, ConduitRouteActions, ConduitQueryParams, ConduitUrlParams, ConduitReturn, ConduitRoute, GrpcError, ModuleErrorDefinition } from '@conduitplatform/grpc-sdk';
1
+ import { ConduitSchema, DatabaseProvider, ConduitModel, ConduitSchemaOptions, Query, FindOneOptions, FindManyOptions, AuthzOptions, PopulateAuthzOptions, CountDocumentsOptions, ConduitGrpcSdk, HealthCheckStatus, GrpcRequest, HealthCheckRequest, GrpcCallback, HealthCheckResponse, SetConfigRequest, GrpcResponse, SetConfigResponse, GetExportableResourcesRequest, GetExportableResourcesResponse, ExportResourcesRequest, ExportResourcesResponse, ImportResourcesRequest, ImportResourcesResponse, PeerWatchEdge, TYPE, ConduitNumberValidation, ConduitStringValidation, Params, Context, Cookies, ParsedRouterRequest, Indexable, UnparsedRouterResponse, ConduitSocketParamTypes, ConduitRouteReturnDefinition, ParsedSocketRequest, UnparsedSocketResponse, Router, Admin, ConduitMiddlewareOptions, ConduitRouteOptions, ConduitSocketOptions, ConduitRouteActions, ConduitQueryParams, ConduitUrlParams, ConduitReturn, ConduitRoute, AccessTokenClaims, GrpcError, ModuleErrorDefinition } from '@conduitplatform/grpc-sdk';
2
2
  import { EventEmitter } from 'events';
3
3
  import { ServerWritableStream, Server } from '@grpc/grpc-js';
4
4
  import convict from 'convict';
@@ -345,6 +345,49 @@ declare function wrapGrpcFunctions(functions: {
345
345
 
346
346
  declare function createParsedRouterRequest(params: Params, urlParams?: Params, queryParams?: Params, bodyParams?: Params, path?: string, headers?: Headers, context?: Context, cookies?: Cookies, rawHeaders?: string[], rawBody?: Buffer): ParsedRouterRequest;
347
347
 
348
+ /**
349
+ * User hydration.
350
+ *
351
+ * Client routes guarded by `authMiddleware` / `authMiddleware?` receive `context.user`.
352
+ * In "session" mode the authentication middleware already sets it. In "jwt" mode the
353
+ * router only knows the user id (`context.userId`, from a verified access token), so
354
+ * module-tools fetches the user (through grpcSdk.authentication.getUser, which reads
355
+ * through a 5s Redis cache) before invoking the handler.
356
+ *
357
+ * Opt out per route with `hydrateUser: false` (RouteBuilder.hydrateUser(false)).
358
+ *
359
+ * @deprecated The default flips to off in the next major version. Modules are then
360
+ * expected to call `grpcSdk.authentication.getUser(context.userId)` explicitly.
361
+ */
362
+ interface UserHydrator {
363
+ /** True for `authMiddleware?`: a failed lookup leaves the context untouched. */
364
+ optional: boolean;
365
+ getUser: (userId: string) => Promise<Indexable>;
366
+ }
367
+ /** Whether the route is guarded by an auth middleware, and if so whether it is the optional form. */
368
+ declare function getAuthMiddlewareKind(routeOptions: Indexable): 'required' | 'optional' | undefined;
369
+ /**
370
+ * Pure hydration decision: the route has an auth middleware, `hydrateUser` is not false,
371
+ * and the context carries a `userId` but no `user`. Admin routes must not call this.
372
+ */
373
+ declare function shouldHydrateUser(routeOptions: Indexable | undefined, context: Indexable | undefined): boolean;
374
+ type UserResolver = (userId: string) => Promise<Indexable>;
375
+ /**
376
+ * Builds the per-route hydrator for client routes, or undefined when the route never hydrates.
377
+ * `resolve` is consulted per request (so a resolver registered after route registration still
378
+ * applies) and returns the lookup function to use, if any.
379
+ */
380
+ declare function createUserHydrator(routeOptions: Indexable | undefined, resolve: () => UserResolver | undefined): UserHydrator | undefined;
381
+ /** Maps a failed user lookup to the gRPC code returned to the caller. */
382
+ declare function mapHydrationErrorCode(error: unknown): number;
383
+ /**
384
+ * Sets context.user when needed. For the mandatory form a failed lookup throws a GrpcError:
385
+ * NOT_FOUND becomes UNAUTHENTICATED; PERMISSION_DENIED and every other gRPC code
386
+ * (UNAVAILABLE, DEADLINE_EXCEEDED, INTERNAL, ...) pass through. The optional form
387
+ * swallows the failure and leaves the context untouched. Failures are logged at warn level.
388
+ */
389
+ declare function hydrateContextUser(routeOptions: Indexable, context: Indexable, hydrator: UserHydrator): Promise<void>;
390
+
348
391
  type RouterRequestHandler = (call: ParsedRouterRequest) => Promise<UnparsedRouterResponse>;
349
392
  type RouterRequestHandlerCallback = (call: ParsedRouterRequest, callback: (response: UnparsedRouterResponse) => void) => Promise<UnparsedRouterResponse | undefined | void>;
350
393
  type SocketRequestHandler = (call: ParsedSocketRequest) => Promise<UnparsedSocketResponse>;
@@ -356,7 +399,10 @@ interface SocketEventHandler {
356
399
  returnType?: ConduitRouteReturnDefinition;
357
400
  handler: SocketRequestHandlers;
358
401
  }
359
- declare function wrapRouterGrpcFunction(fun: RequestHandlers | SocketRequestHandlers, routerType: string): (call: Indexable, callback: any) => void;
402
+ declare function wrapRouterGrpcFunction(fun: RequestHandlers | SocketRequestHandlers, routerType: string, hydration?: {
403
+ options: Indexable;
404
+ hydrator: UserHydrator;
405
+ }): (call: Indexable, callback: any) => void;
360
406
 
361
407
  declare class RoutingController {
362
408
  private _routeHandlers;
@@ -372,6 +418,17 @@ declare class RoutingManager {
372
418
  private _moduleRoutes;
373
419
  static ClientController: RoutingController;
374
420
  static AdminController: RoutingController;
421
+ /** Set by ManagedModule; used to hydrate context.user on client routes. */
422
+ static grpcSdk?: ConduitGrpcSdk;
423
+ private static userResolver?;
424
+ private static warnedNoResolver;
425
+ /**
426
+ * Overrides how users are fetched for hydration (default: grpcSdk.authentication.getUser).
427
+ * Intended for the module that owns the users: the authentication module has no gRPC
428
+ * client to itself and will register a local resolver here.
429
+ */
430
+ static setUserResolver(resolver: UserResolver | undefined): void;
431
+ private static resolveUser;
375
432
  private _routeHandlers;
376
433
  private readonly isAdmin;
377
434
  constructor(_router: Router | Admin, _server: GrpcServer);
@@ -409,6 +466,13 @@ declare class RouteBuilder {
409
466
  rateLimit(maxRequests: number, resetInterval: number): RouteBuilder;
410
467
  captureRawBody(capture: boolean): RouteBuilder;
411
468
  captureRawHeaders(capture: boolean): RouteBuilder;
469
+ /**
470
+ * Whether module-tools hydrates `context.user` from `context.userId` on routes with
471
+ * `authMiddleware` / `authMiddleware?` (default true).
472
+ * @deprecated The default flips to false in the next major; call
473
+ * `grpcSdk.authentication.getUser(userId)` explicitly instead.
474
+ */
475
+ hydrateUser(hydrate: boolean): RouteBuilder;
412
476
  return(name: string, fields: ConduitReturn): RouteBuilder;
413
477
  handler(fn: RequestHandlers): RouteBuilder;
414
478
  add(): void;
@@ -417,7 +481,12 @@ declare class RouteBuilder {
417
481
 
418
482
  declare function wrapFunctionsAsync(functions: {
419
483
  [name: string]: RequestHandlers | SocketRequestHandlers;
420
- }, routerType: 'admin' | 'client'): {
484
+ }, routerType: 'admin' | 'client', hydrations?: {
485
+ [name: string]: {
486
+ options: Indexable;
487
+ hydrator: UserHydrator;
488
+ };
489
+ }): {
421
490
  [name: string]: (call: Indexable, callback?: Indexable) => void;
422
491
  };
423
492
 
@@ -502,9 +571,121 @@ declare function reconcileStoredModuleConfig<T>(args: {
502
571
  persisted: boolean;
503
572
  }>;
504
573
 
574
+ interface AccessTokenSigningKeys {
575
+ kid: string;
576
+ /** PEM (SPKI) Ed25519 public key. */
577
+ publicKey: string;
578
+ previousKid?: string;
579
+ previousPublicKey?: string;
580
+ /** Epoch ms until which the previous key is still accepted. 0 (or unset) means no previous key. */
581
+ previousRetiredAt?: number;
582
+ }
583
+ interface AccessTokenVerifierState {
584
+ tokenVersion: number;
585
+ signing: AccessTokenSigningKeys;
586
+ }
587
+ interface AccessTokenVerifyOptions {
588
+ /** Client id of the request. When set, it must equal the token's `cid`. */
589
+ clientId?: string;
590
+ /** Current time in epoch milliseconds. Defaults to Date.now(). */
591
+ now?: number;
592
+ /** Clock skew tolerance for `exp`. Defaults to 5 seconds. */
593
+ leewaySeconds?: number;
594
+ }
595
+ declare const DEFAULT_LEEWAY_SECONDS = 5;
596
+ /** Picks the public key for `kid`: the current key, or the previous one while its retirement is in the future. */
597
+ declare function selectVerificationKey(kid: string | undefined, signing: AccessTokenSigningKeys, now: number): string | undefined;
598
+ /**
599
+ * Verifies an Ed25519 access token. Throws GrpcError(UNAUTHENTICATED) with a short,
600
+ * key-material-free reason on any failure.
601
+ */
602
+ declare function verifyAccessToken(token: string, state: AccessTokenVerifierState, options?: AccessTokenVerifyOptions): AccessTokenClaims;
603
+
604
+ type AccessTokenMode = 'session' | 'jwt';
605
+ interface AccessTokenVerifierSnapshot {
606
+ mode: AccessTokenMode;
607
+ strictRevocation: boolean;
608
+ ready: boolean;
609
+ tokenVersion: number;
610
+ /** Current signing key id, if keys are loaded. */
611
+ kid?: string;
612
+ }
613
+ declare const AUTHENTICATION_CONFIG_UPDATE_CHANNEL = "authentication:config:update";
614
+ declare class AccessTokenVerifier {
615
+ private readonly grpcSdk;
616
+ private static instance?;
617
+ private _mode;
618
+ private _strictRevocation;
619
+ private _tokenVersion;
620
+ private _signing?;
621
+ private _loaded;
622
+ private _subscribed;
623
+ private _generation;
624
+ private _running;
625
+ private _retryTimer?;
626
+ private _refreshTimer?;
627
+ private _lastRefetch;
628
+ private _refetching;
629
+ private _warned;
630
+ private _listeners;
631
+ private constructor();
632
+ static getInstance(grpcSdk: ConduitGrpcSdk): AccessTokenVerifier;
633
+ /**
634
+ * Internal, for tests only: stops the verifier, clears all state and drops the singleton.
635
+ */
636
+ static resetForTests(): void;
637
+ private resetState;
638
+ /** Snapshot of the verifier's observable state. */
639
+ get state(): AccessTokenVerifierSnapshot;
640
+ /**
641
+ * Registers a listener fired synchronously at the end of every applyConfig that changed
642
+ * anything (including the first successful load). Returns an unsubscribe function.
643
+ */
644
+ onChange(listener: (state: AccessTokenVerifierSnapshot) => void): () => void;
645
+ private fingerprint;
646
+ get mode(): AccessTokenMode;
647
+ get strictRevocation(): boolean;
648
+ /** True once signing keys have been loaded. */
649
+ get ready(): boolean;
650
+ get tokenVersion(): number;
651
+ /**
652
+ * Loads the authentication config, subscribes to its updates and starts a slow periodic
653
+ * refresh. The first attempt is awaited; if it fails, retries continue in the background
654
+ * with capped backoff until the config is loaded and the subscription is acknowledged.
655
+ * Idempotent while running; never throws.
656
+ */
657
+ start(): Promise<void>;
658
+ stop(): void;
659
+ private attempt;
660
+ private subscribe;
661
+ /** Refetches the config from the authentication module; failures keep the previous state. */
662
+ private refetch;
663
+ private warnOnce;
664
+ private static validPem;
665
+ /**
666
+ * Validates and normalizes the signing keys. Returns undefined when no signing block is
667
+ * configured, null when the public key is invalid (the caller keeps the previous state).
668
+ */
669
+ private normalizeSigning;
670
+ private validPreviousKey;
671
+ private parseTokenVersion;
672
+ private notifyListeners;
673
+ /**
674
+ * Applies an authentication module config object (full config). Invalid input is ignored
675
+ * and the previous state is kept; invalid keys are logged once.
676
+ */
677
+ applyConfig(config: unknown): void;
678
+ /**
679
+ * Verifies an access token, throwing GrpcError(UNAUTHENTICATED) with a short reason on failure.
680
+ * `options.now` is epoch milliseconds. An `unknown kid` triggers a throttled (at most once
681
+ * per 10 s) background refetch of the config, to pick up a rotation sooner.
682
+ */
683
+ verify(token: string, options?: AccessTokenVerifyOptions): AccessTokenClaims;
684
+ }
685
+
505
686
  declare class ModuleError extends GrpcError {
506
687
  debugLogInfo?: string;
507
688
  constructor(errorDefinition: ModuleErrorDefinition, debugLogInfo?: string);
508
689
  }
509
690
 
510
- export { ConduitActiveSchema, ConduitBoolean, ConduitDate, ConduitJson, ConduitNumber, ConduitObjectId, type ConduitPackageJson, type ConduitPeerWatchEntry, type ConduitPeersManifest, ConduitRelation, type ConduitService, ConduitServiceModule, ConduitString, ConfigController, DEFAULT_STRIP_FIELDS, type ExportModelAdapter, type ExportResult, type ExportableResource, GrpcServer, type ImportModelAdapter, type ImportResult, type ImportResultEntry, ManagedModule, ModuleError, ModuleLifecycleStage, type RequestHandlers, RouteBuilder, type RouterRequestHandler, type RouterRequestHandlerCallback, RoutingManager, type SanitizeForExportOptions, type SocketEventHandler, type SocketRequestHandler, type SocketRequestHandlerCallback, type SocketRequestHandlers, addServiceToServer, containsRedactedMarker, convictConfigParser, createParsedRouterRequest, createServer, exportModel, importModel, initializeSdk, linearBackoffTimeout, linearBackoffTimeoutAsync, merge, readConduitPeersManifest, reconcileStoredModuleConfig, redactSensitiveConfig, restoreRedactedSecrets, sanitizeDocumentsForExport, stableConfigJson, storedConfigsEquivalent, stripInternalFields, wrapFunctionsAsync, wrapGrpcFunctions, wrapRouterGrpcFunction };
691
+ export { AUTHENTICATION_CONFIG_UPDATE_CHANNEL, type AccessTokenMode, type AccessTokenSigningKeys, AccessTokenVerifier, type AccessTokenVerifierSnapshot, type AccessTokenVerifierState, type AccessTokenVerifyOptions, ConduitActiveSchema, ConduitBoolean, ConduitDate, ConduitJson, ConduitNumber, ConduitObjectId, type ConduitPackageJson, type ConduitPeerWatchEntry, type ConduitPeersManifest, ConduitRelation, type ConduitService, ConduitServiceModule, ConduitString, ConfigController, DEFAULT_LEEWAY_SECONDS, DEFAULT_STRIP_FIELDS, type ExportModelAdapter, type ExportResult, type ExportableResource, GrpcServer, type ImportModelAdapter, type ImportResult, type ImportResultEntry, ManagedModule, ModuleError, ModuleLifecycleStage, type RequestHandlers, RouteBuilder, type RouterRequestHandler, type RouterRequestHandlerCallback, RoutingManager, type SanitizeForExportOptions, type SocketEventHandler, type SocketRequestHandler, type SocketRequestHandlerCallback, type SocketRequestHandlers, type UserHydrator, type UserResolver, addServiceToServer, containsRedactedMarker, convictConfigParser, createParsedRouterRequest, createServer, createUserHydrator, exportModel, getAuthMiddlewareKind, hydrateContextUser, importModel, initializeSdk, linearBackoffTimeout, linearBackoffTimeoutAsync, mapHydrationErrorCode, merge, readConduitPeersManifest, reconcileStoredModuleConfig, redactSensitiveConfig, restoreRedactedSecrets, sanitizeDocumentsForExport, selectVerificationKey, shouldHydrateUser, stableConfigJson, storedConfigsEquivalent, stripInternalFields, verifyAccessToken, wrapFunctionsAsync, wrapGrpcFunctions, wrapRouterGrpcFunction };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ConduitSchema, DatabaseProvider, ConduitModel, ConduitSchemaOptions, Query, FindOneOptions, FindManyOptions, AuthzOptions, PopulateAuthzOptions, CountDocumentsOptions, ConduitGrpcSdk, HealthCheckStatus, GrpcRequest, HealthCheckRequest, GrpcCallback, HealthCheckResponse, SetConfigRequest, GrpcResponse, SetConfigResponse, GetExportableResourcesRequest, GetExportableResourcesResponse, ExportResourcesRequest, ExportResourcesResponse, ImportResourcesRequest, ImportResourcesResponse, PeerWatchEdge, TYPE, ConduitNumberValidation, ConduitStringValidation, Params, Context, Cookies, ParsedRouterRequest, UnparsedRouterResponse, ConduitSocketParamTypes, ConduitRouteReturnDefinition, ParsedSocketRequest, UnparsedSocketResponse, Indexable, Router, Admin, ConduitMiddlewareOptions, ConduitRouteOptions, ConduitSocketOptions, ConduitRouteActions, ConduitQueryParams, ConduitUrlParams, ConduitReturn, ConduitRoute, GrpcError, ModuleErrorDefinition } from '@conduitplatform/grpc-sdk';
1
+ import { ConduitSchema, DatabaseProvider, ConduitModel, ConduitSchemaOptions, Query, FindOneOptions, FindManyOptions, AuthzOptions, PopulateAuthzOptions, CountDocumentsOptions, ConduitGrpcSdk, HealthCheckStatus, GrpcRequest, HealthCheckRequest, GrpcCallback, HealthCheckResponse, SetConfigRequest, GrpcResponse, SetConfigResponse, GetExportableResourcesRequest, GetExportableResourcesResponse, ExportResourcesRequest, ExportResourcesResponse, ImportResourcesRequest, ImportResourcesResponse, PeerWatchEdge, TYPE, ConduitNumberValidation, ConduitStringValidation, Params, Context, Cookies, ParsedRouterRequest, Indexable, UnparsedRouterResponse, ConduitSocketParamTypes, ConduitRouteReturnDefinition, ParsedSocketRequest, UnparsedSocketResponse, Router, Admin, ConduitMiddlewareOptions, ConduitRouteOptions, ConduitSocketOptions, ConduitRouteActions, ConduitQueryParams, ConduitUrlParams, ConduitReturn, ConduitRoute, AccessTokenClaims, GrpcError, ModuleErrorDefinition } from '@conduitplatform/grpc-sdk';
2
2
  import { EventEmitter } from 'events';
3
3
  import { ServerWritableStream, Server } from '@grpc/grpc-js';
4
4
  import convict from 'convict';
@@ -345,6 +345,49 @@ declare function wrapGrpcFunctions(functions: {
345
345
 
346
346
  declare function createParsedRouterRequest(params: Params, urlParams?: Params, queryParams?: Params, bodyParams?: Params, path?: string, headers?: Headers, context?: Context, cookies?: Cookies, rawHeaders?: string[], rawBody?: Buffer): ParsedRouterRequest;
347
347
 
348
+ /**
349
+ * User hydration.
350
+ *
351
+ * Client routes guarded by `authMiddleware` / `authMiddleware?` receive `context.user`.
352
+ * In "session" mode the authentication middleware already sets it. In "jwt" mode the
353
+ * router only knows the user id (`context.userId`, from a verified access token), so
354
+ * module-tools fetches the user (through grpcSdk.authentication.getUser, which reads
355
+ * through a 5s Redis cache) before invoking the handler.
356
+ *
357
+ * Opt out per route with `hydrateUser: false` (RouteBuilder.hydrateUser(false)).
358
+ *
359
+ * @deprecated The default flips to off in the next major version. Modules are then
360
+ * expected to call `grpcSdk.authentication.getUser(context.userId)` explicitly.
361
+ */
362
+ interface UserHydrator {
363
+ /** True for `authMiddleware?`: a failed lookup leaves the context untouched. */
364
+ optional: boolean;
365
+ getUser: (userId: string) => Promise<Indexable>;
366
+ }
367
+ /** Whether the route is guarded by an auth middleware, and if so whether it is the optional form. */
368
+ declare function getAuthMiddlewareKind(routeOptions: Indexable): 'required' | 'optional' | undefined;
369
+ /**
370
+ * Pure hydration decision: the route has an auth middleware, `hydrateUser` is not false,
371
+ * and the context carries a `userId` but no `user`. Admin routes must not call this.
372
+ */
373
+ declare function shouldHydrateUser(routeOptions: Indexable | undefined, context: Indexable | undefined): boolean;
374
+ type UserResolver = (userId: string) => Promise<Indexable>;
375
+ /**
376
+ * Builds the per-route hydrator for client routes, or undefined when the route never hydrates.
377
+ * `resolve` is consulted per request (so a resolver registered after route registration still
378
+ * applies) and returns the lookup function to use, if any.
379
+ */
380
+ declare function createUserHydrator(routeOptions: Indexable | undefined, resolve: () => UserResolver | undefined): UserHydrator | undefined;
381
+ /** Maps a failed user lookup to the gRPC code returned to the caller. */
382
+ declare function mapHydrationErrorCode(error: unknown): number;
383
+ /**
384
+ * Sets context.user when needed. For the mandatory form a failed lookup throws a GrpcError:
385
+ * NOT_FOUND becomes UNAUTHENTICATED; PERMISSION_DENIED and every other gRPC code
386
+ * (UNAVAILABLE, DEADLINE_EXCEEDED, INTERNAL, ...) pass through. The optional form
387
+ * swallows the failure and leaves the context untouched. Failures are logged at warn level.
388
+ */
389
+ declare function hydrateContextUser(routeOptions: Indexable, context: Indexable, hydrator: UserHydrator): Promise<void>;
390
+
348
391
  type RouterRequestHandler = (call: ParsedRouterRequest) => Promise<UnparsedRouterResponse>;
349
392
  type RouterRequestHandlerCallback = (call: ParsedRouterRequest, callback: (response: UnparsedRouterResponse) => void) => Promise<UnparsedRouterResponse | undefined | void>;
350
393
  type SocketRequestHandler = (call: ParsedSocketRequest) => Promise<UnparsedSocketResponse>;
@@ -356,7 +399,10 @@ interface SocketEventHandler {
356
399
  returnType?: ConduitRouteReturnDefinition;
357
400
  handler: SocketRequestHandlers;
358
401
  }
359
- declare function wrapRouterGrpcFunction(fun: RequestHandlers | SocketRequestHandlers, routerType: string): (call: Indexable, callback: any) => void;
402
+ declare function wrapRouterGrpcFunction(fun: RequestHandlers | SocketRequestHandlers, routerType: string, hydration?: {
403
+ options: Indexable;
404
+ hydrator: UserHydrator;
405
+ }): (call: Indexable, callback: any) => void;
360
406
 
361
407
  declare class RoutingController {
362
408
  private _routeHandlers;
@@ -372,6 +418,17 @@ declare class RoutingManager {
372
418
  private _moduleRoutes;
373
419
  static ClientController: RoutingController;
374
420
  static AdminController: RoutingController;
421
+ /** Set by ManagedModule; used to hydrate context.user on client routes. */
422
+ static grpcSdk?: ConduitGrpcSdk;
423
+ private static userResolver?;
424
+ private static warnedNoResolver;
425
+ /**
426
+ * Overrides how users are fetched for hydration (default: grpcSdk.authentication.getUser).
427
+ * Intended for the module that owns the users: the authentication module has no gRPC
428
+ * client to itself and will register a local resolver here.
429
+ */
430
+ static setUserResolver(resolver: UserResolver | undefined): void;
431
+ private static resolveUser;
375
432
  private _routeHandlers;
376
433
  private readonly isAdmin;
377
434
  constructor(_router: Router | Admin, _server: GrpcServer);
@@ -409,6 +466,13 @@ declare class RouteBuilder {
409
466
  rateLimit(maxRequests: number, resetInterval: number): RouteBuilder;
410
467
  captureRawBody(capture: boolean): RouteBuilder;
411
468
  captureRawHeaders(capture: boolean): RouteBuilder;
469
+ /**
470
+ * Whether module-tools hydrates `context.user` from `context.userId` on routes with
471
+ * `authMiddleware` / `authMiddleware?` (default true).
472
+ * @deprecated The default flips to false in the next major; call
473
+ * `grpcSdk.authentication.getUser(userId)` explicitly instead.
474
+ */
475
+ hydrateUser(hydrate: boolean): RouteBuilder;
412
476
  return(name: string, fields: ConduitReturn): RouteBuilder;
413
477
  handler(fn: RequestHandlers): RouteBuilder;
414
478
  add(): void;
@@ -417,7 +481,12 @@ declare class RouteBuilder {
417
481
 
418
482
  declare function wrapFunctionsAsync(functions: {
419
483
  [name: string]: RequestHandlers | SocketRequestHandlers;
420
- }, routerType: 'admin' | 'client'): {
484
+ }, routerType: 'admin' | 'client', hydrations?: {
485
+ [name: string]: {
486
+ options: Indexable;
487
+ hydrator: UserHydrator;
488
+ };
489
+ }): {
421
490
  [name: string]: (call: Indexable, callback?: Indexable) => void;
422
491
  };
423
492
 
@@ -502,9 +571,121 @@ declare function reconcileStoredModuleConfig<T>(args: {
502
571
  persisted: boolean;
503
572
  }>;
504
573
 
574
+ interface AccessTokenSigningKeys {
575
+ kid: string;
576
+ /** PEM (SPKI) Ed25519 public key. */
577
+ publicKey: string;
578
+ previousKid?: string;
579
+ previousPublicKey?: string;
580
+ /** Epoch ms until which the previous key is still accepted. 0 (or unset) means no previous key. */
581
+ previousRetiredAt?: number;
582
+ }
583
+ interface AccessTokenVerifierState {
584
+ tokenVersion: number;
585
+ signing: AccessTokenSigningKeys;
586
+ }
587
+ interface AccessTokenVerifyOptions {
588
+ /** Client id of the request. When set, it must equal the token's `cid`. */
589
+ clientId?: string;
590
+ /** Current time in epoch milliseconds. Defaults to Date.now(). */
591
+ now?: number;
592
+ /** Clock skew tolerance for `exp`. Defaults to 5 seconds. */
593
+ leewaySeconds?: number;
594
+ }
595
+ declare const DEFAULT_LEEWAY_SECONDS = 5;
596
+ /** Picks the public key for `kid`: the current key, or the previous one while its retirement is in the future. */
597
+ declare function selectVerificationKey(kid: string | undefined, signing: AccessTokenSigningKeys, now: number): string | undefined;
598
+ /**
599
+ * Verifies an Ed25519 access token. Throws GrpcError(UNAUTHENTICATED) with a short,
600
+ * key-material-free reason on any failure.
601
+ */
602
+ declare function verifyAccessToken(token: string, state: AccessTokenVerifierState, options?: AccessTokenVerifyOptions): AccessTokenClaims;
603
+
604
+ type AccessTokenMode = 'session' | 'jwt';
605
+ interface AccessTokenVerifierSnapshot {
606
+ mode: AccessTokenMode;
607
+ strictRevocation: boolean;
608
+ ready: boolean;
609
+ tokenVersion: number;
610
+ /** Current signing key id, if keys are loaded. */
611
+ kid?: string;
612
+ }
613
+ declare const AUTHENTICATION_CONFIG_UPDATE_CHANNEL = "authentication:config:update";
614
+ declare class AccessTokenVerifier {
615
+ private readonly grpcSdk;
616
+ private static instance?;
617
+ private _mode;
618
+ private _strictRevocation;
619
+ private _tokenVersion;
620
+ private _signing?;
621
+ private _loaded;
622
+ private _subscribed;
623
+ private _generation;
624
+ private _running;
625
+ private _retryTimer?;
626
+ private _refreshTimer?;
627
+ private _lastRefetch;
628
+ private _refetching;
629
+ private _warned;
630
+ private _listeners;
631
+ private constructor();
632
+ static getInstance(grpcSdk: ConduitGrpcSdk): AccessTokenVerifier;
633
+ /**
634
+ * Internal, for tests only: stops the verifier, clears all state and drops the singleton.
635
+ */
636
+ static resetForTests(): void;
637
+ private resetState;
638
+ /** Snapshot of the verifier's observable state. */
639
+ get state(): AccessTokenVerifierSnapshot;
640
+ /**
641
+ * Registers a listener fired synchronously at the end of every applyConfig that changed
642
+ * anything (including the first successful load). Returns an unsubscribe function.
643
+ */
644
+ onChange(listener: (state: AccessTokenVerifierSnapshot) => void): () => void;
645
+ private fingerprint;
646
+ get mode(): AccessTokenMode;
647
+ get strictRevocation(): boolean;
648
+ /** True once signing keys have been loaded. */
649
+ get ready(): boolean;
650
+ get tokenVersion(): number;
651
+ /**
652
+ * Loads the authentication config, subscribes to its updates and starts a slow periodic
653
+ * refresh. The first attempt is awaited; if it fails, retries continue in the background
654
+ * with capped backoff until the config is loaded and the subscription is acknowledged.
655
+ * Idempotent while running; never throws.
656
+ */
657
+ start(): Promise<void>;
658
+ stop(): void;
659
+ private attempt;
660
+ private subscribe;
661
+ /** Refetches the config from the authentication module; failures keep the previous state. */
662
+ private refetch;
663
+ private warnOnce;
664
+ private static validPem;
665
+ /**
666
+ * Validates and normalizes the signing keys. Returns undefined when no signing block is
667
+ * configured, null when the public key is invalid (the caller keeps the previous state).
668
+ */
669
+ private normalizeSigning;
670
+ private validPreviousKey;
671
+ private parseTokenVersion;
672
+ private notifyListeners;
673
+ /**
674
+ * Applies an authentication module config object (full config). Invalid input is ignored
675
+ * and the previous state is kept; invalid keys are logged once.
676
+ */
677
+ applyConfig(config: unknown): void;
678
+ /**
679
+ * Verifies an access token, throwing GrpcError(UNAUTHENTICATED) with a short reason on failure.
680
+ * `options.now` is epoch milliseconds. An `unknown kid` triggers a throttled (at most once
681
+ * per 10 s) background refetch of the config, to pick up a rotation sooner.
682
+ */
683
+ verify(token: string, options?: AccessTokenVerifyOptions): AccessTokenClaims;
684
+ }
685
+
505
686
  declare class ModuleError extends GrpcError {
506
687
  debugLogInfo?: string;
507
688
  constructor(errorDefinition: ModuleErrorDefinition, debugLogInfo?: string);
508
689
  }
509
690
 
510
- export { ConduitActiveSchema, ConduitBoolean, ConduitDate, ConduitJson, ConduitNumber, ConduitObjectId, type ConduitPackageJson, type ConduitPeerWatchEntry, type ConduitPeersManifest, ConduitRelation, type ConduitService, ConduitServiceModule, ConduitString, ConfigController, DEFAULT_STRIP_FIELDS, type ExportModelAdapter, type ExportResult, type ExportableResource, GrpcServer, type ImportModelAdapter, type ImportResult, type ImportResultEntry, ManagedModule, ModuleError, ModuleLifecycleStage, type RequestHandlers, RouteBuilder, type RouterRequestHandler, type RouterRequestHandlerCallback, RoutingManager, type SanitizeForExportOptions, type SocketEventHandler, type SocketRequestHandler, type SocketRequestHandlerCallback, type SocketRequestHandlers, addServiceToServer, containsRedactedMarker, convictConfigParser, createParsedRouterRequest, createServer, exportModel, importModel, initializeSdk, linearBackoffTimeout, linearBackoffTimeoutAsync, merge, readConduitPeersManifest, reconcileStoredModuleConfig, redactSensitiveConfig, restoreRedactedSecrets, sanitizeDocumentsForExport, stableConfigJson, storedConfigsEquivalent, stripInternalFields, wrapFunctionsAsync, wrapGrpcFunctions, wrapRouterGrpcFunction };
691
+ export { AUTHENTICATION_CONFIG_UPDATE_CHANNEL, type AccessTokenMode, type AccessTokenSigningKeys, AccessTokenVerifier, type AccessTokenVerifierSnapshot, type AccessTokenVerifierState, type AccessTokenVerifyOptions, ConduitActiveSchema, ConduitBoolean, ConduitDate, ConduitJson, ConduitNumber, ConduitObjectId, type ConduitPackageJson, type ConduitPeerWatchEntry, type ConduitPeersManifest, ConduitRelation, type ConduitService, ConduitServiceModule, ConduitString, ConfigController, DEFAULT_LEEWAY_SECONDS, DEFAULT_STRIP_FIELDS, type ExportModelAdapter, type ExportResult, type ExportableResource, GrpcServer, type ImportModelAdapter, type ImportResult, type ImportResultEntry, ManagedModule, ModuleError, ModuleLifecycleStage, type RequestHandlers, RouteBuilder, type RouterRequestHandler, type RouterRequestHandlerCallback, RoutingManager, type SanitizeForExportOptions, type SocketEventHandler, type SocketRequestHandler, type SocketRequestHandlerCallback, type SocketRequestHandlers, type UserHydrator, type UserResolver, addServiceToServer, containsRedactedMarker, convictConfigParser, createParsedRouterRequest, createServer, createUserHydrator, exportModel, getAuthMiddlewareKind, hydrateContextUser, importModel, initializeSdk, linearBackoffTimeout, linearBackoffTimeoutAsync, mapHydrationErrorCode, merge, readConduitPeersManifest, reconcileStoredModuleConfig, redactSensitiveConfig, restoreRedactedSecrets, sanitizeDocumentsForExport, selectVerificationKey, shouldHydrateUser, stableConfigJson, storedConfigsEquivalent, stripInternalFields, verifyAccessToken, wrapFunctionsAsync, wrapGrpcFunctions, wrapRouterGrpcFunction };