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

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-C0FkkeKE.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,111 +734,109 @@ 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
+ let start = 0;
746
+ let end = value.length;
747
+ while (start < end && value[start] === "/") start += 1;
748
+ while (end > start && value[end - 1] === "/") end -= 1;
749
+ return value.slice(start, end);
750
+ }
751
+ function stripTrailingSlashes(value) {
752
+ let end = value.length;
753
+ while (end > 0 && value[end - 1] === "/") end -= 1;
754
+ return value.slice(0, end);
755
+ }
737
756
  async function loadNodeVault() {
738
757
  try {
739
758
  const mod = await import("node-vault");
740
759
  return mod.default ?? mod;
741
760
  } catch {
742
- throw new Error("node-vault is not installed. Install the optional peer dependency: npm install node-vault");
761
+ throw new Error("node-vault is not installed. Install it as a peer dependency: npm install node-vault");
743
762
  }
744
763
  }
745
764
  var VaultClient = class {
746
- client = null;
765
+ logger;
766
+ address;
767
+ authMethod;
768
+ role;
769
+ mountPoint;
770
+ tokenPath;
771
+ k8sAuthMount;
772
+ namespace;
747
773
  clientPromise;
748
774
  authenticated = false;
749
- cache = new Map();
750
- config;
751
- logger;
752
- cacheEnabled;
753
- cacheTtlMs;
775
+ /** In-flight auth so concurrent getSecret() calls don't all trigger kubernetesLogin. */
776
+ authPromise = null;
754
777
  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
- k8sAuthMount: "kubernetes",
762
- ...options.config
763
- };
764
778
  this.logger = options.logger;
765
- this.cacheEnabled = this.config.cache?.enabled !== false;
766
- this.cacheTtlMs = (this.config.cache?.ttlSeconds ?? 300) * 1e3;
767
- this.clientPromise = this.initClient();
768
- }
769
- async initClient() {
770
- const nodeVault = await loadNodeVault();
771
- const client = nodeVault({
779
+ const cfg = options.config;
780
+ this.address = stripTrailingSlashes(cfg.address ?? DEFAULTS.address);
781
+ this.authMethod = cfg.authMethod ?? DEFAULTS.authMethod;
782
+ this.role = cfg.role;
783
+ this.mountPoint = stripSlashes(cfg.mountPoint ?? DEFAULTS.mountPoint);
784
+ this.tokenPath = cfg.tokenPath ?? DEFAULTS.tokenPath;
785
+ this.k8sAuthMount = cfg.k8sAuthMount ?? DEFAULTS.k8sAuthMount;
786
+ this.namespace = cfg.namespace;
787
+ const factory = options.nodeVaultFactory ? Promise.resolve(options.nodeVaultFactory) : loadNodeVault();
788
+ this.clientPromise = factory.then((create) => create({
772
789
  apiVersion: "v1",
773
- endpoint: this.config.address,
774
- namespace: this.config.namespace
775
- });
776
- this.client = client;
777
- return client;
790
+ endpoint: this.address,
791
+ namespace: this.namespace
792
+ }));
778
793
  }
