@ciq-dev/neoiq-foundation-node 1.0.1 → 1.0.2

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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { AutoInstrumentationConfigSchema, FeaturesConfigSchema, FoundationConfigSchema, LoggingConfigSchema, OtelConfigSchema, RedactionConfigSchema, RequestLoggingConfigSchema, ShutdownConfigSchema, SpanStatusCode, context, getActiveSpan, getDefaultOtelEndpoint, getTraceContext, getTracer, isTracingEnabled, parseConfig, propagation, setupTracing, shutdownTracing, trace } from "./tracing-CcsyQIpB.mjs";
1
+ import { AutoInstrumentationConfigSchema, FeaturesConfigSchema, FoundationConfigSchema, LoggingConfigSchema, OtelConfigSchema, RedactionConfigSchema, RequestLoggingConfigSchema, ShutdownConfigSchema, SpanStatusCode, VaultCacheConfigSchema, VaultConfigSchema, context, getActiveSpan, getDefaultOtelEndpoint, getTraceContext, getTracer, isTracingEnabled, parseConfig, propagation, setupTracing, shutdownTracing, trace } from "./tracing-BwTyDuEv.mjs";
2
2
  import { resourceFromAttributes } from "@opentelemetry/resources";
3
3
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
4
4
  import { SpanStatusCode as SpanStatusCode$1, context as context$1, metrics, propagation as propagation$1, trace as trace$1 } from "@opentelemetry/api";
@@ -11,6 +11,7 @@ import { randomUUID } from "crypto";
11
11
  import axios from "axios";
12
12
  import axiosRetry from "axios-retry";
13
13
  import CircuitBreaker from "opossum";
14
+ import { readFile } from "node:fs/promises";
14
15
  import { createHash } from "node:crypto";
15
16
  import { Readable } from "node:stream";
16
17
 
@@ -730,6 +731,136 @@ function createHttpClient(options) {
730
731
  return client;
731
732
  }
732
733
 
734
+ //#endregion
735
+ //#region src/features/vault.ts
736
+ async function loadNodeVault() {
737
+ try {
738
+ const mod = await import("node-vault");
739
+ return mod.default ?? mod;
740
+ } catch {
741
+ throw new Error("node-vault is not installed. Install the optional peer dependency: npm install node-vault");
742
+ }
743
+ }
744
+ var VaultClient = class {
745
+ client = null;
746
+ clientPromise;
747
+ authenticated = false;
748
+ cache = new Map();
749
+ config;
750
+ logger;
751
+ cacheEnabled;
752
+ cacheTtlMs;
753
+ constructor(options) {
754
+ this.config = {
755
+ address: "https://vault.beta.commerceiq.ai/",
756
+ authMethod: "kubernetes",
757
+ role: "",
758
+ mountPoint: "secret",
759
+ tokenPath: "/var/run/secrets/kubernetes.io/serviceaccount/token",
760
+ ...options.config
761
+ };
762
+ this.logger = options.logger;
763
+ this.cacheEnabled = this.config.cache?.enabled !== false;
764
+ this.cacheTtlMs = (this.config.cache?.ttlSeconds ?? 300) * 1e3;
765
+ this.clientPromise = this.initClient();
766
+ }
767
+ async initClient() {
768
+ const nodeVault = await loadNodeVault();
769
+ const client = nodeVault({
770
+ apiVersion: "v1",
771
+ endpoint: this.config.address,
772
+ namespace: this.config.namespace
773
+ });
774
+ this.client = client;
775
+ return client;
776
+ }
777
+ async authenticate() {
778
+ if (this.authenticated) return;
779
+ const client = await this.clientPromise;
780
+ if (this.config.authMethod === "token") {
781
+ const token = process.env.VAULT_TOKEN;
782
+ if (!token) throw new Error("VAULT_TOKEN environment variable is required for token-based authentication");
783
+ client.token = token;
784
+ this.authenticated = true;
785
+ this.logger.info({ authMethod: "token" }, "Vault authenticated via token");
786
+ return;
787
+ }
788
+ const role = this.config.role;
789
+ if (!role) throw new Error("Vault role is required for Kubernetes authentication. Set vault.role in config or VAULT_AUTH_ROLE env var.");
790
+ let jwt;
791
+ try {
792
+ jwt = await readFile(this.config.tokenPath, "utf8");
793
+ } catch (err) {
794
+ const message = err instanceof Error ? err.message : String(err);
795
+ throw new Error(`Failed to read Kubernetes service account token from ${this.config.tokenPath}: ${message}`);
796
+ }
797
+ const result = await client.kubernetesLogin({
798
+ role,
799
+ jwt
800
+ });
801
+ client.token = result.auth.client_token;
802
+ this.authenticated = true;
803
+ this.logger.info({
804
+ authMethod: "kubernetes",
805
+ role,
806
+ leaseDuration: result.auth.lease_duration
807
+ }, "Vault authenticated via Kubernetes service account");
808
+ }
809
+ async getSecret(path) {
810
+ const cacheKey = path;
811
+ if (this.cacheEnabled) {
812
+ const cached = this.cache.get(cacheKey);
813
+ if (cached && cached.expiresAt > Date.now()) {
814
+ this.logger.debug({
815
+ path,
816
+ cache: "hit"
817
+ }, "Vault secret cache hit");
818
+ return cached.data;
819
+ }
820
+ }
821
+ await this.authenticate();
822
+ const client = await this.clientPromise;
823
+ const fullPath = `${this.config.mountPoint}/data/${path}`;
824
+ this.logger.debug({ path: fullPath }, "Reading secret from Vault");
825
+ const response = await client.read(fullPath);
826
+ const data = response.data.data;
827
+ if (this.cacheEnabled) {
828
+ this.cache.set(cacheKey, {
829
+ data,
830
+ expiresAt: Date.now() + this.cacheTtlMs
831
+ });
832
+ this.logger.debug({
833
+ path,
834
+ cache: "miss",
835
+ ttlMs: this.cacheTtlMs
836
+ }, "Vault secret cached");
837
+ }
838
+ return data;
839
+ }
840
+ async getSecretValue(path, key) {
841
+ const secrets = await this.getSecret(path);
842
+ return secrets[key];
843
+ }
844
+ isAuthenticated() {
845
+ return this.authenticated;
846
+ }
847
+ clearCache() {
848
+ this.cache.clear();
849
+ this.logger.debug({}, "Vault secret cache cleared");
850
+ }
851
+ async checkHealth() {
852
+ try {
853
+ await this.authenticate();
854
+ return true;
855
+ } catch {
856
+ return false;
857
+ }
858
+ }
859
+ };
860
+ function createVaultClient(options) {
861
+ return new VaultClient(options);
862
+ }
863
+
733
864
  //#endregion
