@blokjs/runner 0.2.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/Blok.d.ts +19 -0
- package/dist/Blok.js +184 -0
- package/dist/Blok.js.map +1 -0
- package/dist/BlokResponse.d.ts +16 -0
- package/dist/BlokResponse.js +28 -0
- package/dist/BlokResponse.js.map +1 -0
- package/dist/Configuration.d.ts +37 -0
- package/dist/Configuration.js +248 -0
- package/dist/Configuration.js.map +1 -0
- package/dist/ConfigurationResolver.d.ts +7 -0
- package/dist/ConfigurationResolver.js +15 -0
- package/dist/ConfigurationResolver.js.map +1 -0
- package/dist/DefaultLogger.d.ts +65 -0
- package/dist/DefaultLogger.js +101 -0
- package/dist/DefaultLogger.js.map +1 -0
- package/dist/LocalStorage.d.ts +7 -0
- package/dist/LocalStorage.js +56 -0
- package/dist/LocalStorage.js.map +1 -0
- package/dist/MemoryUsage.d.ts +22 -0
- package/dist/MemoryUsage.js +83 -0
- package/dist/MemoryUsage.js.map +1 -0
- package/dist/NodeMap.d.ts +7 -0
- package/dist/NodeMap.js +13 -0
- package/dist/NodeMap.js.map +1 -0
- package/dist/ResolverBase.d.ts +8 -0
- package/dist/ResolverBase.js +18 -0
- package/dist/ResolverBase.js.map +1 -0
- package/dist/Runner.d.ts +25 -0
- package/dist/Runner.js +32 -0
- package/dist/Runner.js.map +1 -0
- package/dist/RunnerNode.d.ts +9 -0
- package/dist/RunnerNode.js +8 -0
- package/dist/RunnerNode.js.map +1 -0
- package/dist/RunnerNodeBase.d.ts +4 -0
- package/dist/RunnerNodeBase.js +3 -0
- package/dist/RunnerNodeBase.js.map +1 -0
- package/dist/RunnerSteps.d.ts +14 -0
- package/dist/RunnerSteps.js +110 -0
- package/dist/RunnerSteps.js.map +1 -0
- package/dist/RuntimeAdapterNode.d.ts +19 -0
- package/dist/RuntimeAdapterNode.js +87 -0
- package/dist/RuntimeAdapterNode.js.map +1 -0
- package/dist/RuntimeRegistry.d.ts +61 -0
- package/dist/RuntimeRegistry.js +87 -0
- package/dist/RuntimeRegistry.js.map +1 -0
- package/dist/TriggerBase.d.ts +119 -0
- package/dist/TriggerBase.js +413 -0
- package/dist/TriggerBase.js.map +1 -0
- package/dist/adapters/BunRuntimeAdapter.d.ts +38 -0
- package/dist/adapters/BunRuntimeAdapter.js +169 -0
- package/dist/adapters/BunRuntimeAdapter.js.map +1 -0
- package/dist/adapters/DockerRuntimeAdapter.d.ts +85 -0
- package/dist/adapters/DockerRuntimeAdapter.js +298 -0
- package/dist/adapters/DockerRuntimeAdapter.js.map +1 -0
- package/dist/adapters/HttpRuntimeAdapter.d.ts +58 -0
- package/dist/adapters/HttpRuntimeAdapter.js +152 -0
- package/dist/adapters/HttpRuntimeAdapter.js.map +1 -0
- package/dist/adapters/NodeJsRuntimeAdapter.d.ts +23 -0
- package/dist/adapters/NodeJsRuntimeAdapter.js +67 -0
- package/dist/adapters/NodeJsRuntimeAdapter.js.map +1 -0
- package/dist/adapters/RuntimeAdapter.d.ts +42 -0
- package/dist/adapters/RuntimeAdapter.js +2 -0
- package/dist/adapters/RuntimeAdapter.js.map +1 -0
- package/dist/adapters/WasmRuntimeAdapter.d.ts +69 -0
- package/dist/adapters/WasmRuntimeAdapter.js +279 -0
- package/dist/adapters/WasmRuntimeAdapter.js.map +1 -0
- package/dist/cache/NodeResultCache.d.ts +286 -0
- package/dist/cache/NodeResultCache.js +499 -0
- package/dist/cache/NodeResultCache.js.map +1 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +2 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cost/CostEstimator.d.ts +57 -0
- package/dist/cost/CostEstimator.js +171 -0
- package/dist/cost/CostEstimator.js.map +1 -0
- package/dist/cost/index.d.ts +4 -0
- package/dist/cost/index.js +3 -0
- package/dist/cost/index.js.map +1 -0
- package/dist/cost/pricing.d.ts +24 -0
- package/dist/cost/pricing.js +169 -0
- package/dist/cost/pricing.js.map +1 -0
- package/dist/defineNode.d.ts +155 -0
- package/dist/defineNode.js +191 -0
- package/dist/defineNode.js.map +1 -0
- package/dist/graphql/GraphQLSchemaGenerator.d.ts +129 -0
- package/dist/graphql/GraphQLSchemaGenerator.js +425 -0
- package/dist/graphql/GraphQLSchemaGenerator.js.map +1 -0
- package/dist/hmr/FileWatcher.d.ts +62 -0
- package/dist/hmr/FileWatcher.js +185 -0
- package/dist/hmr/FileWatcher.js.map +1 -0
- package/dist/hmr/HmrDevConsole.d.ts +13 -0
- package/dist/hmr/HmrDevConsole.js +46 -0
- package/dist/hmr/HmrDevConsole.js.map +1 -0
- package/dist/hmr/HotReloadManager.d.ts +84 -0
- package/dist/hmr/HotReloadManager.js +195 -0
- package/dist/hmr/HotReloadManager.js.map +1 -0
- package/dist/hmr/index.d.ts +39 -0
- package/dist/hmr/index.js +38 -0
- package/dist/hmr/index.js.map +1 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/APMIntegration.d.ts +141 -0
- package/dist/integrations/APMIntegration.js +212 -0
- package/dist/integrations/APMIntegration.js.map +1 -0
- package/dist/integrations/AzureMonitorIntegration.d.ts +118 -0
- package/dist/integrations/AzureMonitorIntegration.js +254 -0
- package/dist/integrations/AzureMonitorIntegration.js.map +1 -0
- package/dist/integrations/CloudWatchIntegration.d.ts +135 -0
- package/dist/integrations/CloudWatchIntegration.js +293 -0
- package/dist/integrations/CloudWatchIntegration.js.map +1 -0
- package/dist/integrations/SentryIntegration.d.ts +153 -0
- package/dist/integrations/SentryIntegration.js +200 -0
- package/dist/integrations/SentryIntegration.js.map +1 -0
- package/dist/integrations/index.d.ts +19 -0
- package/dist/integrations/index.js +16 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/marketplace/RuntimeAutoScaler.d.ts +148 -0
- package/dist/marketplace/RuntimeAutoScaler.js +366 -0
- package/dist/marketplace/RuntimeAutoScaler.js.map +1 -0
- package/dist/marketplace/RuntimeCatalog.d.ts +174 -0
- package/dist/marketplace/RuntimeCatalog.js +339 -0
- package/dist/marketplace/RuntimeCatalog.js.map +1 -0
- package/dist/marketplace/RuntimeDiscovery.d.ts +86 -0
- package/dist/marketplace/RuntimeDiscovery.js +219 -0
- package/dist/marketplace/RuntimeDiscovery.js.map +1 -0
- package/dist/marketplace/RuntimeHealthMonitor.d.ts +100 -0
- package/dist/marketplace/RuntimeHealthMonitor.js +241 -0
- package/dist/marketplace/RuntimeHealthMonitor.js.map +1 -0
- package/dist/marketplace/RuntimeMetricsDashboard.d.ts +113 -0
- package/dist/marketplace/RuntimeMetricsDashboard.js +293 -0
- package/dist/marketplace/RuntimeMetricsDashboard.js.map +1 -0
- package/dist/monitoring/CircuitBreaker.d.ts +107 -0
- package/dist/monitoring/CircuitBreaker.js +238 -0
- package/dist/monitoring/CircuitBreaker.js.map +1 -0
- package/dist/monitoring/DistributedTracer.d.ts +125 -0
- package/dist/monitoring/DistributedTracer.js +230 -0
- package/dist/monitoring/DistributedTracer.js.map +1 -0
- package/dist/monitoring/HealthCheck.d.ts +54 -0
- package/dist/monitoring/HealthCheck.js +102 -0
- package/dist/monitoring/HealthCheck.js.map +1 -0
- package/dist/monitoring/PerformanceProfiler.d.ts +63 -0
- package/dist/monitoring/PerformanceProfiler.js +229 -0
- package/dist/monitoring/PerformanceProfiler.js.map +1 -0
- package/dist/monitoring/PrometheusBootstrap.d.ts +30 -0
- package/dist/monitoring/PrometheusBootstrap.js +71 -0
- package/dist/monitoring/PrometheusBootstrap.js.map +1 -0
- package/dist/monitoring/PrometheusMetricsBridge.d.ts +60 -0
- package/dist/monitoring/PrometheusMetricsBridge.js +216 -0
- package/dist/monitoring/PrometheusMetricsBridge.js.map +1 -0
- package/dist/monitoring/RateLimiter.d.ts +58 -0
- package/dist/monitoring/RateLimiter.js +128 -0
- package/dist/monitoring/RateLimiter.js.map +1 -0
- package/dist/monitoring/StructuredLogger.d.ts +131 -0
- package/dist/monitoring/StructuredLogger.js +207 -0
- package/dist/monitoring/StructuredLogger.js.map +1 -0
- package/dist/monitoring/TracingBootstrap.d.ts +69 -0
- package/dist/monitoring/TracingBootstrap.js +129 -0
- package/dist/monitoring/TracingBootstrap.js.map +1 -0
- package/dist/monitoring/TriggerMetricsCollector.d.ts +94 -0
- package/dist/monitoring/TriggerMetricsCollector.js +174 -0
- package/dist/monitoring/TriggerMetricsCollector.js.map +1 -0
- package/dist/monitoring/index.d.ts +9 -0
- package/dist/monitoring/index.js +10 -0
- package/dist/monitoring/index.js.map +1 -0
- package/dist/openapi/OpenAPIGenerator.d.ts +192 -0
- package/dist/openapi/OpenAPIGenerator.js +373 -0
- package/dist/openapi/OpenAPIGenerator.js.map +1 -0
- package/dist/openapi/index.d.ts +20 -0
- package/dist/openapi/index.js +20 -0
- package/dist/openapi/index.js.map +1 -0
- package/dist/security/ABAC.d.ts +224 -0
- package/dist/security/ABAC.js +380 -0
- package/dist/security/ABAC.js.map +1 -0
- package/dist/security/AuditLogger.d.ts +242 -0
- package/dist/security/AuditLogger.js +317 -0
- package/dist/security/AuditLogger.js.map +1 -0
- package/dist/security/AuthMiddleware.d.ts +163 -0
- package/dist/security/AuthMiddleware.js +274 -0
- package/dist/security/AuthMiddleware.js.map +1 -0
- package/dist/security/EncryptionAtRest.d.ts +206 -0
- package/dist/security/EncryptionAtRest.js +236 -0
- package/dist/security/EncryptionAtRest.js.map +1 -0
- package/dist/security/OAuthProvider.d.ts +334 -0
- package/dist/security/OAuthProvider.js +719 -0
- package/dist/security/OAuthProvider.js.map +1 -0
- package/dist/security/PIIDetector.d.ts +233 -0
- package/dist/security/PIIDetector.js +354 -0
- package/dist/security/PIIDetector.js.map +1 -0
- package/dist/security/RBAC.d.ts +143 -0
- package/dist/security/RBAC.js +285 -0
- package/dist/security/RBAC.js.map +1 -0
- package/dist/security/SecretManager.d.ts +652 -0
- package/dist/security/SecretManager.js +1146 -0
- package/dist/security/SecretManager.js.map +1 -0
- package/dist/security/TLSConfig.d.ts +305 -0
- package/dist/security/TLSConfig.js +550 -0
- package/dist/security/TLSConfig.js.map +1 -0
- package/dist/security/index.d.ts +79 -0
- package/dist/security/index.js +80 -0
- package/dist/security/index.js.map +1 -0
- package/dist/testing/TestHarness.d.ts +189 -0
- package/dist/testing/TestHarness.js +272 -0
- package/dist/testing/TestHarness.js.map +1 -0
- package/dist/testing/TestLogger.d.ts +103 -0
- package/dist/testing/TestLogger.js +153 -0
- package/dist/testing/TestLogger.js.map +1 -0
- package/dist/testing/WorkflowTestRunner.d.ts +172 -0
- package/dist/testing/WorkflowTestRunner.js +355 -0
- package/dist/testing/WorkflowTestRunner.js.map +1 -0
- package/dist/testing/index.d.ts +21 -0
- package/dist/testing/index.js +22 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tracing/InMemoryRunStore.d.ts +44 -0
- package/dist/tracing/InMemoryRunStore.js +341 -0
- package/dist/tracing/InMemoryRunStore.js.map +1 -0
- package/dist/tracing/PostgresRunStore.d.ts +82 -0
- package/dist/tracing/PostgresRunStore.js +640 -0
- package/dist/tracing/PostgresRunStore.js.map +1 -0
- package/dist/tracing/RunStore.d.ts +38 -0
- package/dist/tracing/RunStore.js +2 -0
- package/dist/tracing/RunStore.js.map +1 -0
- package/dist/tracing/RunTracker.d.ts +75 -0
- package/dist/tracing/RunTracker.js +374 -0
- package/dist/tracing/RunTracker.js.map +1 -0
- package/dist/tracing/SqliteRunStore.d.ts +53 -0
- package/dist/tracing/SqliteRunStore.js +703 -0
- package/dist/tracing/SqliteRunStore.js.map +1 -0
- package/dist/tracing/TraceRouter.d.ts +47 -0
- package/dist/tracing/TraceRouter.js +904 -0
- package/dist/tracing/TraceRouter.js.map +1 -0
- package/dist/tracing/TracingLogger.d.ts +21 -0
- package/dist/tracing/TracingLogger.js +62 -0
- package/dist/tracing/TracingLogger.js.map +1 -0
- package/dist/tracing/createStore.d.ts +30 -0
- package/dist/tracing/createStore.js +75 -0
- package/dist/tracing/createStore.js.map +1 -0
- package/dist/tracing/index.d.ts +13 -0
- package/dist/tracing/index.js +9 -0
- package/dist/tracing/index.js.map +1 -0
- package/dist/tracing/sanitize.d.ts +7 -0
- package/dist/tracing/sanitize.js +95 -0
- package/dist/tracing/sanitize.js.map +1 -0
- package/dist/tracing/types.d.ts +178 -0
- package/dist/tracing/types.js +3 -0
- package/dist/tracing/types.js.map +1 -0
- package/dist/types/Average.d.ts +11 -0
- package/dist/types/Average.js +2 -0
- package/dist/types/Average.js.map +1 -0
- package/dist/types/Condition.d.ts +8 -0
- package/dist/types/Condition.js +2 -0
- package/dist/types/Condition.js.map +1 -0
- package/dist/types/Conditions.d.ts +5 -0
- package/dist/types/Conditions.js +2 -0
- package/dist/types/Conditions.js.map +1 -0
- package/dist/types/Config.d.ts +12 -0
- package/dist/types/Config.js +2 -0
- package/dist/types/Config.js.map +1 -0
- package/dist/types/Flow.d.ts +5 -0
- package/dist/types/Flow.js +2 -0
- package/dist/types/Flow.js.map +1 -0
- package/dist/types/GlobalOptions.d.ts +11 -0
- package/dist/types/GlobalOptions.js +2 -0
- package/dist/types/GlobalOptions.js.map +1 -0
- package/dist/types/Inputs.d.ts +5 -0
- package/dist/types/Inputs.js +2 -0
- package/dist/types/Inputs.js.map +1 -0
- package/dist/types/JsonLikeObject.d.ts +3 -0
- package/dist/types/JsonLikeObject.js +2 -0
- package/dist/types/JsonLikeObject.js.map +1 -0
- package/dist/types/Mapper.d.ts +5 -0
- package/dist/types/Mapper.js +2 -0
- package/dist/types/Mapper.js.map +1 -0
- package/dist/types/Node.d.ts +10 -0
- package/dist/types/Node.js +2 -0
- package/dist/types/Node.js.map +1 -0
- package/dist/types/ParamsDictionary.d.ts +3 -0
- package/dist/types/ParamsDictionary.js +2 -0
- package/dist/types/ParamsDictionary.js.map +1 -0
- package/dist/types/Properties.d.ts +5 -0
- package/dist/types/Properties.js +2 -0
- package/dist/types/Properties.js.map +1 -0
- package/dist/types/Targets.d.ts +5 -0
- package/dist/types/Targets.js +2 -0
- package/dist/types/Targets.js.map +1 -0
- package/dist/types/Trigger.d.ts +5 -0
- package/dist/types/Trigger.js +2 -0
- package/dist/types/Trigger.js.map +1 -0
- package/dist/types/TriggerHttp.d.ts +7 -0
- package/dist/types/TriggerHttp.js +2 -0
- package/dist/types/TriggerHttp.js.map +1 -0
- package/dist/types/TriggerResponse.d.ts +6 -0
- package/dist/types/TriggerResponse.js +2 -0
- package/dist/types/TriggerResponse.js.map +1 -0
- package/dist/types/Triggers.d.ts +5 -0
- package/dist/types/Triggers.js +2 -0
- package/dist/types/Triggers.js.map +1 -0
- package/dist/types/TryCatch.d.ts +6 -0
- package/dist/types/TryCatch.js +2 -0
- package/dist/types/TryCatch.js.map +1 -0
- package/dist/visualization/NodeDependencyGraph.d.ts +76 -0
- package/dist/visualization/NodeDependencyGraph.js +418 -0
- package/dist/visualization/NodeDependencyGraph.js.map +1 -0
- package/dist/visualization/WorkflowVisualizer.d.ts +144 -0
- package/dist/visualization/WorkflowVisualizer.js +446 -0
- package/dist/visualization/WorkflowVisualizer.js.map +1 -0
- package/package.json +95 -0
|
@@ -0,0 +1,1146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret Management for Blok Framework
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified interface for secret management across multiple providers:
|
|
5
|
+
* - HashiCorp Vault (KV v2 engine via REST API)
|
|
6
|
+
* - AWS Secrets Manager (via @aws-sdk/client-secrets-manager)
|
|
7
|
+
* - GCP Secret Manager (via @google-cloud/secret-manager)
|
|
8
|
+
* - Environment Variables (process.env)
|
|
9
|
+
* - In-Memory (for testing)
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Provider chain: try providers in order, first match wins
|
|
13
|
+
* - Caching layer with TTL and max size (LRU eviction)
|
|
14
|
+
* - Audit event emission for secret access tracking
|
|
15
|
+
* - Template resolution for `${secret:KEY}` patterns
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import {
|
|
20
|
+
* SecretManager,
|
|
21
|
+
* EnvironmentSecretProvider,
|
|
22
|
+
* InMemorySecretProvider,
|
|
23
|
+
* } from "@blokjs/runner";
|
|
24
|
+
*
|
|
25
|
+
* // Simple setup with environment variables
|
|
26
|
+
* const secrets = new SecretManager({
|
|
27
|
+
* providers: [
|
|
28
|
+
* { type: "environment", config: { prefix: "BLOK_SECRET_" } },
|
|
29
|
+
* ],
|
|
30
|
+
* cache: { enabled: true, ttlMs: 60_000, maxSize: 100 },
|
|
31
|
+
* auditLog: true,
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* const dbPassword = await secrets.getSecret("DB_PASSWORD");
|
|
35
|
+
* const connStr = await secrets.resolveTemplate(
|
|
36
|
+
* "postgres://user:${secret:DB_PASSWORD}@host/db"
|
|
37
|
+
* );
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
import { EventEmitter } from "node:events";
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// EnvironmentSecretProvider
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
/**
|
|
45
|
+
* Secret provider backed by process.env
|
|
46
|
+
*
|
|
47
|
+
* Reads environment variables, optionally with a prefix. Supports
|
|
48
|
+
* case-insensitive lookups when configured.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const provider = new EnvironmentSecretProvider({ prefix: "APP_" });
|
|
53
|
+
* // Reads process.env.APP_DATABASE_URL
|
|
54
|
+
* const dbUrl = await provider.get("DATABASE_URL");
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export class EnvironmentSecretProvider {
|
|
58
|
+
name = "environment";
|
|
59
|
+
prefix;
|
|
60
|
+
caseSensitive;
|
|
61
|
+
constructor(config) {
|
|
62
|
+
this.prefix = config?.prefix ?? "";
|
|
63
|
+
this.caseSensitive = config?.caseSensitive ?? true;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Retrieve an environment variable value
|
|
67
|
+
* @param key - Variable name (without prefix)
|
|
68
|
+
*/
|
|
69
|
+
async get(key) {
|
|
70
|
+
const envKey = this.resolveKey(key);
|
|
71
|
+
const value = process.env[envKey];
|
|
72
|
+
return value !== undefined ? value : null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Set an environment variable (primarily useful for testing)
|
|
76
|
+
* @param key - Variable name (without prefix)
|
|
77
|
+
* @param value - Value to set
|
|
78
|
+
*/
|
|
79
|
+
async set(key, value, _metadata) {
|
|
80
|
+
const envKey = this.resolveKey(key);
|
|
81
|
+
process.env[envKey] = value;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Delete an environment variable
|
|
85
|
+
* @param key - Variable name (without prefix)
|
|
86
|
+
*/
|
|
87
|
+
async delete(key) {
|
|
88
|
+
const envKey = this.resolveKey(key);
|
|
89
|
+
delete process.env[envKey];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* List environment variable names matching the configured prefix
|
|
93
|
+
* @param prefix - Additional prefix to filter by (applied after the provider prefix)
|
|
94
|
+
*/
|
|
95
|
+
async list(prefix) {
|
|
96
|
+
const fullPrefix = this.prefix + (prefix ?? "");
|
|
97
|
+
const keys = Object.keys(process.env).filter((k) => {
|
|
98
|
+
const candidate = this.caseSensitive ? k : k.toUpperCase();
|
|
99
|
+
const match = this.caseSensitive ? fullPrefix : fullPrefix.toUpperCase();
|
|
100
|
+
return candidate.startsWith(match);
|
|
101
|
+
});
|
|
102
|
+
// Strip the provider prefix from returned keys
|
|
103
|
+
return keys.map((k) => k.slice(this.prefix.length));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check whether an environment variable exists
|
|
107
|
+
* @param key - Variable name (without prefix)
|
|
108
|
+
*/
|
|
109
|
+
async exists(key) {
|
|
110
|
+
const envKey = this.resolveKey(key);
|
|
111
|
+
return envKey in process.env;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Build the full environment variable name from a logical key
|
|
115
|
+
*/
|
|
116
|
+
resolveKey(key) {
|
|
117
|
+
const fullKey = this.prefix + key;
|
|
118
|
+
if (this.caseSensitive) {
|
|
119
|
+
return fullKey;
|
|
120
|
+
}
|
|
121
|
+
// For case-insensitive mode, find the matching key in process.env
|
|
122
|
+
const upper = fullKey.toUpperCase();
|
|
123
|
+
const match = Object.keys(process.env).find((k) => k.toUpperCase() === upper);
|
|
124
|
+
return match ?? fullKey;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// InMemorySecretProvider
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
/**
|
|
131
|
+
* In-memory secret provider for testing and development
|
|
132
|
+
*
|
|
133
|
+
* Stores secrets in a Map with full CRUD support. Provides stats
|
|
134
|
+
* for debugging and verification.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* const provider = new InMemorySecretProvider();
|
|
139
|
+
* await provider.set("API_KEY", "test-key-123");
|
|
140
|
+
* const key = await provider.get("API_KEY"); // "test-key-123"
|
|
141
|
+
* console.log(provider.getStats()); // { size: 1, keys: ["API_KEY"] }
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export class InMemorySecretProvider {
|
|
145
|
+
name = "memory";
|
|
146
|
+
store = new Map();
|
|
147
|
+
/**
|
|
148
|
+
* Retrieve a secret from the in-memory store
|
|
149
|
+
* @param key - The secret key
|
|
150
|
+
*/
|
|
151
|
+
async get(key) {
|
|
152
|
+
const entry = this.store.get(key);
|
|
153
|
+
if (!entry)
|
|
154
|
+
return null;
|
|
155
|
+
// Check expiration
|
|
156
|
+
if (entry.metadata?.expiresAt && entry.metadata.expiresAt < Date.now()) {
|
|
157
|
+
this.store.delete(key);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return entry.value;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Store a secret in the in-memory store
|
|
164
|
+
* @param key - The secret key
|
|
165
|
+
* @param value - The secret value
|
|
166
|
+
* @param metadata - Optional metadata
|
|
167
|
+
*/
|
|
168
|
+
async set(key, value, metadata) {
|
|
169
|
+
this.store.set(key, { value, metadata });
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Delete a secret from the in-memory store
|
|
173
|
+
* @param key - The secret key
|
|
174
|
+
*/
|
|
175
|
+
async delete(key) {
|
|
176
|
+
this.store.delete(key);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* List all secret keys, optionally filtered by prefix
|
|
180
|
+
* @param prefix - Optional prefix filter
|
|
181
|
+
*/
|
|
182
|
+
async list(prefix) {
|
|
183
|
+
const keys = Array.from(this.store.keys());
|
|
184
|
+
if (!prefix)
|
|
185
|
+
return keys;
|
|
186
|
+
return keys.filter((k) => k.startsWith(prefix));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Check whether a secret exists in the store
|
|
190
|
+
* @param key - The secret key
|
|
191
|
+
*/
|
|
192
|
+
async exists(key) {
|
|
193
|
+
if (!this.store.has(key))
|
|
194
|
+
return false;
|
|
195
|
+
// Check expiration
|
|
196
|
+
const entry = this.store.get(key);
|
|
197
|
+
if (entry.metadata?.expiresAt && entry.metadata.expiresAt < Date.now()) {
|
|
198
|
+
this.store.delete(key);
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get debug statistics about the in-memory store
|
|
205
|
+
* @returns Object with size and list of keys
|
|
206
|
+
*/
|
|
207
|
+
getStats() {
|
|
208
|
+
return {
|
|
209
|
+
size: this.store.size,
|
|
210
|
+
keys: Array.from(this.store.keys()),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Clear all secrets from the store
|
|
215
|
+
*/
|
|
216
|
+
clear() {
|
|
217
|
+
this.store.clear();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// VaultSecretProvider
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
/**
|
|
224
|
+
* HashiCorp Vault secret provider (KV v2 engine)
|
|
225
|
+
*
|
|
226
|
+
* Communicates with Vault via its HTTP REST API using the native `fetch` API.
|
|
227
|
+
* Supports token-based authentication, namespaces, and configurable mount paths.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* const vault = new VaultSecretProvider({
|
|
232
|
+
* address: "https://vault.example.com:8200",
|
|
233
|
+
* token: process.env.VAULT_TOKEN,
|
|
234
|
+
* mountPath: "secret",
|
|
235
|
+
* });
|
|
236
|
+
*
|
|
237
|
+
* const dbPassword = await vault.get("database/credentials");
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export class VaultSecretProvider {
|
|
241
|
+
name = "vault";
|
|
242
|
+
address;
|
|
243
|
+
token;
|
|
244
|
+
namespace;
|
|
245
|
+
mountPath;
|
|
246
|
+
apiVersion;
|
|
247
|
+
constructor(config) {
|
|
248
|
+
this.address = config.address.replace(/\/+$/, "");
|
|
249
|
+
this.token = config.token ?? "";
|
|
250
|
+
this.namespace = config.namespace;
|
|
251
|
+
this.mountPath = config.mountPath ?? "secret";
|
|
252
|
+
this.apiVersion = config.apiVersion ?? "v1";
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Read a secret from Vault KV v2
|
|
256
|
+
* @param key - The secret path within the mount
|
|
257
|
+
*/
|
|
258
|
+
async get(key) {
|
|
259
|
+
const url = this.buildUrl("data", key);
|
|
260
|
+
const response = await fetch(url, {
|
|
261
|
+
method: "GET",
|
|
262
|
+
headers: this.buildHeaders(),
|
|
263
|
+
});
|
|
264
|
+
if (response.status === 404)
|
|
265
|
+
return null;
|
|
266
|
+
if (!response.ok) {
|
|
267
|
+
const body = await response.text();
|
|
268
|
+
throw new Error(`Vault GET failed (${response.status}): ${body}`);
|
|
269
|
+
}
|
|
270
|
+
const json = (await response.json());
|
|
271
|
+
// KV v2 nests the actual data under data.data
|
|
272
|
+
const value = json.data?.data?.value;
|
|
273
|
+
if (typeof value === "string")
|
|
274
|
+
return value;
|
|
275
|
+
// If the secret has multiple fields, return as JSON
|
|
276
|
+
if (json.data?.data && typeof json.data.data === "object") {
|
|
277
|
+
return JSON.stringify(json.data.data);
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Write a secret to Vault KV v2
|
|
283
|
+
* @param key - The secret path within the mount
|
|
284
|
+
* @param value - The secret value
|
|
285
|
+
* @param metadata - Optional metadata (stored as custom_metadata)
|
|
286
|
+
*/
|
|
287
|
+
async set(key, value, metadata) {
|
|
288
|
+
const url = this.buildUrl("data", key);
|
|
289
|
+
const body = {
|
|
290
|
+
data: { value },
|
|
291
|
+
};
|
|
292
|
+
if (metadata) {
|
|
293
|
+
body.options = {};
|
|
294
|
+
if (metadata.version) {
|
|
295
|
+
body.options.cas = Number.parseInt(metadata.version, 10);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const response = await fetch(url, {
|
|
299
|
+
method: "POST",
|
|
300
|
+
headers: this.buildHeaders(),
|
|
301
|
+
body: JSON.stringify(body),
|
|
302
|
+
});
|
|
303
|
+
if (!response.ok) {
|
|
304
|
+
const responseBody = await response.text();
|
|
305
|
+
throw new Error(`Vault POST failed (${response.status}): ${responseBody}`);
|
|
306
|
+
}
|
|
307
|
+
// Set custom metadata if provided
|
|
308
|
+
if (metadata?.tags || metadata?.description) {
|
|
309
|
+
await this.setMetadata(key, metadata);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Delete a secret from Vault KV v2
|
|
314
|
+
* @param key - The secret path within the mount
|
|
315
|
+
*/
|
|
316
|
+
async delete(key) {
|
|
317
|
+
const url = this.buildUrl("metadata", key);
|
|
318
|
+
const response = await fetch(url, {
|
|
319
|
+
method: "DELETE",
|
|
320
|
+
headers: this.buildHeaders(),
|
|
321
|
+
});
|
|
322
|
+
if (!response.ok && response.status !== 404) {
|
|
323
|
+
const body = await response.text();
|
|
324
|
+
throw new Error(`Vault DELETE failed (${response.status}): ${body}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* List secret keys under a given path prefix
|
|
329
|
+
* @param prefix - Optional path prefix
|
|
330
|
+
*/
|
|
331
|
+
async list(prefix) {
|
|
332
|
+
const path = prefix ?? "";
|
|
333
|
+
const url = this.buildUrl("metadata", path) + "?list=true";
|
|
334
|
+
const response = await fetch(url, {
|
|
335
|
+
method: "LIST",
|
|
336
|
+
headers: this.buildHeaders(),
|
|
337
|
+
});
|
|
338
|
+
if (response.status === 404)
|
|
339
|
+
return [];
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
const body = await response.text();
|
|
342
|
+
throw new Error(`Vault LIST failed (${response.status}): ${body}`);
|
|
343
|
+
}
|
|
344
|
+
const json = (await response.json());
|
|
345
|
+
return json.data?.keys ?? [];
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Check whether a secret exists in Vault
|
|
349
|
+
* @param key - The secret path within the mount
|
|
350
|
+
*/
|
|
351
|
+
async exists(key) {
|
|
352
|
+
const url = this.buildUrl("data", key);
|
|
353
|
+
const response = await fetch(url, {
|
|
354
|
+
method: "GET",
|
|
355
|
+
headers: this.buildHeaders(),
|
|
356
|
+
});
|
|
357
|
+
return response.ok;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Update the Vault token (e.g., after token renewal)
|
|
361
|
+
* @param token - The new Vault token
|
|
362
|
+
*/
|
|
363
|
+
setToken(token) {
|
|
364
|
+
this.token = token;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Build the full URL for a Vault KV v2 API call
|
|
368
|
+
*/
|
|
369
|
+
buildUrl(operation, path) {
|
|
370
|
+
const cleanPath = path.replace(/^\/+|\/+$/g, "");
|
|
371
|
+
return `${this.address}/${this.apiVersion}/${this.mountPath}/${operation}/${cleanPath}`;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Build common HTTP headers for Vault requests
|
|
375
|
+
*/
|
|
376
|
+
buildHeaders() {
|
|
377
|
+
const headers = {
|
|
378
|
+
"Content-Type": "application/json",
|
|
379
|
+
};
|
|
380
|
+
if (this.token) {
|
|
381
|
+
headers["X-Vault-Token"] = this.token;
|
|
382
|
+
}
|
|
383
|
+
if (this.namespace) {
|
|
384
|
+
headers["X-Vault-Namespace"] = this.namespace;
|
|
385
|
+
}
|
|
386
|
+
return headers;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Set custom metadata on a secret in Vault KV v2
|
|
390
|
+
*/
|
|
391
|
+
async setMetadata(key, metadata) {
|
|
392
|
+
const url = this.buildUrl("metadata", key);
|
|
393
|
+
const body = {
|
|
394
|
+
custom_metadata: {
|
|
395
|
+
...metadata.tags,
|
|
396
|
+
...(metadata.description ? { description: metadata.description } : {}),
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
const response = await fetch(url, {
|
|
400
|
+
method: "POST",
|
|
401
|
+
headers: this.buildHeaders(),
|
|
402
|
+
body: JSON.stringify(body),
|
|
403
|
+
});
|
|
404
|
+
if (!response.ok) {
|
|
405
|
+
// Non-fatal: metadata update failure should not break the set operation
|
|
406
|
+
const responseBody = await response.text();
|
|
407
|
+
console.warn(`Vault metadata update failed (${response.status}): ${responseBody}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
// AWSSecretsProvider
|
|
413
|
+
// ---------------------------------------------------------------------------
|
|
414
|
+
/**
|
|
415
|
+
* AWS Secrets Manager provider
|
|
416
|
+
*
|
|
417
|
+
* Uses the `@aws-sdk/client-secrets-manager` SDK, loaded dynamically at
|
|
418
|
+
* first use to avoid hard dependencies.
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* ```typescript
|
|
422
|
+
* const aws = new AWSSecretsProvider({
|
|
423
|
+
* region: "us-east-1",
|
|
424
|
+
* });
|
|
425
|
+
*
|
|
426
|
+
* const apiKey = await aws.get("prod/api-key");
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
export class AWSSecretsProvider {
|
|
430
|
+
name = "aws";
|
|
431
|
+
region;
|
|
432
|
+
accessKeyId;
|
|
433
|
+
secretAccessKey;
|
|
434
|
+
profile;
|
|
435
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
436
|
+
client = null;
|
|
437
|
+
constructor(config) {
|
|
438
|
+
this.region = config.region;
|
|
439
|
+
this.accessKeyId = config.accessKeyId;
|
|
440
|
+
this.secretAccessKey = config.secretAccessKey;
|
|
441
|
+
this.profile = config.profile;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Retrieve a secret from AWS Secrets Manager
|
|
445
|
+
* @param key - The secret name or ARN
|
|
446
|
+
*/
|
|
447
|
+
async get(key) {
|
|
448
|
+
const client = await this.getClient();
|
|
449
|
+
const { GetSecretValueCommand } = await this.getSDK();
|
|
450
|
+
try {
|
|
451
|
+
const result = await client.send(new GetSecretValueCommand({ SecretId: key }));
|
|
452
|
+
return result.SecretString ?? null;
|
|
453
|
+
}
|
|
454
|
+
catch (err) {
|
|
455
|
+
if (this.isAWSError(err, "ResourceNotFoundException")) {
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
throw err;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Create or update a secret in AWS Secrets Manager
|
|
463
|
+
* @param key - The secret name
|
|
464
|
+
* @param value - The secret value
|
|
465
|
+
* @param metadata - Optional metadata (tags and description supported)
|
|
466
|
+
*/
|
|
467
|
+
async set(key, value, metadata) {
|
|
468
|
+
const client = await this.getClient();
|
|
469
|
+
const sdk = await this.getSDK();
|
|
470
|
+
// Try to update first, create if it does not exist
|
|
471
|
+
try {
|
|
472
|
+
await client.send(new sdk.UpdateSecretCommand({
|
|
473
|
+
SecretId: key,
|
|
474
|
+
SecretString: value,
|
|
475
|
+
...(metadata?.description ? { Description: metadata.description } : {}),
|
|
476
|
+
}));
|
|
477
|
+
}
|
|
478
|
+
catch (err) {
|
|
479
|
+
if (this.isAWSError(err, "ResourceNotFoundException")) {
|
|
480
|
+
const createParams = {
|
|
481
|
+
Name: key,
|
|
482
|
+
SecretString: value,
|
|
483
|
+
};
|
|
484
|
+
if (metadata?.description) {
|
|
485
|
+
createParams.Description = metadata.description;
|
|
486
|
+
}
|
|
487
|
+
if (metadata?.tags) {
|
|
488
|
+
createParams.Tags = Object.entries(metadata.tags).map(([Key, Value]) => ({
|
|
489
|
+
Key,
|
|
490
|
+
Value,
|
|
491
|
+
}));
|
|
492
|
+
}
|
|
493
|
+
await client.send(new sdk.CreateSecretCommand(createParams));
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
throw err;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Delete a secret from AWS Secrets Manager
|
|
502
|
+
* @param key - The secret name or ARN
|
|
503
|
+
*/
|
|
504
|
+
async delete(key) {
|
|
505
|
+
const client = await this.getClient();
|
|
506
|
+
const { DeleteSecretCommand } = await this.getSDK();
|
|
507
|
+
try {
|
|
508
|
+
await client.send(new DeleteSecretCommand({
|
|
509
|
+
SecretId: key,
|
|
510
|
+
ForceDeleteWithoutRecovery: true,
|
|
511
|
+
}));
|
|
512
|
+
}
|
|
513
|
+
catch (err) {
|
|
514
|
+
if (!this.isAWSError(err, "ResourceNotFoundException")) {
|
|
515
|
+
throw err;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* List secrets in AWS Secrets Manager, optionally filtered by name prefix
|
|
521
|
+
* @param prefix - Optional name prefix filter
|
|
522
|
+
*/
|
|
523
|
+
async list(prefix) {
|
|
524
|
+
const client = await this.getClient();
|
|
525
|
+
const { ListSecretsCommand } = await this.getSDK();
|
|
526
|
+
const secrets = [];
|
|
527
|
+
let nextToken;
|
|
528
|
+
do {
|
|
529
|
+
const params = {
|
|
530
|
+
MaxResults: 100,
|
|
531
|
+
...(nextToken ? { NextToken: nextToken } : {}),
|
|
532
|
+
};
|
|
533
|
+
if (prefix) {
|
|
534
|
+
params.Filters = [{ Key: "name", Values: [prefix] }];
|
|
535
|
+
}
|
|
536
|
+
const result = await client.send(new ListSecretsCommand(params));
|
|
537
|
+
if (result.SecretList) {
|
|
538
|
+
for (const secret of result.SecretList) {
|
|
539
|
+
if (secret.Name) {
|
|
540
|
+
secrets.push(secret.Name);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
nextToken = result.NextToken;
|
|
545
|
+
} while (nextToken);
|
|
546
|
+
return secrets;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Check whether a secret exists in AWS Secrets Manager
|
|
550
|
+
* @param key - The secret name or ARN
|
|
551
|
+
*/
|
|
552
|
+
async exists(key) {
|
|
553
|
+
const client = await this.getClient();
|
|
554
|
+
const { DescribeSecretCommand } = await this.getSDK();
|
|
555
|
+
try {
|
|
556
|
+
await client.send(new DescribeSecretCommand({ SecretId: key }));
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
catch (err) {
|
|
560
|
+
if (this.isAWSError(err, "ResourceNotFoundException")) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
throw err;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Lazily initialize and cache the AWS SecretsManager client
|
|
568
|
+
*/
|
|
569
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
570
|
+
async getClient() {
|
|
571
|
+
if (this.client)
|
|
572
|
+
return this.client;
|
|
573
|
+
const { SecretsManagerClient } = await this.getSDK();
|
|
574
|
+
const clientConfig = {
|
|
575
|
+
region: this.region,
|
|
576
|
+
};
|
|
577
|
+
if (this.accessKeyId && this.secretAccessKey) {
|
|
578
|
+
clientConfig.credentials = {
|
|
579
|
+
accessKeyId: this.accessKeyId,
|
|
580
|
+
secretAccessKey: this.secretAccessKey,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
if (this.profile) {
|
|
584
|
+
// When a profile is specified, set the AWS_PROFILE env var so
|
|
585
|
+
// the SDK default credential chain picks it up.
|
|
586
|
+
process.env.AWS_PROFILE = this.profile;
|
|
587
|
+
}
|
|
588
|
+
this.client = new SecretsManagerClient(clientConfig);
|
|
589
|
+
return this.client;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Dynamically import the AWS Secrets Manager SDK
|
|
593
|
+
*/
|
|
594
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
595
|
+
async getSDK() {
|
|
596
|
+
try {
|
|
597
|
+
// @ts-ignore -- optional peer dependency, loaded dynamically at runtime
|
|
598
|
+
return await import("@aws-sdk/client-secrets-manager");
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
throw new Error("AWS Secrets Manager SDK not found. Install it with: npm install @aws-sdk/client-secrets-manager");
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Type-safe check for AWS SDK error names
|
|
606
|
+
*/
|
|
607
|
+
isAWSError(err, code) {
|
|
608
|
+
return typeof err === "object" && err !== null && "name" in err && err.name === code;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
// ---------------------------------------------------------------------------
|
|
612
|
+
// GCPSecretProvider
|
|
613
|
+
// ---------------------------------------------------------------------------
|
|
614
|
+
/**
|
|
615
|
+
* Google Cloud Secret Manager provider
|
|
616
|
+
*
|
|
617
|
+
* Uses the `@google-cloud/secret-manager` SDK, loaded dynamically at
|
|
618
|
+
* first use to avoid hard dependencies.
|
|
619
|
+
*
|
|
620
|
+
* @example
|
|
621
|
+
* ```typescript
|
|
622
|
+
* const gcp = new GCPSecretProvider({
|
|
623
|
+
* projectId: "my-project",
|
|
624
|
+
* });
|
|
625
|
+
*
|
|
626
|
+
* const apiKey = await gcp.get("api-key");
|
|
627
|
+
* ```
|
|
628
|
+
*/
|
|
629
|
+
export class GCPSecretProvider {
|
|
630
|
+
name = "gcp";
|
|
631
|
+
projectId;
|
|
632
|
+
keyFile;
|
|
633
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
634
|
+
client = null;
|
|
635
|
+
constructor(config) {
|
|
636
|
+
this.projectId = config.projectId;
|
|
637
|
+
this.keyFile = config.keyFile;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Retrieve the latest version of a secret from GCP Secret Manager
|
|
641
|
+
* @param key - The secret ID
|
|
642
|
+
*/
|
|
643
|
+
async get(key) {
|
|
644
|
+
const client = await this.getClient();
|
|
645
|
+
try {
|
|
646
|
+
const [version] = await client.accessSecretVersion({
|
|
647
|
+
name: `projects/${this.projectId}/secrets/${key}/versions/latest`,
|
|
648
|
+
});
|
|
649
|
+
const payload = version.payload?.data;
|
|
650
|
+
if (!payload)
|
|
651
|
+
return null;
|
|
652
|
+
if (typeof payload === "string")
|
|
653
|
+
return payload;
|
|
654
|
+
if (payload instanceof Uint8Array || Buffer.isBuffer(payload)) {
|
|
655
|
+
return Buffer.from(payload).toString("utf-8");
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
catch (err) {
|
|
660
|
+
if (this.isGCPNotFoundError(err)) {
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
throw err;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Create a secret and add a version, or add a new version to an existing secret
|
|
668
|
+
* @param key - The secret ID
|
|
669
|
+
* @param value - The secret value
|
|
670
|
+
* @param metadata - Optional metadata (tags mapped to GCP labels)
|
|
671
|
+
*/
|
|
672
|
+
async set(key, value, metadata) {
|
|
673
|
+
const client = await this.getClient();
|
|
674
|
+
const parent = `projects/${this.projectId}`;
|
|
675
|
+
const secretName = `${parent}/secrets/${key}`;
|
|
676
|
+
// Try to create the secret resource first
|
|
677
|
+
try {
|
|
678
|
+
const createRequest = {
|
|
679
|
+
parent,
|
|
680
|
+
secretId: key,
|
|
681
|
+
secret: {
|
|
682
|
+
replication: { automatic: {} },
|
|
683
|
+
...(metadata?.tags ? { labels: metadata.tags } : {}),
|
|
684
|
+
},
|
|
685
|
+
};
|
|
686
|
+
await client.createSecret(createRequest);
|
|
687
|
+
}
|
|
688
|
+
catch (err) {
|
|
689
|
+
// 6 = ALREADY_EXISTS - that is fine, we will add a version
|
|
690
|
+
if (!this.isGCPError(err, 6)) {
|
|
691
|
+
throw err;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
// Add the secret version with the actual payload
|
|
695
|
+
await client.addSecretVersion({
|
|
696
|
+
parent: secretName,
|
|
697
|
+
payload: {
|
|
698
|
+
data: Buffer.from(value, "utf-8"),
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Delete a secret from GCP Secret Manager
|
|
704
|
+
* @param key - The secret ID
|
|
705
|
+
*/
|
|
706
|
+
async delete(key) {
|
|
707
|
+
const client = await this.getClient();
|
|
708
|
+
try {
|
|
709
|
+
await client.deleteSecret({
|
|
710
|
+
name: `projects/${this.projectId}/secrets/${key}`,
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
catch (err) {
|
|
714
|
+
if (!this.isGCPNotFoundError(err)) {
|
|
715
|
+
throw err;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* List secrets in the GCP project, optionally filtered by prefix
|
|
721
|
+
* @param prefix - Optional prefix filter applied to secret IDs
|
|
722
|
+
*/
|
|
723
|
+
async list(prefix) {
|
|
724
|
+
const client = await this.getClient();
|
|
725
|
+
const parent = `projects/${this.projectId}`;
|
|
726
|
+
const [secrets] = await client.listSecrets({ parent });
|
|
727
|
+
const names = [];
|
|
728
|
+
for (const secret of secrets) {
|
|
729
|
+
// Extract secret ID from the full resource name
|
|
730
|
+
const fullName = secret.name ?? "";
|
|
731
|
+
const parts = fullName.split("/");
|
|
732
|
+
const secretId = parts[parts.length - 1];
|
|
733
|
+
if (secretId) {
|
|
734
|
+
if (!prefix || secretId.startsWith(prefix)) {
|
|
735
|
+
names.push(secretId);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return names;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Check whether a secret exists in GCP Secret Manager
|
|
743
|
+
* @param key - The secret ID
|
|
744
|
+
*/
|
|
745
|
+
async exists(key) {
|
|
746
|
+
const client = await this.getClient();
|
|
747
|
+
try {
|
|
748
|
+
await client.getSecret({
|
|
749
|
+
name: `projects/${this.projectId}/secrets/${key}`,
|
|
750
|
+
});
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
catch (err) {
|
|
754
|
+
if (this.isGCPNotFoundError(err)) {
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
throw err;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Lazily initialize and cache the GCP Secret Manager client
|
|
762
|
+
*/
|
|
763
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
764
|
+
async getClient() {
|
|
765
|
+
if (this.client)
|
|
766
|
+
return this.client;
|
|
767
|
+
try {
|
|
768
|
+
// @ts-ignore -- optional peer dependency, loaded dynamically at runtime
|
|
769
|
+
const module = await import("@google-cloud/secret-manager");
|
|
770
|
+
const { SecretManagerServiceClient } = module;
|
|
771
|
+
const options = {};
|
|
772
|
+
if (this.keyFile) {
|
|
773
|
+
options.keyFilename = this.keyFile;
|
|
774
|
+
}
|
|
775
|
+
this.client = new SecretManagerServiceClient(options);
|
|
776
|
+
return this.client;
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
throw new Error("GCP Secret Manager SDK not found. Install it with: npm install @google-cloud/secret-manager");
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Check for GCP "not found" errors (gRPC status code 5)
|
|
784
|
+
*/
|
|
785
|
+
isGCPNotFoundError(err) {
|
|
786
|
+
return this.isGCPError(err, 5);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Check for specific gRPC error codes from the GCP SDK
|
|
790
|
+
*/
|
|
791
|
+
isGCPError(err, code) {
|
|
792
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === code;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// ---------------------------------------------------------------------------
|
|
796
|
+
// SecretManager
|
|
797
|
+
// ---------------------------------------------------------------------------
|
|
798
|
+
/** Regex for matching `${secret:KEY}` patterns in template strings */
|
|
799
|
+
const SECRET_TEMPLATE_REGEX = /\$\{secret:([^}]+)\}/g;
|
|
800
|
+
/**
|
|
801
|
+
* Unified Secret Manager for the Blok Framework
|
|
802
|
+
*
|
|
803
|
+
* Orchestrates multiple secret providers with a provider chain (first match
|
|
804
|
+
* wins), optional caching, and audit event emission.
|
|
805
|
+
*
|
|
806
|
+
* @example
|
|
807
|
+
* ```typescript
|
|
808
|
+
* const manager = new SecretManager({
|
|
809
|
+
* providers: [
|
|
810
|
+
* { type: "vault", config: { address: "https://vault:8200", token: "s.xxx" } },
|
|
811
|
+
* { type: "environment", config: { prefix: "BLOK_" } },
|
|
812
|
+
* ],
|
|
813
|
+
* cache: { enabled: true, ttlMs: 300_000, maxSize: 500 },
|
|
814
|
+
* auditLog: true,
|
|
815
|
+
* });
|
|
816
|
+
*
|
|
817
|
+
* manager.on("secretAccess", (event) => {
|
|
818
|
+
* console.log(`[audit] ${event.operation} ${event.key} via ${event.provider}`);
|
|
819
|
+
* });
|
|
820
|
+
*
|
|
821
|
+
* const password = await manager.getSecretOrThrow("DB_PASSWORD");
|
|
822
|
+
* const connStr = await manager.resolveTemplate(
|
|
823
|
+
* "postgres://admin:${secret:DB_PASSWORD}@db:5432/app"
|
|
824
|
+
* );
|
|
825
|
+
* ```
|
|
826
|
+
*/
|
|
827
|
+
export class SecretManager extends EventEmitter {
|
|
828
|
+
providers = [];
|
|
829
|
+
cache = new Map();
|
|
830
|
+
cacheConfig;
|
|
831
|
+
auditLog;
|
|
832
|
+
cacheAccessOrder = [];
|
|
833
|
+
constructor(config) {
|
|
834
|
+
super();
|
|
835
|
+
this.cacheConfig = config.cache ?? { enabled: false, ttlMs: 0, maxSize: 0 };
|
|
836
|
+
this.auditLog = config.auditLog ?? false;
|
|
837
|
+
// Initialize providers in order
|
|
838
|
+
for (const providerConfig of config.providers) {
|
|
839
|
+
this.providers.push(this.createProvider(providerConfig));
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Retrieve a secret value by key
|
|
844
|
+
*
|
|
845
|
+
* Checks the cache first (if enabled), then queries each provider
|
|
846
|
+
* in order until a value is found.
|
|
847
|
+
*
|
|
848
|
+
* @param key - The secret key
|
|
849
|
+
* @returns The secret value, or null if not found in any provider
|
|
850
|
+
*/
|
|
851
|
+
async getSecret(key) {
|
|
852
|
+
// Check cache
|
|
853
|
+
if (this.cacheConfig.enabled) {
|
|
854
|
+
const cached = this.getCached(key);
|
|
855
|
+
if (cached !== undefined) {
|
|
856
|
+
this.emitAccess("get", key, "cache", true, true);
|
|
857
|
+
return cached;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
// Query providers in order
|
|
861
|
+
for (const provider of this.providers) {
|
|
862
|
+
try {
|
|
863
|
+
const value = await provider.get(key);
|
|
864
|
+
if (value !== null) {
|
|
865
|
+
if (this.cacheConfig.enabled) {
|
|
866
|
+
this.setCache(key, value);
|
|
867
|
+
}
|
|
868
|
+
this.emitAccess("get", key, provider.name, true, false);
|
|
869
|
+
return value;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
this.emitAccess("get", key, provider.name, false, false, errorMessage(err));
|
|
874
|
+
// Continue to next provider
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
this.emitAccess("get", key, "none", true, false);
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Retrieve a secret or throw if it does not exist
|
|
882
|
+
*
|
|
883
|
+
* @param key - The secret key
|
|
884
|
+
* @returns The secret value
|
|
885
|
+
* @throws Error if the secret is not found in any provider
|
|
886
|
+
*/
|
|
887
|
+
async getSecretOrThrow(key) {
|
|
888
|
+
const value = await this.getSecret(key);
|
|
889
|
+
if (value === null) {
|
|
890
|
+
throw new Error(`Secret '${key}' not found in any provider`);
|
|
891
|
+
}
|
|
892
|
+
return value;
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Store a secret value in the first writable provider
|
|
896
|
+
*
|
|
897
|
+
* @param key - The secret key
|
|
898
|
+
* @param value - The secret value
|
|
899
|
+
* @param metadata - Optional metadata to associate with the secret
|
|
900
|
+
*/
|
|
901
|
+
async setSecret(key, value, metadata) {
|
|
902
|
+
let written = false;
|
|
903
|
+
for (const provider of this.providers) {
|
|
904
|
+
try {
|
|
905
|
+
await provider.set(key, value, metadata);
|
|
906
|
+
written = true;
|
|
907
|
+
// Update cache
|
|
908
|
+
if (this.cacheConfig.enabled) {
|
|
909
|
+
this.setCache(key, value);
|
|
910
|
+
}
|
|
911
|
+
this.emitAccess("set", key, provider.name, true, false);
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
catch (err) {
|
|
915
|
+
this.emitAccess("set", key, provider.name, false, false, errorMessage(err));
|
|
916
|
+
// Continue to next provider
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (!written) {
|
|
920
|
+
throw new Error(`Failed to set secret '${key}' in any provider`);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Delete a secret from all providers that contain it
|
|
925
|
+
*
|
|
926
|
+
* @param key - The secret key
|
|
927
|
+
*/
|
|
928
|
+
async deleteSecret(key) {
|
|
929
|
+
// Invalidate cache
|
|
930
|
+
this.cache.delete(key);
|
|
931
|
+
this.cacheAccessOrder = this.cacheAccessOrder.filter((k) => k !== key);
|
|
932
|
+
for (const provider of this.providers) {
|
|
933
|
+
try {
|
|
934
|
+
await provider.delete(key);
|
|
935
|
+
this.emitAccess("delete", key, provider.name, true, false);
|
|
936
|
+
}
|
|
937
|
+
catch (err) {
|
|
938
|
+
this.emitAccess("delete", key, provider.name, false, false, errorMessage(err));
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* List secret keys across all providers, optionally filtered by prefix
|
|
944
|
+
*
|
|
945
|
+
* Merges results from all providers and deduplicates.
|
|
946
|
+
*
|
|
947
|
+
* @param prefix - Optional prefix filter
|
|
948
|
+
* @returns Deduplicated array of secret key names
|
|
949
|
+
*/
|
|
950
|
+
async listSecrets(prefix) {
|
|
951
|
+
const allKeys = new Set();
|
|
952
|
+
for (const provider of this.providers) {
|
|
953
|
+
try {
|
|
954
|
+
const keys = await provider.list(prefix);
|
|
955
|
+
for (const key of keys) {
|
|
956
|
+
allKeys.add(key);
|
|
957
|
+
}
|
|
958
|
+
this.emitAccess("list", undefined, provider.name, true, false);
|
|
959
|
+
}
|
|
960
|
+
catch (err) {
|
|
961
|
+
this.emitAccess("list", undefined, provider.name, false, false, errorMessage(err));
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return Array.from(allKeys);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Check whether a secret exists in any provider
|
|
968
|
+
*
|
|
969
|
+
* @param key - The secret key
|
|
970
|
+
* @returns True if the secret exists in at least one provider
|
|
971
|
+
*/
|
|
972
|
+
async exists(key) {
|
|
973
|
+
// Check cache first
|
|
974
|
+
if (this.cacheConfig.enabled) {
|
|
975
|
+
const cached = this.getCached(key);
|
|
976
|
+
if (cached !== undefined) {
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
for (const provider of this.providers) {
|
|
981
|
+
try {
|
|
982
|
+
const found = await provider.exists(key);
|
|
983
|
+
if (found) {
|
|
984
|
+
this.emitAccess("exists", key, provider.name, true, false);
|
|
985
|
+
return true;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
catch (err) {
|
|
989
|
+
this.emitAccess("exists", key, provider.name, false, false, errorMessage(err));
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Resolve `${secret:KEY}` patterns in a template string
|
|
996
|
+
*
|
|
997
|
+
* Replaces every occurrence of `${secret:SOME_KEY}` with the actual
|
|
998
|
+
* secret value from the provider chain. Missing secrets are replaced
|
|
999
|
+
* with an empty string.
|
|
1000
|
+
*
|
|
1001
|
+
* @param template - The template string with `${secret:...}` placeholders
|
|
1002
|
+
* @returns The resolved string with secret values substituted
|
|
1003
|
+
*
|
|
1004
|
+
* @example
|
|
1005
|
+
* ```typescript
|
|
1006
|
+
* const resolved = await manager.resolveTemplate(
|
|
1007
|
+
* "mongodb://${secret:MONGO_USER}:${secret:MONGO_PASS}@host/db"
|
|
1008
|
+
* );
|
|
1009
|
+
* ```
|
|
1010
|
+
*/
|
|
1011
|
+
async resolveTemplate(template) {
|
|
1012
|
+
const matches = [];
|
|
1013
|
+
let match;
|
|
1014
|
+
// Reset regex state
|
|
1015
|
+
SECRET_TEMPLATE_REGEX.lastIndex = 0;
|
|
1016
|
+
while ((match = SECRET_TEMPLATE_REGEX.exec(template)) !== null) {
|
|
1017
|
+
matches.push({ placeholder: match[0], key: match[1] });
|
|
1018
|
+
}
|
|
1019
|
+
if (matches.length === 0)
|
|
1020
|
+
return template;
|
|
1021
|
+
// Resolve all secrets in parallel
|
|
1022
|
+
const resolutions = await Promise.all(matches.map(async (m) => ({
|
|
1023
|
+
placeholder: m.placeholder,
|
|
1024
|
+
value: (await this.getSecret(m.key)) ?? "",
|
|
1025
|
+
})));
|
|
1026
|
+
let result = template;
|
|
1027
|
+
for (const resolution of resolutions) {
|
|
1028
|
+
result = result.split(resolution.placeholder).join(resolution.value);
|
|
1029
|
+
}
|
|
1030
|
+
return result;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Get the list of configured providers
|
|
1034
|
+
* @returns Array of provider instances
|
|
1035
|
+
*/
|
|
1036
|
+
getProviders() {
|
|
1037
|
+
return [...this.providers];
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Get current cache statistics
|
|
1041
|
+
* @returns Object with cache size and hit information
|
|
1042
|
+
*/
|
|
1043
|
+
getCacheStats() {
|
|
1044
|
+
return {
|
|
1045
|
+
size: this.cache.size,
|
|
1046
|
+
maxSize: this.cacheConfig.maxSize,
|
|
1047
|
+
enabled: this.cacheConfig.enabled,
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Clear the secret cache
|
|
1052
|
+
*/
|
|
1053
|
+
clearCache() {
|
|
1054
|
+
this.cache.clear();
|
|
1055
|
+
this.cacheAccessOrder = [];
|
|
1056
|
+
}
|
|
1057
|
+
// -----------------------------------------------------------------------
|
|
1058
|
+
// Private helpers
|
|
1059
|
+
// -----------------------------------------------------------------------
|
|
1060
|
+
/**
|
|
1061
|
+
* Create a provider instance from its configuration
|
|
1062
|
+
*/
|
|
1063
|
+
createProvider(config) {
|
|
1064
|
+
switch (config.type) {
|
|
1065
|
+
case "environment":
|
|
1066
|
+
return new EnvironmentSecretProvider(config.config);
|
|
1067
|
+
case "memory":
|
|
1068
|
+
return new InMemorySecretProvider();
|
|
1069
|
+
case "vault":
|
|
1070
|
+
return new VaultSecretProvider(config.config);
|
|
1071
|
+
case "aws":
|
|
1072
|
+
return new AWSSecretsProvider(config.config);
|
|
1073
|
+
case "gcp":
|
|
1074
|
+
return new GCPSecretProvider(config.config);
|
|
1075
|
+
default: {
|
|
1076
|
+
const exhaustive = config;
|
|
1077
|
+
throw new Error(`Unknown secret provider type: ${exhaustive.type}`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Retrieve a value from the cache, returning undefined if not found or expired
|
|
1083
|
+
*/
|
|
1084
|
+
getCached(key) {
|
|
1085
|
+
const entry = this.cache.get(key);
|
|
1086
|
+
if (!entry)
|
|
1087
|
+
return undefined;
|
|
1088
|
+
if (Date.now() > entry.expiresAt) {
|
|
1089
|
+
this.cache.delete(key);
|
|
1090
|
+
this.cacheAccessOrder = this.cacheAccessOrder.filter((k) => k !== key);
|
|
1091
|
+
return undefined;
|
|
1092
|
+
}
|
|
1093
|
+
// Move to end of access order (LRU)
|
|
1094
|
+
this.cacheAccessOrder = this.cacheAccessOrder.filter((k) => k !== key);
|
|
1095
|
+
this.cacheAccessOrder.push(key);
|
|
1096
|
+
return entry.value;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Store a value in the cache with TTL, evicting LRU entries if at capacity
|
|
1100
|
+
*/
|
|
1101
|
+
setCache(key, value) {
|
|
1102
|
+
// Evict if at capacity
|
|
1103
|
+
while (this.cache.size >= this.cacheConfig.maxSize && this.cacheAccessOrder.length > 0) {
|
|
1104
|
+
const evict = this.cacheAccessOrder.shift();
|
|
1105
|
+
if (evict) {
|
|
1106
|
+
this.cache.delete(evict);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
this.cache.set(key, {
|
|
1110
|
+
value,
|
|
1111
|
+
expiresAt: Date.now() + this.cacheConfig.ttlMs,
|
|
1112
|
+
});
|
|
1113
|
+
// Update access order
|
|
1114
|
+
this.cacheAccessOrder = this.cacheAccessOrder.filter((k) => k !== key);
|
|
1115
|
+
this.cacheAccessOrder.push(key);
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Emit a secret access audit event
|
|
1119
|
+
*/
|
|
1120
|
+
emitAccess(operation, key, provider, success, cached, error) {
|
|
1121
|
+
if (!this.auditLog)
|
|
1122
|
+
return;
|
|
1123
|
+
const event = {
|
|
1124
|
+
operation,
|
|
1125
|
+
key,
|
|
1126
|
+
provider,
|
|
1127
|
+
success,
|
|
1128
|
+
cached,
|
|
1129
|
+
timestamp: new Date().toISOString(),
|
|
1130
|
+
...(error ? { error } : {}),
|
|
1131
|
+
};
|
|
1132
|
+
this.emit("secretAccess", event);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
// ---------------------------------------------------------------------------
|
|
1136
|
+
// Helpers
|
|
1137
|
+
// ---------------------------------------------------------------------------
|
|
1138
|
+
/**
|
|
1139
|
+
* Safely extract an error message from an unknown thrown value
|
|
1140
|
+
*/
|
|
1141
|
+
function errorMessage(err) {
|
|
1142
|
+
if (err instanceof Error)
|
|
1143
|
+
return err.message;
|
|
1144
|
+
return String(err);
|
|
1145
|
+
}
|
|
1146
|
+
//# sourceMappingURL=SecretManager.js.map
|