@camstack/server 1.2.486 → 1.2.488

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.
Files changed (38) hide show
  1. package/dist/agent/agent-mesh-join.js +121 -0
  2. package/dist/agent/agent-service.js +4 -0
  3. package/dist/agent/main.js +62 -2
  4. package/dist/api/auth-whoami.js +1 -1
  5. package/dist/api/core/auth-admin-tokens.procedures.js +138 -0
  6. package/dist/api/core/auth-limits.js +77 -0
  7. package/dist/api/core/auth-reauth.procedures.js +108 -0
  8. package/dist/api/core/auth-sessions.procedures.js +166 -0
  9. package/dist/api/core/auth.router.js +281 -80
  10. package/dist/api/health/adaptive-probe.routes.js +1 -1
  11. package/dist/api/mesh/enroll.routes.js +28 -0
  12. package/dist/api/trpc/core-cap-bridge.js +88 -5
  13. package/dist/api/trpc/generated-cap-mounts.js +19 -1
  14. package/dist/api/trpc/generated-cap-routers.js +512 -205
  15. package/dist/api/trpc/trpc.context.js +86 -34
  16. package/dist/api/trpc/trpc.middleware.js +8 -11
  17. package/dist/api/trpc/trpc.router.js +6 -1
  18. package/dist/auth/auth-network-policy.js +75 -0
  19. package/dist/auth/auth-rate-limit.js +25 -20
  20. package/dist/auth/client-address.js +68 -0
  21. package/dist/auth/external-link-intents.js +12 -3
  22. package/dist/auth/hub-sessions.js +74 -0
  23. package/dist/auth/native-link-completions.js +68 -0
  24. package/dist/auth/native-return.js +33 -0
  25. package/dist/auth/session-lifetimes.js +46 -0
  26. package/dist/auth/session-mint.js +110 -13
  27. package/dist/auth/sso-finish-deps.js +56 -1
  28. package/dist/auth/sso-finish.js +132 -17
  29. package/dist/auth/sso-routes.js +95 -0
  30. package/dist/auth/used-bridge-ids.js +52 -0
  31. package/dist/core/addon/addon-registry.service.js +5 -0
  32. package/dist/core/agent/agent-registry.service.js +23 -0
  33. package/dist/core/auth/auth.service.js +50 -1
  34. package/dist/core/auth/handoff-code.service.js +9 -3
  35. package/dist/core/moleculer/hub-mesh-guard.js +132 -0
  36. package/dist/core/moleculer/moleculer.service.js +46 -0
  37. package/dist/main.js +97 -29
  38. package/package.json +13 -13
package/dist/main.js CHANGED
@@ -59,6 +59,7 @@ const ws_1 = require("@trpc/server/adapters/ws");
59
59
  const ws_2 = require("ws");
60
60
  const addon_upload_1 = require("./api/addon-upload");
61
61
  const auth_whoami_1 = require("./api/auth-whoami");
62
+ const enroll_routes_js_1 = require("./api/mesh/enroll.routes.js");
62
63
  const backup_download_js_1 = require("./api/backup-download.js");
63
64
  const cap_providers_1 = require("./api/core/cap-providers");
64
65
  const logging_settings_1 = require("./api/core/logging-settings");
@@ -86,12 +87,17 @@ const ws_request_census_1 = require("./api/trpc/ws-request-census");
86
87
  const ws_slow_consumer_guard_js_1 = require("./api/trpc/ws-slow-consumer-guard.js");
87
88
  const addon_route_jwt_gate_js_1 = require("./auth/addon-route-jwt-gate.js");
88
89
  const addon_route_share_gate_js_1 = require("./auth/addon-route-share-gate.js");
90
+ const auth_network_policy_js_1 = require("./auth/auth-network-policy.js");
91
+ const hub_sessions_js_1 = require("./auth/hub-sessions.js");
92
+ const session_lifetimes_js_1 = require("./auth/session-lifetimes.js");
89
93
  const auth_rate_limit_1 = require("./auth/auth-rate-limit");
90
94
  const external_link_intents_js_1 = require("./auth/external-link-intents.js");