734
865
  //#region src/foundation.ts
735
866
  const deprecationWarnings = new Set();
@@ -747,11 +878,12 @@ function warnDeprecation(oldPath, newPath, logger) {
747
878
  function createFoundation(input) {
748
879
  const startTime = Date.now();
749
880
  const config = parseConfig(input);
750
- const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig } = config;
881
+ const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig, vault: vaultConfig } = config;
751
882
  const features = {
752
883
  tracing: featuresConfig.tracing ?? true,
753
884
  metrics: featuresConfig.metrics ?? true,
754
- logging: featuresConfig.logging ?? true
885
+ logging: featuresConfig.logging ?? true,
886
+ vault: vaultConfig.enabled ?? false
755
887
  };
756
888
  const contextManager = createContextManager();
757
889
  let logger;
@@ -829,6 +961,22 @@ function createFoundation(input) {
829
961
  metricsError = err.message;
830
962
  logger.error({ error: metricsError }, "Metrics setup failed - continuing without metrics");
831
963
  }
964
+ let vaultClient = null;
965
+ let vaultError;
966
+ if (features.vault) try {
967
+ vaultClient = createVaultClient({
968
+ config: vaultConfig,
969
+ logger
970
+ });
971
+ logger.info({
972
+ feature: "vault",
973
+ address: vaultConfig.address,
974
+ authMethod: vaultConfig.authMethod
975
+ }, "Vault client created (lazy auth on first secret read)");
976
+ } catch (err) {
977
+ vaultError = err.message;
978
+ logger.error({ error: vaultError }, "Vault setup failed - continuing without vault");
979
+ }
832
980
  logger.info({
833
981
  serviceName,
834
982
  serviceVersion,
@@ -896,37 +1044,45 @@ function createFoundation(input) {
896
1044
  ...options,
897
1045
  foundation
898
1046
  }) };