779
794
  async authenticate() {
780
795
  if (this.authenticated) return;
796
+ if (this.authPromise) return this.authPromise;
797
+ this.authPromise = this.doAuthenticate().finally(() => {
798
+ if (!this.authenticated) this.authPromise = null;
799
+ });
800
+ return this.authPromise;
801
+ }
802
+ async doAuthenticate() {
781
803
  const client = await this.clientPromise;
782
- if (this.config.authMethod === "token") {
804
+ if (this.authMethod === "token") {
783
805
  const token = process.env.VAULT_TOKEN;
784
- if (!token) throw new Error("VAULT_TOKEN environment variable is required for token-based authentication");
806
+ if (!token) throw new Error("VAULT_TOKEN env var is required for token auth");
785
807
  client.token = token;
786
808
  this.authenticated = true;
787
809
  this.logger.info({ authMethod: "token" }, "Vault authenticated via token");
788
810
  return;
789
811
  }
790
- const role = this.config.role;
791
- if (!role) throw new Error("Vault role is required for Kubernetes authentication. Set vault.role in config or VAULT_AUTH_ROLE env var.");
812
+ if (!this.role) throw new Error("Vault role is required for Kubernetes auth (set vault.role or VAULT_AUTH_ROLE)");
792
813
  let jwt;
793
814
  try {
794
- jwt = await (0, node_fs_promises.readFile)(this.config.tokenPath, "utf8");
815
+ jwt = await (0, node_fs_promises.readFile)(this.tokenPath, "utf8");
795
816
  } catch (err) {
796
- const message = err instanceof Error ? err.message : String(err);
797
- throw new Error(`Failed to read Kubernetes service account token from ${this.config.tokenPath}: ${message}`);
817
+ const msg = err instanceof Error ? err.message : String(err);
818
+ throw new Error(`Failed to read service-account token at ${this.tokenPath}: ${msg}`);
798
819
  }
799
820
  const result = await client.kubernetesLogin({
800
- role,
821
+ role: this.role,
801
822
  jwt,
802
- mount_point: this.config.k8sAuthMount
823
+ mount_point: this.k8sAuthMount
803
824
  });
804
825
  client.token = result.auth.client_token;
805
826
  this.authenticated = true;
806
827
  this.logger.info({
807
828
  authMethod: "kubernetes",
808
- role,
829
+ role: this.role,
809
830
  leaseDuration: result.auth.lease_duration
810
831
  }, "Vault authenticated via Kubernetes service account");
811
832
  }
812
833
  async getSecret(path) {
813
- const cacheKey = path;
814
- if (this.cacheEnabled) {
815
- const cached = this.cache.get(cacheKey);
816
- if (cached && cached.expiresAt > Date.now()) {
817
- this.logger.debug({
818
- path,
819
- cache: "hit"
820
- }, "Vault secret cache hit");
821
- return cached.data;
822
- }
823
- }
824
834
  await this.authenticate();
825
835
  const client = await this.clientPromise;
826
- const fullPath = `${this.config.mountPoint}/data/${path}`;
836
+ const fullPath = `${this.mountPoint}/data/${stripSlashes(path)}`;
827
837
  this.logger.debug({ path: fullPath }, "Reading secret from Vault");
828
838
  const response = await client.read(fullPath);
829
- const data = response.data.data;
830
- if (this.cacheEnabled) {
831
- this.cache.set(cacheKey, {
832
- data,
833
- expiresAt: Date.now() + this.cacheTtlMs
834
- });
835
- this.logger.debug({
836
- path,
837
- cache: "miss",
838
- ttlMs: this.cacheTtlMs
839
- }, "Vault secret cached");
840
- }
841
- return data;
839
+ return response.data.data;
842
840
  }
843
841
  async getSecretValue(path, key) {
844
842
  const secrets = await this.getSecret(path);
@@ -847,10 +845,6 @@ var VaultClient = class {
847
845
  isAuthenticated() {
848
846
  return this.authenticated;
849
847
  }
850
- clearCache() {
851
- this.cache.clear();
852
- this.logger.debug({}, "Vault secret cache cleared");
853
- }
854
848
  async checkHealth() {
855
849
  try {
856
850
  await this.authenticate();
@@ -881,12 +875,11 @@ function warnDeprecation(oldPath, newPath, logger) {
881
875
  function createFoundation(input) {
882
876
  const startTime = Date.now();
883
877
  const config = require_tracing.parseConfig(input);
884
- const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig, vault: vaultConfig } = config;
878
+ const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig } = config;
885
879
  const features = {
886
880
  tracing: featuresConfig.tracing ?? true,
887
881
  metrics: featuresConfig.metrics ?? true,
888
- logging: featuresConfig.logging ?? true,
889
- vault: vaultConfig.enabled ?? false
882
+ logging: featuresConfig.logging ?? true
890
883
  };
891
884
  const contextManager = createContextManager();
892
885
  let logger;
@@ -964,22 +957,6 @@ function createFoundation(input) {
964
957
  metricsError = err.message;
965
958
  logger.error({ error: metricsError }, "Metrics setup failed - continuing without metrics");
966
959
  }
967
- let vaultClient = null;
968
- let vaultError;
969
- if (features.vault) try {
970
- vaultClient = createVaultClient({
971
- config: vaultConfig,
972
- logger
973
- });
974
- logger.info({
975
- feature: "vault",
976
- address: vaultConfig.address,
977
- authMethod: vaultConfig.authMethod
978
- }, "Vault client created (lazy auth on first secret read)");
979
- } catch (err) {
980
- vaultError = err.message;
981
- logger.error({ error: vaultError }, "Vault setup failed - continuing without vault");
982
- }
983
960
  logger.info({
984
961
  serviceName,
985
962
  serviceVersion,
@@ -1047,45 +1024,50 @@ function createFoundation(input) {
1047
1024
  ...options,
1048
1025
  foundation
1049
1026
  }) };
1050
- const secretsModule = vaultClient;
1027
+ let secrets = null;
1028
+ if (config.vault?.enabled) try {
1029
+ secrets = createVaultClient({
1030
+ config: config.vault,
1031
+ logger
1032
+ });
1033
+ logger.info({
1034
+ address: config.vault.address,
1035
+ authMethod: config.vault.authMethod
1036
+ }, "Vault secrets module initialized");
1037
+ } catch (err) {
1038
+ logger.error({ error: err.message }, "Vault initialization failed - continuing without secrets");
1039
+ }
1051
1040
  const buildHealthStatus = () => {
1052
1041
  const tracingUp = !features.tracing || !tracingError && require_tracing.isTracingEnabled();
1053
1042
  const metricsUp = !features.metrics || !metricsError && isMetricsEnabled();
1054
1043
  const loggingUp = !loggingError;
1055
- const vaultUp = !features.vault || !vaultError && !!vaultClient;
1056
- const allUp = tracingUp && metricsUp && loggingUp && vaultUp;
1057
- const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && (!vaultUp || !features.vault) && !loggingUp;
1044
+ const allUp = tracingUp && metricsUp && loggingUp;
1045
+ const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && !loggingUp;
1058
1046
  let status = "healthy";
1059
1047
  if (!allUp) status = allDown ? "unhealthy" : "degraded";
1060
- const components = {
1061
- tracing: {
1062
- enabled: features.tracing,
1063
- status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
1064
- message: tracingError
1065
- },
1066
- metrics: {
1067
- enabled: features.metrics,
1068
- status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
1069
- message: metricsError
1070
- },
1071
- logging: {
1072
- enabled: features.logging,
1073
- status: loggingError ? "down" : "up",
1074
- message: loggingError
1075
- }
1076
- };
1077
- if (features.vault) components.vault = {
1078
- enabled: true,
1079
- status: vaultError ? "down" : "up",
1080
- message: vaultError
1081
- };
1082
1048
  return {
1083
1049
  status,
1084
1050
  timestamp: new Date().toISOString(),
1085
1051
  service: serviceName,
1086
1052
  version: serviceVersion,
1087
1053
  uptime: Math.floor((Date.now() - startTime) / 1e3),
1088
- components
1054
+ components: {
1055
+ tracing: {
1056
+ enabled: features.tracing,
1057
+ status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
1058
+ message: tracingError
1059
+ },
1060
+ metrics: {
1061
+ enabled: features.metrics,
1062
+ status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
1063
+ message: metricsError
1064
+ },
1065
+ logging: {
1066
+ enabled: features.logging,
1067
+ status: loggingError ? "down" : "up",
1068
+ message: loggingError
1069
+ }
1070
+ }
1089
1071
  };
1090
1072
  };
1091
1073
  const shutdownFn = async () => {
@@ -1093,7 +1075,6 @@ function createFoundation(input) {
1093
1075
  const promises = [];
1094
1076
  if (features.tracing && require_tracing.isTracingEnabled()) promises.push(require_tracing.shutdownTracing());
1095
1077
  if (features.metrics && isMetricsEnabled()) promises.push(shutdownMetrics());
1096
- if (vaultClient) vaultClient.clearCache();
1097
1078
  await Promise.all(promises);
1098
1079
  logger.info({}, "Foundation shutdown complete");
1099
1080
  };
@@ -1126,7 +1107,7 @@ function createFoundation(input) {
1126
1107
  observability,
1127
1108
  http: httpModule,
1128
1109
  lifecycle,
1129
- secrets: secretsModule,
1110
+ secrets,
1130
1111
  logger,
1131
1112
  context: contextManager,
1132
1113
  fastifyPlugin: createObservabilityPlugin({
@@ -1484,7 +1465,6 @@ exports.RedactionConfigSchema = require_tracing.RedactionConfigSchema
1484
1465
  exports.RequestLoggingConfigSchema = require_tracing.RequestLoggingConfigSchema
1485
1466
  exports.ShutdownConfigSchema = require_tracing.ShutdownConfigSchema
1486
1467
  exports.SpanStatusCode = __opentelemetry_api.SpanStatusCode
1487
- exports.VaultCacheConfigSchema = require_tracing.VaultCacheConfigSchema
1488
1468
  exports.VaultClient = VaultClient
1489
1469
  exports.VaultConfigSchema = require_tracing.VaultConfigSchema
1490
1470
  exports.buildPinoRedactConfig = buildPinoRedactConfig