@ciq-dev/neoiq-foundation-node 1.0.2 → 1.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const require_tracing = require('./tracing-CMXNnEXN.js');
2
+ const require_tracing = require('./tracing-MhJ1fEY_.js');
3
3
  const __opentelemetry_resources = require_tracing.__toESM(require("@opentelemetry/resources"));
4
4
  const __opentelemetry_semantic_conventions = require_tracing.__toESM(require("@opentelemetry/semantic-conventions"));
5
5
  const __opentelemetry_api = require_tracing.__toESM(require("@opentelemetry/api"));
@@ -734,109 +734,100 @@ function createHttpClient(options) {
734
734
 
735
735
  //#endregion
736
736
  //#region src/features/vault.ts
737
+ const DEFAULTS = {
738
+ address: "https://vault.beta.commerceiq.ai",
739
+ authMethod: "kubernetes",
740
+ mountPoint: "secret",
741
+ tokenPath: "/var/run/secrets/kubernetes.io/serviceaccount/token",
742
+ k8sAuthMount: "kubernetes"
743
+ };
744
+ function stripSlashes(value) {
745
+ return value.replace(/^\/+|\/+$/g, "");
746
+ }
737
747
  async function loadNodeVault() {
738
748
  try {
739
749
  const mod = await import("node-vault");
740
750
  return mod.default ?? mod;
741
751
  } catch {
742
- throw new Error("node-vault is not installed. Install the optional peer dependency: npm install node-vault");
752
+ throw new Error("node-vault is not installed. Install it as a peer dependency: npm install node-vault");
743
753
  }
744
754
  }
745
755
  var VaultClient = class {
746
- client = null;
756
+ logger;
757
+ address;
758
+ authMethod;
759
+ role;
760
+ mountPoint;
761
+ tokenPath;
762
+ k8sAuthMount;
763
+ namespace;
747
764
  clientPromise;
748
765
  authenticated = false;
749
- cache = new Map();
750
- config;
751
- logger;
752
- cacheEnabled;
753
- cacheTtlMs;
766
+ /** In-flight auth so concurrent getSecret() calls don't all trigger kubernetesLogin. */
767
+ authPromise = null;
754
768
  constructor(options) {
755
- this.config = {
756
- address: "https://vault.beta.commerceiq.ai/",
757
- authMethod: "kubernetes",
758
- role: "",
759
- mountPoint: "secret",
760
- tokenPath: "/var/run/secrets/kubernetes.io/serviceaccount/token",
761
- ...options.config
762
- };
763
769
  this.logger = options.logger;
764
- this.cacheEnabled = this.config.cache?.enabled !== false;
765
- this.cacheTtlMs = (this.config.cache?.ttlSeconds ?? 300) * 1e3;
766
- this.clientPromise = this.initClient();
767
- }
768
- async initClient() {
769
- const nodeVault = await loadNodeVault();
770
- const client = nodeVault({
770
+ const cfg = options.config;
771
+ this.address = (cfg.address ?? DEFAULTS.address).replace(/\/+$/, "");
772
+ this.authMethod = cfg.authMethod ?? DEFAULTS.authMethod;
773
+ this.role = cfg.role;
774
+ this.mountPoint = stripSlashes(cfg.mountPoint ?? DEFAULTS.mountPoint);
775
+ this.tokenPath = cfg.tokenPath ?? DEFAULTS.tokenPath;
776
+ this.k8sAuthMount = cfg.k8sAuthMount ?? DEFAULTS.k8sAuthMount;
777
+ this.namespace = cfg.namespace;
778
+ const factory = options.nodeVaultFactory ? Promise.resolve(options.nodeVaultFactory) : loadNodeVault();
779
+ this.clientPromise = factory.then((create) => create({
771
780
  apiVersion: "v1",
772
- endpoint: this.config.address,
773
- namespace: this.config.namespace
774
- });
775
- this.client = client;
776
- return client;
781
+ endpoint: this.address,
782
+ namespace: this.namespace
783
+ }));
777
784
  }
778
785
  async authenticate() {
779
786
  if (this.authenticated) return;
787
+ if (this.authPromise) return this.authPromise;
788
+ this.authPromise = this.doAuthenticate().finally(() => {
789
+ if (!this.authenticated) this.authPromise = null;
790
+ });
791
+ return this.authPromise;
792
+ }
793
+ async doAuthenticate() {
780
794
  const client = await this.clientPromise;
781
- if (this.config.authMethod === "token") {
795
+ if (this.authMethod === "token") {
782
796
  const token = process.env.VAULT_TOKEN;
783
- if (!token) throw new Error("VAULT_TOKEN environment variable is required for token-based authentication");
797
+ if (!token) throw new Error("VAULT_TOKEN env var is required for token auth");
784
798
  client.token = token;
785
799
  this.authenticated = true;
786
800
  this.logger.info({ authMethod: "token" }, "Vault authenticated via token");
787
801
  return;
788
802
  }
789
- const role = this.config.role;
790
- if (!role) throw new Error("Vault role is required for Kubernetes authentication. Set vault.role in config or VAULT_AUTH_ROLE env var.");
803
+ if (!this.role) throw new Error("Vault role is required for Kubernetes auth (set vault.role or VAULT_AUTH_ROLE)");
791
804
  let jwt;
792
805
  try {
793
- jwt = await (0, node_fs_promises.readFile)(this.config.tokenPath, "utf8");
806
+ jwt = await (0, node_fs_promises.readFile)(this.tokenPath, "utf8");
794
807
  } catch (err) {
795
- const message = err instanceof Error ? err.message : String(err);
796
- throw new Error(`Failed to read Kubernetes service account token from ${this.config.tokenPath}: ${message}`);
808
+ const msg = err instanceof Error ? err.message : String(err);
809
+ throw new Error(`Failed to read service-account token at ${this.tokenPath}: ${msg}`);
797
810
  }
798
811
  const result = await client.kubernetesLogin({
799
- role,
800
- jwt
812
+ role: this.role,
813
+ jwt,
814
+ mount_point: this.k8sAuthMount
801
815
  });
802
816
  client.token = result.auth.client_token;
803
817
  this.authenticated = true;
804
818
  this.logger.info({
805
819
  authMethod: "kubernetes",
806
- role,
820
+ role: this.role,
807
821
  leaseDuration: result.auth.lease_duration
808
822
  }, "Vault authenticated via Kubernetes service account");
809
823
  }
810
824
  async getSecret(path) {
811
- const cacheKey = path;
812
- if (this.cacheEnabled) {
813
- const cached = this.cache.get(cacheKey);
814
- if (cached && cached.expiresAt > Date.now()) {
815
- this.logger.debug({
816
- path,
817
- cache: "hit"
818
- }, "Vault secret cache hit");
819
- return cached.data;
820
- }
821
- }
822
825
  await this.authenticate();
823
826
  const client = await this.clientPromise;
824
- const fullPath = `${this.config.mountPoint}/data/${path}`;
827
+ const fullPath = `${this.mountPoint}/data/${stripSlashes(path)}`;
825
828
  this.logger.debug({ path: fullPath }, "Reading secret from Vault");
826
829
  const response = await client.read(fullPath);
827
- const data = response.data.data;
828
- if (this.cacheEnabled) {
829
- this.cache.set(cacheKey, {
830
- data,
831
- expiresAt: Date.now() + this.cacheTtlMs
832
- });
833
- this.logger.debug({
834
- path,
835
- cache: "miss",
836
- ttlMs: this.cacheTtlMs
837
- }, "Vault secret cached");
838
- }
839
- return data;
830
+ return response.data.data;
840
831
  }
841
832
  async getSecretValue(path, key) {
842
833
  const secrets = await this.getSecret(path);
@@ -845,10 +836,6 @@ var VaultClient = class {
845
836
  isAuthenticated() {
846
837
  return this.authenticated;
847
838
  }
848
- clearCache() {
849
- this.cache.clear();
850
- this.logger.debug({}, "Vault secret cache cleared");
851
- }
852
839
  async checkHealth() {
853
840
  try {
854
841
  await this.authenticate();
@@ -879,12 +866,11 @@ function warnDeprecation(oldPath, newPath, logger) {
879
866
  function createFoundation(input) {
880
867
  const startTime = Date.now();
881
868
  const config = require_tracing.parseConfig(input);
882
- const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig, vault: vaultConfig } = config;
869
+ const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig } = config;
883
870
  const features = {
884
871
  tracing: featuresConfig.tracing ?? true,
885
872
  metrics: featuresConfig.metrics ?? true,
886
- logging: featuresConfig.logging ?? true,
887
- vault: vaultConfig.enabled ?? false
873
+ logging: featuresConfig.logging ?? true
888
874
  };
889
875
  const contextManager = createContextManager();
890
876
  let logger;
@@ -962,22 +948,6 @@ function createFoundation(input) {
962
948
  metricsError = err.message;
963
949
  logger.error({ error: metricsError }, "Metrics setup failed - continuing without metrics");
964
950
  }
965
- let vaultClient = null;
966
- let vaultError;
967
- if (features.vault) try {
968
- vaultClient = createVaultClient({
969
- config: vaultConfig,
970
- logger
971
- });
972
- logger.info({
973
- feature: "vault",
974
- address: vaultConfig.address,
975
- authMethod: vaultConfig.authMethod
976
- }, "Vault client created (lazy auth on first secret read)");
977
- } catch (err) {
978
- vaultError = err.message;
979
- logger.error({ error: vaultError }, "Vault setup failed - continuing without vault");
980
- }
981
951
  logger.info({
982
952
  serviceName,
983
953
  serviceVersion,
@@ -1045,45 +1015,50 @@ function createFoundation(input) {
1045
1015
  ...options,
1046
1016
  foundation
1047
1017
  }) };
1048
- const secretsModule = vaultClient;
1018
+ let secrets = null;
1019
+ if (config.vault?.enabled) try {
1020
+ secrets = createVaultClient({
1021
+ config: config.vault,
1022
+ logger
1023
+ });
1024
+ logger.info({
1025
+ address: config.vault.address,
1026
+ authMethod: config.vault.authMethod
1027
+ }, "Vault secrets module initialized");
1028
+ } catch (err) {
1029
+ logger.error({ error: err.message }, "Vault initialization failed - continuing without secrets");
1030
+ }
1049
1031
  const buildHealthStatus = () => {
1050
1032
  const tracingUp = !features.tracing || !tracingError && require_tracing.isTracingEnabled();
1051
1033
  const metricsUp = !features.metrics || !metricsError && isMetricsEnabled();
1052
1034
  const loggingUp = !loggingError;
1053
- const vaultUp = !features.vault || !vaultError && !!vaultClient;
1054
- const allUp = tracingUp && metricsUp && loggingUp && vaultUp;
1055
- const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && (!vaultUp || !features.vault) && !loggingUp;
1035
+ const allUp = tracingUp && metricsUp && loggingUp;
1036
+ const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && !loggingUp;
1056
1037
  let status = "healthy";
1057
1038
  if (!allUp) status = allDown ? "unhealthy" : "degraded";
1058
- const components = {
1059
- tracing: {
1060
- enabled: features.tracing,
1061
- status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
1062
- message: tracingError
1063
- },
1064
- metrics: {
1065
- enabled: features.metrics,
1066
- status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
1067
- message: metricsError
1068
- },
1069
- logging: {
1070
- enabled: features.logging,
1071
- status: loggingError ? "down" : "up",
1072
- message: loggingError
1073
- }
1074
- };
1075
- if (features.vault) components.vault = {
1076
- enabled: true,
1077
- status: vaultError ? "down" : "up",
1078
- message: vaultError
1079
- };
1080
1039
  return {
1081
1040
  status,
1082
1041
  timestamp: new Date().toISOString(),
1083
1042
  service: serviceName,
1084
1043
  version: serviceVersion,
1085
1044
  uptime: Math.floor((Date.now() - startTime) / 1e3),
1086
- components
1045
+ components: {
1046
+ tracing: {
1047
+ enabled: features.tracing,
1048
+ status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
1049
+ message: tracingError
1050
+ },
1051
+ metrics: {
1052
+ enabled: features.metrics,
1053
+ status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
1054
+ message: metricsError
1055
+ },
1056
+ logging: {
1057
+ enabled: features.logging,
1058
+ status: loggingError ? "down" : "up",
1059
+ message: loggingError
1060
+ }
1061
+ }
1087
1062
  };
1088
1063
  };
1089
1064
  const shutdownFn = async () => {
@@ -1091,7 +1066,6 @@ function createFoundation(input) {
1091
1066
  const promises = [];
1092
1067
  if (features.tracing && require_tracing.isTracingEnabled()) promises.push(require_tracing.shutdownTracing());
1093
1068
  if (features.metrics && isMetricsEnabled()) promises.push(shutdownMetrics());
1094
- if (vaultClient) vaultClient.clearCache();
1095
1069
  await Promise.all(promises);
1096
1070
  logger.info({}, "Foundation shutdown complete");
1097
1071
  };
@@ -1124,7 +1098,7 @@ function createFoundation(input) {
1124
1098
  observability,
1125
1099
  http: httpModule,
1126
1100
  lifecycle,
1127
- secrets: secretsModule,
1101
+ secrets,
1128
1102
  logger,
1129
1103
  context: contextManager,
1130
1104
  fastifyPlugin: createObservabilityPlugin({
@@ -1482,7 +1456,6 @@ exports.RedactionConfigSchema = require_tracing.RedactionConfigSchema
1482
1456
  exports.RequestLoggingConfigSchema = require_tracing.RequestLoggingConfigSchema
1483
1457
  exports.ShutdownConfigSchema = require_tracing.ShutdownConfigSchema
1484
1458
  exports.SpanStatusCode = __opentelemetry_api.SpanStatusCode
1485
- exports.VaultCacheConfigSchema = require_tracing.VaultCacheConfigSchema
1486
1459
  exports.VaultClient = VaultClient
1487
1460
  exports.VaultConfigSchema = require_tracing.VaultConfigSchema
1488
1461
  exports.buildPinoRedactConfig = buildPinoRedactConfig