1047
+ const secretsModule = vaultClient;
899
1048
  const buildHealthStatus = () => {
900
1049
  const tracingUp = !features.tracing || !tracingError && isTracingEnabled();
901
1050
  const metricsUp = !features.metrics || !metricsError && isMetricsEnabled();
902
1051
  const loggingUp = !loggingError;
903
- const allUp = tracingUp && metricsUp && loggingUp;
904
- const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && !loggingUp;
1052
+ const vaultUp = !features.vault || !vaultError && !!vaultClient;
1053
+ const allUp = tracingUp && metricsUp && loggingUp && vaultUp;
1054
+ const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && (!vaultUp || !features.vault) && !loggingUp;
905
1055
  let status = "healthy";
906
1056
  if (!allUp) status = allDown ? "unhealthy" : "degraded";
1057
+ const components = {
1058
+ tracing: {
1059
+ enabled: features.tracing,
1060
+ status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
1061
+ message: tracingError
1062
+ },
1063
+ metrics: {
1064
+ enabled: features.metrics,
1065
+ status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
1066
+ message: metricsError
1067
+ },
1068
+ logging: {
1069
+ enabled: features.logging,
1070
+ status: loggingError ? "down" : "up",
1071
+ message: loggingError
1072
+ }
1073
+ };
1074
+ if (features.vault) components.vault = {
1075
+ enabled: true,
1076
+ status: vaultError ? "down" : "up",
1077
+ message: vaultError
1078
+ };
907
1079
  return {
908
1080
  status,
909
1081
  timestamp: new Date().toISOString(),
910
1082
  service: serviceName,
911
1083
  version: serviceVersion,
912
1084
  uptime: Math.floor((Date.now() - startTime) / 1e3),
913
- components: {
914
- tracing: {
915
- enabled: features.tracing,
916
- status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
917
- message: tracingError
918
- },
919
- metrics: {
920
- enabled: features.metrics,
921
- status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
922
- message: metricsError
923
- },
924
- logging: {
925
- enabled: features.logging,
926
- status: loggingError ? "down" : "up",
927
- message: loggingError
928
- }
929
- }
1085
+ components
930
1086
  };
931
1087
  };
932
1088
  const shutdownFn = async () => {
@@ -934,6 +1090,7 @@ function createFoundation(input) {
934
1090
  const promises = [];
935
1091
  if (features.tracing && isTracingEnabled()) promises.push(shutdownTracing());
936
1092
  if (features.metrics && isMetricsEnabled()) promises.push(shutdownMetrics());
1093
+ if (vaultClient) vaultClient.clearCache();
937
1094
  await Promise.all(promises);
938
1095
  logger.info({}, "Foundation shutdown complete");
939
1096
  };
@@ -966,6 +1123,7 @@ function createFoundation(input) {
966
1123
  observability,
967
1124
  http: httpModule,
968
1125
  lifecycle,
1126
+ secrets: secretsModule,
969
1127
  logger,
970
1128
  context: contextManager,
971
1129
  fastifyPlugin: createObservabilityPlugin({
@@ -1311,5 +1469,5 @@ var InMemoryObjectStore = class {
1311
1469
  };
1312
1470
 
1313
1471
  //#endregion
1314
- export { AutoInstrumentationConfigSchema, AwsS3ObjectStore, FeaturesConfigSchema, FoundationConfigSchema, InMemoryObjectStore, LoggingConfigSchema, OtelConfigSchema, REDACT_PATHS, RedactionConfigSchema, RequestLoggingConfigSchema, ShutdownConfigSchema, SpanStatusCode, buildPinoRedactConfig, context, createContextManager, createFallbackLogger, createFoundation, createHttpClient, createLogger, createObservabilityPlugin, getActiveSpan, getDefaultOtelEndpoint, getGlobalLogger, getMeter, getTraceContext, getTracer, isMetricsEnabled, isTracingEnabled, metrics, parseConfig, propagation, sanitizeBody, setGlobalLogger, setupMetrics, setupObservability, setupTracing, shutdownMetrics, shutdownTracing, trace };
1472
+ export { AutoInstrumentationConfigSchema, AwsS3ObjectStore, FeaturesConfigSchema, FoundationConfigSchema, InMemoryObjectStore, LoggingConfigSchema, OtelConfigSchema, REDACT_PATHS, RedactionConfigSchema, RequestLoggingConfigSchema, ShutdownConfigSchema, SpanStatusCode, VaultCacheConfigSchema, VaultClient, VaultConfigSchema, buildPinoRedactConfig, context, createContextManager, createFallbackLogger, createFoundation, createHttpClient, createLogger, createObservabilityPlugin, createVaultClient, getActiveSpan, getDefaultOtelEndpoint, getGlobalLogger, getMeter, getTraceContext, getTracer, isMetricsEnabled, isTracingEnabled, metrics, parseConfig, propagation, sanitizeBody, setGlobalLogger, setupMetrics, setupObservability, setupTracing, shutdownMetrics, shutdownTracing, trace };
1315
1473
  //# sourceMappingURL=index.mjs.map