91
95
  const session_cookie_js_1 = require("./auth/session-cookie.js");
92
96
  const session_mint_js_1 = require("./auth/session-mint.js");
93
- const sso_finish_js_1 = require("./auth/sso-finish.js");
94
97
  const sso_finish_deps_js_1 = require("./auth/sso-finish-deps.js");
98
+ const sso_routes_js_1 = require("./auth/sso-routes.js");
99
+ const used_bridge_ids_js_1 = require("./auth/used-bridge-ids.js");
100
+ const native_link_completions_js_1 = require("./auth/native-link-completions.js");
95
101
  const boot_config_1 = require("./boot/boot-config");
96
102
  const platform_keychain_key_1 = require("./boot/platform-keychain-key");
97
103
  const integration_id_backfill_1 = require("./boot/integration-id-backfill");
@@ -410,6 +416,16 @@ async function bootstrap() {
410
416
  storageService.setLocationManager(infra.locationManager);
411
417
  // ConfigService — SettingsStore is wired later by the settings-store capability consumer
412
418
  const config = app.get(config_service_1.ConfigService);
419
+ // The real client address behind a trusted proxy, and the opt-in trusted
420
+ // networks — read from settings per request (auth-network-policy.ts).
421
+ const authNetwork = (0, auth_network_policy_js_1.createAuthNetworkPolicy)((key) => config.get(key));
422
+ // Public addon routes that declare `rateLimit: 'auth'` (an IdP start /
423
+ // callback) and the SSO finish: 30 per minute per real client address.
424
+ const authRouteLimiter = (0, auth_rate_limit_1.createRateLimiter)({ windowMs: 60_000, max: 30 });
425
+ // Login sessions (local-auth's table, reached through its hub-internal
426
+ // surface): every session token minted from here on carries a `sid`, and
427
+ // every verify of one asks whether that session is still live.
428
+ const sessionLifetimes = () => (0, session_lifetimes_js_1.readSessionLifetimes)((key) => config.get(key));
413
429
  // Register addon upload route (multipart — must be registered before tRPC).
414
430
  // We pass AddonRegistryService so the handler can resolve the
415
431
  // `user-management` cap singleton at request time (the only working
@@ -442,6 +458,7 @@ async function bootstrap() {
442
458
  // Mirrors the addon-upload auth chain so the CLI can ping for
443
459
  // token-still-valid without consuming a real cap.
444
460
  await (0, auth_whoami_1.registerAuthWhoamiRoute)(fastify, uploadAuthService, uploadAddonRegistry);
461
+ (0, enroll_routes_js_1.registerMeshEnrollRoute)(fastify);
445
462
  console.log('[bootstrap] Auth whoami route registered at GET /api/auth/whoami');
446
463
  }
447
464
  catch (err) {
@@ -498,6 +515,11 @@ async function bootstrap() {
498
515
  addonRegistry.setDataPlaneRegistry(dataPlaneRegistry);
499
516
  // ── Configure the CapabilityRegistry (created in AddonRegistryService constructor) ──
500
517
  const capabilityRegistry = addonRegistry.getCapabilityRegistry();
518
+ const sessionBacking = (0, hub_sessions_js_1.sessionBackingFrom)(capabilityRegistry, sessionLifetimes);
519
+ // From here on every verify of a session-grade token — tRPC, addon routes,
520
+ // the session cookie, OAuth authorize, uploads, health — requires its
521
+ // session to be live (`hub-sessions.ts`).
522
+ app.get(auth_service_1.AuthService).setSessionCheck((0, hub_sessions_js_1.createSessionTokenCheck)(capabilityRegistry));
501
523
  // Declare every shipped capability BEFORE app.init() so addon
502
524
  // registerProvider() calls (in-process or via the Moleculer bridge
503
525
  // from forked workers / agents) find their definition during
@@ -507,7 +529,7 @@ async function bootstrap() {
507
529
  capabilityRegistry.declareCapability(capDef);
508
530
  }
509
531
  // Hub-internal `sso-bridge` provider — gives auth-provider addons
510
- // (OIDC, SAML, magic-link, …) a typed gateway to mint an HMAC-signed
532
+ // (OIDC, SAML, …) a typed gateway to mint an HMAC-signed
511
533
  // bridge token before redirecting to `/api/auth/sso/finish`. Without
512
534
  // this, the finish endpoint would have to trust unsigned query params
513
535
  // (anyone could craft `?isAdmin=1`). Backed by AuthService.
@@ -538,12 +560,41 @@ async function bootstrap() {
538
560
  // Pending "link this sign-in account" intents (D782) — minted by
539
561
  // `auth.beginExternalLink`, consumed once by `/api/auth/sso/finish`.
540
562
  const externalLinkIntents = new external_link_intents_js_1.ExternalLinkIntents();
563
+ // Native-app links parked for the app to claim, and one-time codes that
564
+ // open the account page signed in, in a browser (D801).
565
+ const nativeLinkCompletions = new native_link_completions_js_1.NativeLinkCompletions();
566
+ // `jti`s of login bridges already finished — a bridge is single-use.
567
+ const usedBridgeIds = new used_bridge_ids_js_1.UsedBridgeIds();
568
+ const browserHandoffCodes = new handoff_code_service_1.HandoffCodeService();
569
+ const nativeLinkMinter = (0, session_mint_js_1.createSessionMinter)({
570
+ signer: app.get(auth_service_1.AuthService),
571
+ directory: () => capabilityRegistry.getSingleton('user-management'),
572
+ sessions: sessionBacking,
573
+ });
574
+ const nativeLinkWiring = () => ({
575
+ auth: app.get(auth_service_1.AuthService),
576
+ registry: capabilityRegistry,
577
+ moleculer: app.get(moleculer_service_1.MoleculerService),
578
+ minter: nativeLinkMinter,
579
+ linkIntents: externalLinkIntents,
580
+ nativeLinks: nativeLinkCompletions,
581
+ usedBridgeIds,
582
+ });
541
583
  const buildRouter = () => (0, trpc_router_1.buildAppRouter)({
542
584
  authService: app.get(auth_service_1.AuthService),
543
585
  shareTokenService,
544
586
  handoffCodeService,
545
587
  externalLinkIntents,
588
+ nativeLink: {
589
+ completions: nativeLinkCompletions,
590
+ browserHandoffs: browserHandoffCodes,
591
+ // The SSO finish's own link step — the same conflict rule, the same
592
+ // store — run for the app's claim instead of the browser's return.
593
+ link: (input) => (0, sso_finish_deps_js_1.createSsoFinishDeps)(nativeLinkWiring(), undefined).identities.link(input),
594
+ },
546
595
  configService: config,
596
+ authNetworkPolicy: authNetwork,
597
+ sessionBacking,
547
598
  featureService: app.get(feature_service_1.FeatureService),
548
599
  loggingService,
549
600
  eventBus: app.get(event_bus_service_1.EventBusService),
@@ -582,7 +633,11 @@ async function bootstrap() {
582
633
  beforeAddonBoot: () => {
583
634
  try {
584
635
  appRouter = buildRouter();
585
- app.get(moleculer_service_1.MoleculerService).registerCoreCapService((0, core_cap_bridge_1.buildCoreCapService)(appRouter));
636
+ {
637
+ const meshBroker = app.get(moleculer_service_1.MoleculerService);
638
+ // D802: another node's call runs as `node:<id>`, never the hub's own principal.
639
+ meshBroker.registerCoreCapService((0, core_cap_bridge_1.buildCoreCapService)(appRouter, undefined, { ownNodeId: meshBroker.nodeId }));
640
+ }
586
641
  console.log('[bootstrap] core-cap mesh bridge registered ($core-caps) — before addon boot');
587
642
  }
588
643
  catch (err) {
@@ -629,6 +684,7 @@ async function bootstrap() {
629
684
  (0, adaptive_probe_routes_1.registerAdaptiveProbeRoute)(fastify, {
630
685
  verifyToken: (token) => app.get(auth_service_1.AuthService).verifyToken(token),
631
686
  limiter: (0, auth_rate_limit_1.createRateLimiter)({ windowMs: 60_000, max: adaptive_probe_routes_1.ADAPTIVE_PROBE_PER_IP_PER_MINUTE }),
687
+ network: authNetwork,
632
688
  });
633
689
  }
634
690
  catch (err) {
@@ -661,7 +717,9 @@ async function bootstrap() {
661
717
  // an operator armed earlier. The logger is attached now so a restored census
662
718
  // has somewhere to report before the first request arrives.
663
719
  const httpRequestCensus = (0, http_request_census_singleton_1.getHttpRequestCensus)();
664
- httpRequestCensus.setLogger(loggingService.createLogger('tRPC'));
720
+ const trpcLogger = loggingService.createLogger('tRPC');
721
+ httpRequestCensus.setLogger(trpcLogger);
722
+ (0, core_cap_bridge_1.setRemotePrivilegeLogSink)(trpcLogger);
665
723
  try {
666
724
  const authService = app.get(auth_service_1.AuthService);
667
725
  // Normally already built and bridged onto the mesh in `beforeAddonBoot`
@@ -670,7 +728,11 @@ async function bootstrap() {
670
728
  if (appRouter === null) {
671
729
  appRouter = buildRouter();
672
730
  try {
673
- app.get(moleculer_service_1.MoleculerService).registerCoreCapService((0, core_cap_bridge_1.buildCoreCapService)(appRouter));
731
+ {
732
+ const meshBroker = app.get(moleculer_service_1.MoleculerService);
733
+ // D802: another node's call runs as `node:<id>`, never the hub's own principal.
734
+ meshBroker.registerCoreCapService((0, core_cap_bridge_1.buildCoreCapService)(appRouter, undefined, { ownNodeId: meshBroker.nodeId }));
735
+ }
674
736
  console.log('[bootstrap] core-cap mesh bridge registered ($core-caps) — late fallback');
675
737
  }
676
738
  catch (err) {
@@ -820,37 +882,31 @@ async function bootstrap() {
820
882
  reply.header('cache-control', 'public, max-age=86400');
821
883
  return reply.send(fs.readFileSync(filePath));
822
884
  });
823
- // ── SSO finish endpoint ─────────────────────────────────────────
824
- // OIDC / magic-link redirect here with an HMAC-signed LOGIN bridge
825
- // (`sso-bridge` cap). The bridge says who signed in; the session is
826
- // minted by THE session mint from the user record as it is now (D712) —
827
- // `auth/sso-finish.ts` holds the rules, this route only maps the reply.
828
- const ssoAuth = app.get(auth_service_1.AuthService);
829
- const ssoLogger = app.get(logging_service_1.LoggingService).createLogger('sso-finish');
885
+ // ── SSO finish + browser handoff ────────────────────────────────
886
+ // OIDC redirects to the finish with an HMAC-signed, single-use LOGIN
887
+ // bridge bound to the browser that started it; the session is minted by
888
+ // THE session mint from the user record as it is now (D712). Rules in
889
+ // `auth/sso-finish.ts`, routes in `auth/sso-routes.ts`.
830
890
  const ssoMinter = (0, session_mint_js_1.createSessionMinter)({
831
- signer: ssoAuth,
891
+ signer: app.get(auth_service_1.AuthService),
832
892
  directory: () => capabilityRegistry.getSingleton('user-management'),
893
+ sessions: sessionBacking,
833
894
  });
834
- fastify.get('/api/auth/sso/finish', async (request, reply) => {
835
- const outcome = await (0, sso_finish_js_1.finishSsoLogin)(request.query.bridge, (0, sso_finish_deps_js_1.createSsoFinishDeps)({
836
- auth: ssoAuth,
895
+ (0, sso_routes_js_1.registerSsoRoutes)(fastify, {
896
+ wiring: {
897
+ auth: app.get(auth_service_1.AuthService),
837
898
  registry: capabilityRegistry,
838
899
  moleculer: app.get(moleculer_service_1.MoleculerService),
839
900
  minter: ssoMinter,
840
901
  linkIntents: externalLinkIntents,
841
- logFailure: (error) => ssoLogger.error('SSO finish failed', {
842
- meta: { error: error instanceof Error ? error.message : String(error) },
843
- }),
844
- }, request.headers.cookie),
845
- // A popup sign-in (D791) returns through the admin-ui completion page.
846
- { popup: request.query.popup === '1' });
847
- if (outcome.kind === 'error')
848
- return reply.status(outcome.status).send(outcome.body);
849
- reply.code(302);
850
- // Admin-ui paths are relative to the hub's mount (HA ingress etc.).
851
- const ssoMount = (0, spa_mount_1.parseForwardedPrefix)(request.headers['x-forwarded-prefix']) ?? '';
852
- reply.header('Location', `${ssoMount}${outcome.location}`);
853
- return reply.send('');
902
+ nativeLinks: nativeLinkCompletions,
903
+ usedBridgeIds,
904
+ },
905
+ network: authNetwork,
906
+ limiter: authRouteLimiter,
907
+ browserHandoffCodes,
908
+ minter: ssoMinter,
909
+ logger: app.get(logging_service_1.LoggingService).createLogger('sso-finish'),
854
910
  });
855
911
  // ── Backup archive download ─────────────────────────────────────
856
912
  // Streams an archive through the storage cap's chunked-download
@@ -1052,6 +1108,18 @@ async function bootstrap() {
1052
1108
  }
1053
1109
  return reply.status(404).send({ error: 'Not found' });
1054
1110
  }
1111
+ // A route that asked for it (an IdP start / callback) is limited per
1112
+ // REAL client address before its handler runs.
1113
+ if (match.route.rateLimit === 'auth') {
1114
+ const routeClient = (0, auth_rate_limit_1.clientKeyFromRequest)(request, authNetwork);
1115
+ if (routeClient !== null) {
1116
+ const verdict = authRouteLimiter.check(`route:${addonId}:${routeClient}`);
1117
+ if (!verdict.allowed) {
1118
+ reply.header('retry-after', String(verdict.retryAfterSeconds));
1119
+ return reply.status(429).send({ error: 'Too many attempts' });
1120
+ }
1121
+ }
1122
+ }
1055
1123
  // Auth check based on route.access
1056
1124
  if (match.route.access !== 'public') {
1057
1125
  const authHeader = request.headers.authorization;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.486",
3
+ "version": "1.2.488",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "files": [
@@ -37,18 +37,18 @@
37
37
  ]
38
38
  },
39
39
  "dependencies": {
40
- "@camstack/addon-admin-ui": "1.2.359",
41
- "@camstack/addon-agent-ui": "1.2.195",
42
- "@camstack/addon-auth": "1.2.134",
43
- "@camstack/addon-decoder-nodeav": "1.2.121",
44
- "@camstack/addon-notifiers": "1.2.128",
45
- "@camstack/addon-pipeline": "1.2.346",
46
- "@camstack/addon-pipeline-orchestrator": "1.2.298",
47
- "@camstack/addon-post-analysis": "1.2.347",
48
- "@camstack/sdk": "1.2.127",
49
- "@camstack/system": "1.2.381",
50
- "@camstack/types": "1.2.332",
51
- "@camstack/ui-library": "1.2.253",
40
+ "@camstack/addon-admin-ui": "1.2.361",
41
+ "@camstack/addon-agent-ui": "1.2.196",
42
+ "@camstack/addon-auth": "1.2.135",
43
+ "@camstack/addon-decoder-nodeav": "1.2.122",
44
+ "@camstack/addon-notifiers": "1.2.129",
45
+ "@camstack/addon-pipeline": "1.2.347",
46
+ "@camstack/addon-pipeline-orchestrator": "1.2.300",
47
+ "@camstack/addon-post-analysis": "1.2.348",
48
+ "@camstack/sdk": "1.2.128",
49
+ "@camstack/system": "1.2.383",
50
+ "@camstack/types": "1.2.333",
51
+ "@camstack/ui-library": "1.2.254",
52
52
  "@fastify/compress": "^9.0.0",
53
53
  "@fastify/cookie": "^11.0.2",
54
54
  "@fastify/cors": "^11.2.0",