@kibibit/configit 1.0.0-beta.26 → 1.0.0-beta.27
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/README.md +419 -0
- package/lib/scripts/test-vault-comprehensive.d.ts +2 -0
- package/lib/scripts/test-vault-comprehensive.d.ts.map +1 -0
- package/lib/scripts/test-vault-comprehensive.js +422 -0
- package/lib/scripts/test-vault-comprehensive.js.map +1 -0
- package/lib/scripts/test-vault-dynamic.d.ts +2 -0
- package/lib/scripts/test-vault-dynamic.d.ts.map +1 -0
- package/lib/scripts/test-vault-dynamic.js +193 -0
- package/lib/scripts/test-vault-dynamic.js.map +1 -0
- package/lib/scripts/test-vault-gcp-ttl.d.ts +3 -0
- package/lib/scripts/test-vault-gcp-ttl.d.ts.map +1 -0
- package/lib/scripts/test-vault-gcp-ttl.js +218 -0
- package/lib/scripts/test-vault-gcp-ttl.js.map +1 -0
- package/lib/scripts/test-vault.d.ts +2 -0
- package/lib/scripts/test-vault.d.ts.map +1 -0
- package/lib/scripts/test-vault.js +167 -0
- package/lib/scripts/test-vault.js.map +1 -0
- package/lib/src/config.errors.d.ts.map +1 -0
- package/lib/src/config.errors.js.map +1 -0
- package/lib/src/config.model.d.ts.map +1 -0
- package/lib/src/config.model.js.map +1 -0
- package/lib/{config.service.d.ts → src/config.service.d.ts} +10 -1
- package/lib/src/config.service.d.ts.map +1 -0
- package/lib/{config.service.js → src/config.service.js} +75 -9
- package/lib/src/config.service.js.map +1 -0
- package/lib/src/environment.service.d.ts.map +1 -0
- package/lib/src/environment.service.js.map +1 -0
- package/lib/{index.d.ts → src/index.d.ts} +1 -0
- package/lib/src/index.d.ts.map +1 -0
- package/lib/{index.js → src/index.js} +1 -0
- package/lib/src/index.js.map +1 -0
- package/lib/src/json-schema.validator.d.ts.map +1 -0
- package/lib/src/json-schema.validator.js.map +1 -0
- package/lib/src/vault/__tests__/vault-integration.test.d.ts +2 -0
- package/lib/src/vault/__tests__/vault-integration.test.d.ts.map +1 -0
- package/lib/src/vault/__tests__/vault-integration.test.js +190 -0
- package/lib/src/vault/__tests__/vault-integration.test.js.map +1 -0
- package/lib/src/vault/decorators.d.ts +17 -0
- package/lib/src/vault/decorators.d.ts.map +1 -0
- package/lib/src/vault/decorators.js +149 -0
- package/lib/src/vault/decorators.js.map +1 -0
- package/lib/src/vault/index.d.ts +7 -0
- package/lib/src/vault/index.d.ts.map +1 -0
- package/lib/src/vault/index.js +42 -0
- package/lib/src/vault/index.js.map +1 -0
- package/lib/src/vault/secret-refresh-manager.d.ts +23 -0
- package/lib/src/vault/secret-refresh-manager.d.ts.map +1 -0
- package/lib/src/vault/secret-refresh-manager.js +149 -0
- package/lib/src/vault/secret-refresh-manager.js.map +1 -0
- package/lib/src/vault/types.d.ts +149 -0
- package/lib/src/vault/types.d.ts.map +1 -0
- package/lib/src/vault/types.js +4 -0
- package/lib/src/vault/types.js.map +1 -0
- package/lib/src/vault/vault-cache.d.ts +20 -0
- package/lib/src/vault/vault-cache.d.ts.map +1 -0
- package/lib/src/vault/vault-cache.js +139 -0
- package/lib/src/vault/vault-cache.js.map +1 -0
- package/lib/src/vault/vault-integration.d.ts +27 -0
- package/lib/src/vault/vault-integration.d.ts.map +1 -0
- package/lib/src/vault/vault-integration.js +211 -0
- package/lib/src/vault/vault-integration.js.map +1 -0
- package/lib/src/vault/vault-provider.d.ts +37 -0
- package/lib/src/vault/vault-provider.d.ts.map +1 -0
- package/lib/src/vault/vault-provider.js +354 -0
- package/lib/src/vault/vault-provider.js.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -65
- package/src/config.service.ts +155 -10
- package/src/config.service.vault.spec.ts +859 -0
- package/src/index.ts +1 -0
- package/src/vault/__tests__/vault-integration.test.ts +226 -0
- package/src/vault/decorators.ts +228 -0
- package/src/vault/index.ts +31 -0
- package/src/vault/secret-refresh-manager.ts +241 -0
- package/src/vault/types.ts +487 -0
- package/src/vault/vault-cache.ts +240 -0
- package/src/vault/vault-integration.ts +332 -0
- package/src/vault/vault-provider.ts +576 -0
- package/lib/config.errors.d.ts.map +0 -1
- package/lib/config.errors.js.map +0 -1
- package/lib/config.model.d.ts.map +0 -1
- package/lib/config.model.js.map +0 -1
- package/lib/config.service.d.ts.map +0 -1
- package/lib/config.service.js.map +0 -1
- package/lib/environment.service.d.ts.map +0 -1
- package/lib/environment.service.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/json-schema.validator.d.ts.map +0 -1
- package/lib/json-schema.validator.js.map +0 -1
- /package/lib/{config.errors.d.ts → src/config.errors.d.ts} +0 -0
- /package/lib/{config.errors.js → src/config.errors.js} +0 -0
- /package/lib/{config.model.d.ts → src/config.model.d.ts} +0 -0
- /package/lib/{config.model.js → src/config.model.js} +0 -0
- /package/lib/{environment.service.d.ts → src/environment.service.d.ts} +0 -0
- /package/lib/{environment.service.js → src/environment.service.js} +0 -0
- /package/lib/{json-schema.validator.d.ts → src/json-schema.validator.d.ts} +0 -0
- /package/lib/{json-schema.validator.js → src/json-schema.validator.js} +0 -0
package/src/index.ts
CHANGED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault Integration Tests
|
|
3
|
+
* These tests require a local Vault instance and are skipped in CI.
|
|
4
|
+
* To run locally:
|
|
5
|
+
* docker compose -f docker-compose.vault.yml up -d
|
|
6
|
+
* bash scripts/vault-setup.sh
|
|
7
|
+
* VAULT_INTEGRATION_TESTS=true npm test
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { IsString } from 'class-validator';
|
|
11
|
+
|
|
12
|
+
import { VaultKey, VaultPath } from '../decorators';
|
|
13
|
+
import { IVaultConfigOptions } from '../types';
|
|
14
|
+
import { VaultIntegration } from '../vault-integration';
|
|
15
|
+
|
|
16
|
+
import 'reflect-metadata';
|
|
17
|
+
|
|
18
|
+
// Skip these tests unless explicitly enabled (requires running Vault)
|
|
19
|
+
const SKIP_VAULT_TESTS = !process.env.VAULT_INTEGRATION_TESTS;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Test configuration class with Vault secrets
|
|
23
|
+
* Note: VaultPath should use logical paths (without engine-specific prefix)
|
|
24
|
+
* The engine prefix (secret/data/ for kv-v2) is added automatically
|
|
25
|
+
*/
|
|
26
|
+
class TestVaultConfig {
|
|
27
|
+
@VaultPath('configit/api')
|
|
28
|
+
@VaultKey('api_key')
|
|
29
|
+
@IsString()
|
|
30
|
+
API_KEY!: string;
|
|
31
|
+
|
|
32
|
+
@VaultPath('configit/api')
|
|
33
|
+
@VaultKey('api_secret')
|
|
34
|
+
@IsString()
|
|
35
|
+
API_SECRET!: string;
|
|
36
|
+
|
|
37
|
+
@VaultPath('configit/database')
|
|
38
|
+
@VaultKey('host')
|
|
39
|
+
@IsString()
|
|
40
|
+
DB_HOST!: string;
|
|
41
|
+
|
|
42
|
+
@VaultPath('configit/database')
|
|
43
|
+
@VaultKey('port')
|
|
44
|
+
@IsString()
|
|
45
|
+
DB_PORT!: string;
|
|
46
|
+
|
|
47
|
+
@VaultPath('configit/database')
|
|
48
|
+
@VaultKey('username')
|
|
49
|
+
@IsString()
|
|
50
|
+
DB_USERNAME!: string;
|
|
51
|
+
|
|
52
|
+
@VaultPath('configit/database')
|
|
53
|
+
@VaultKey('password')
|
|
54
|
+
@IsString()
|
|
55
|
+
DB_PASSWORD!: string;
|
|
56
|
+
|
|
57
|
+
@VaultPath('configit/features')
|
|
58
|
+
@VaultKey('enable_beta')
|
|
59
|
+
@IsString()
|
|
60
|
+
ENABLE_BETA!: string;
|
|
61
|
+
|
|
62
|
+
@VaultPath('configit/features')
|
|
63
|
+
@VaultKey('max_connections')
|
|
64
|
+
@IsString()
|
|
65
|
+
MAX_CONNECTIONS!: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const VAULT_CONFIG: IVaultConfigOptions = {
|
|
69
|
+
endpoint: 'http://127.0.0.1:8200',
|
|
70
|
+
auth: {
|
|
71
|
+
methods: [
|
|
72
|
+
{
|
|
73
|
+
type: 'token',
|
|
74
|
+
config: {
|
|
75
|
+
token: process.env.VAULT_TOKEN || 'configit-dev-token'
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
tls: {
|
|
81
|
+
enabled: false,
|
|
82
|
+
verifyCertificate: false
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Use describe.skip when Vault is not available
|
|
87
|
+
const describeVault = SKIP_VAULT_TESTS ? describe.skip : describe;
|
|
88
|
+
|
|
89
|
+
describeVault('VaultIntegration (requires running Vault)', () => {
|
|
90
|
+
let vaultIntegration: VaultIntegration;
|
|
91
|
+
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
if (vaultIntegration) {
|
|
94
|
+
vaultIntegration.shutdown();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('when Vault is available', () => {
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
vaultIntegration = new VaultIntegration(VAULT_CONFIG);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should initialize successfully', async () => {
|
|
104
|
+
await vaultIntegration.initialize();
|
|
105
|
+
expect(vaultIntegration.isInitialized()).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should load secrets for a config class', async () => {
|
|
109
|
+
await vaultIntegration.initialize();
|
|
110
|
+
await vaultIntegration.loadSecrets(TestVaultConfig);
|
|
111
|
+
|
|
112
|
+
// Verify secrets are cached
|
|
113
|
+
expect(vaultIntegration.getSecret('API_KEY')).toBe('test-api-key-123');
|
|
114
|
+
expect(vaultIntegration.getSecret('API_SECRET')).toBe('test-api-secret-xyz');
|
|
115
|
+
expect(vaultIntegration.getSecret('DB_HOST')).toBe('localhost');
|
|
116
|
+
expect(vaultIntegration.getSecret('DB_PORT')).toBe('5432');
|
|
117
|
+
expect(vaultIntegration.getSecret('DB_USERNAME')).toBe('testuser');
|
|
118
|
+
expect(vaultIntegration.getSecret('DB_PASSWORD')).toBe('testpassword');
|
|
119
|
+
expect(vaultIntegration.getSecret('ENABLE_BETA')).toBe('true');
|
|
120
|
+
expect(vaultIntegration.getSecret('MAX_CONNECTIONS')).toBe('100');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should report healthy status after initialization', async () => {
|
|
124
|
+
await vaultIntegration.initialize();
|
|
125
|
+
await vaultIntegration.loadSecrets(TestVaultConfig);
|
|
126
|
+
|
|
127
|
+
const health = vaultIntegration.getHealth();
|
|
128
|
+
expect(health.connected).toBe(true);
|
|
129
|
+
expect(health.authenticated).toBe(true);
|
|
130
|
+
expect(health.cacheSize).toBeGreaterThan(0);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should invalidate cache entries', async () => {
|
|
134
|
+
await vaultIntegration.initialize();
|
|
135
|
+
await vaultIntegration.loadSecrets(TestVaultConfig);
|
|
136
|
+
|
|
137
|
+
// Verify secret is cached
|
|
138
|
+
expect(vaultIntegration.getSecret('API_KEY')).toBe('test-api-key-123');
|
|
139
|
+
|
|
140
|
+
// Invalidate
|
|
141
|
+
vaultIntegration.invalidateProperty('API_KEY');
|
|
142
|
+
|
|
143
|
+
// Should be null after invalidation
|
|
144
|
+
expect(vaultIntegration.getSecret('API_KEY')).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('error handling', () => {
|
|
149
|
+
it('should throw error when not initialized', async () => {
|
|
150
|
+
vaultIntegration = new VaultIntegration(VAULT_CONFIG);
|
|
151
|
+
|
|
152
|
+
await expect(vaultIntegration.loadSecrets(TestVaultConfig))
|
|
153
|
+
.rejects
|
|
154
|
+
.toThrow('VaultIntegration not initialized');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should throw error with invalid token', async () => {
|
|
158
|
+
const invalidConfig: IVaultConfigOptions = {
|
|
159
|
+
...VAULT_CONFIG,
|
|
160
|
+
auth: {
|
|
161
|
+
methods: [
|
|
162
|
+
{
|
|
163
|
+
type: 'token',
|
|
164
|
+
config: {
|
|
165
|
+
token: 'invalid-token'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
vaultIntegration = new VaultIntegration(invalidConfig);
|
|
173
|
+
|
|
174
|
+
await expect(vaultIntegration.initialize())
|
|
175
|
+
.rejects
|
|
176
|
+
.toThrow(/authentication.*failed/i);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Run this file directly to test Vault integration:
|
|
183
|
+
* npx ts-node src/vault/__tests__/vault-integration.test.ts
|
|
184
|
+
*/
|
|
185
|
+
if (require.main === module) {
|
|
186
|
+
(async () => {
|
|
187
|
+
console.log('Testing Vault Integration...\n');
|
|
188
|
+
|
|
189
|
+
const integration = new VaultIntegration(VAULT_CONFIG);
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
console.log('1. Initializing...');
|
|
193
|
+
await integration.initialize();
|
|
194
|
+
console.log(' ✓ Initialized\n');
|
|
195
|
+
|
|
196
|
+
console.log('2. Loading secrets...');
|
|
197
|
+
await integration.loadSecrets(TestVaultConfig);
|
|
198
|
+
console.log(' ✓ Secrets loaded\n');
|
|
199
|
+
|
|
200
|
+
console.log('3. Reading secrets:');
|
|
201
|
+
console.log(` API_KEY: ${ integration.getSecret('API_KEY') }`);
|
|
202
|
+
console.log(` API_SECRET: ${ integration.getSecret('API_SECRET') }`);
|
|
203
|
+
console.log(` DB_HOST: ${ integration.getSecret('DB_HOST') }`);
|
|
204
|
+
console.log(` DB_PORT: ${ integration.getSecret('DB_PORT') }`);
|
|
205
|
+
console.log(` DB_USERNAME: ${ integration.getSecret('DB_USERNAME') }`);
|
|
206
|
+
console.log(` DB_PASSWORD: ${ integration.getSecret('DB_PASSWORD') }`);
|
|
207
|
+
console.log(` ENABLE_BETA: ${ integration.getSecret('ENABLE_BETA') }`);
|
|
208
|
+
console.log(` MAX_CONNECTIONS: ${ integration.getSecret('MAX_CONNECTIONS') }\n`);
|
|
209
|
+
|
|
210
|
+
console.log('4. Health status:');
|
|
211
|
+
const health = integration.getHealth();
|
|
212
|
+
console.log(` Connected: ${ health.connected }`);
|
|
213
|
+
console.log(` Authenticated: ${ health.authenticated }`);
|
|
214
|
+
console.log(` Cache size: ${ health.cacheSize }`);
|
|
215
|
+
console.log(` Errors: ${ health.errors.length }\n`);
|
|
216
|
+
|
|
217
|
+
console.log('✓ All tests passed!\n');
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('✗ Test failed:', error);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
} finally {
|
|
222
|
+
integration.shutdown();
|
|
223
|
+
}
|
|
224
|
+
})();
|
|
225
|
+
}
|
|
226
|
+
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault Decorators
|
|
3
|
+
* Composable decorators for marking configuration properties as Vault secrets
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { kebabCase } from 'lodash';
|
|
7
|
+
|
|
8
|
+
import { VaultEngineType, VaultPropertyMetadata } from './types';
|
|
9
|
+
|
|
10
|
+
import 'reflect-metadata';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Symbols for storing individual decorator metadata
|
|
14
|
+
*/
|
|
15
|
+
const VAULT_PATH_SYMBOL = Symbol('configit:vault:path');
|
|
16
|
+
const VAULT_ENGINE_SYMBOL = Symbol('configit:vault:engine');
|
|
17
|
+
const VAULT_KEY_SYMBOL = Symbol('configit:vault:key');
|
|
18
|
+
const VAULT_REFRESH_BUFFER_SYMBOL = Symbol('configit:vault:refreshBuffer');
|
|
19
|
+
const VAULT_OPTIONAL_SYMBOL = Symbol('configit:vault:optional');
|
|
20
|
+
const VAULT_PROPERTIES_SYMBOL = Symbol('configit:vault:properties');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register a property as having Vault metadata (for discovery)
|
|
24
|
+
*/
|
|
25
|
+
function registerVaultProperty(target: unknown, propertyKey: string): void {
|
|
26
|
+
const constructor = (target as object).constructor;
|
|
27
|
+
const existingProperties: string[] = Reflect.getMetadata(VAULT_PROPERTIES_SYMBOL, constructor) || [];
|
|
28
|
+
if (!existingProperties.includes(propertyKey)) {
|
|
29
|
+
Reflect.defineMetadata(VAULT_PROPERTIES_SYMBOL, [ ...existingProperties, propertyKey ], constructor);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get all registered Vault property names for a class
|
|
35
|
+
*/
|
|
36
|
+
export function getVaultPropertyNames(target: unknown): string[] {
|
|
37
|
+
const constructor = typeof target === 'function' ? target : (target as object).constructor;
|
|
38
|
+
return Reflect.getMetadata(VAULT_PROPERTIES_SYMBOL, constructor) || [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Specifies the Vault path for a property (required for Vault secrets)
|
|
43
|
+
* @param path Full Vault path (e.g., 'secret/data/myapp/db_password' or 'database/creds/my-role')
|
|
44
|
+
*/
|
|
45
|
+
export function VaultPath(path: string): PropertyDecorator {
|
|
46
|
+
return function(target: unknown, propertyKey: string | symbol) {
|
|
47
|
+
const key = String(propertyKey);
|
|
48
|
+
Reflect.defineMetadata(VAULT_PATH_SYMBOL, path, target, key);
|
|
49
|
+
// Register this property for discovery
|
|
50
|
+
registerVaultProperty(target, key);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Specifies the Vault secrets engine type (optional, auto-detected from path)
|
|
56
|
+
* @param engine Engine type
|
|
57
|
+
*/
|
|
58
|
+
export function VaultEngine(engine: VaultEngineType): PropertyDecorator {
|
|
59
|
+
return function(target: unknown, propertyKey: string | symbol) {
|
|
60
|
+
const key = String(propertyKey);
|
|
61
|
+
Reflect.defineMetadata(VAULT_ENGINE_SYMBOL, engine, target, key);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Specifies the key name within the secret (optional, defaults to property name in kebab-case)
|
|
67
|
+
* Only used for KV v1/v2 engines
|
|
68
|
+
* @param key Key name
|
|
69
|
+
*/
|
|
70
|
+
export function VaultKey(key: string): PropertyDecorator {
|
|
71
|
+
return function(target: unknown, propertyKey: string | symbol) {
|
|
72
|
+
const keyName = String(propertyKey);
|
|
73
|
+
Reflect.defineMetadata(VAULT_KEY_SYMBOL, key, target, keyName);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Override default refresh buffer (optional)
|
|
79
|
+
* @param seconds Refresh buffer in seconds (default: 300s or 10% of TTL, whichever is smaller)
|
|
80
|
+
*/
|
|
81
|
+
export function VaultRefreshBuffer(seconds: number): PropertyDecorator {
|
|
82
|
+
return function(target: unknown, propertyKey: string | symbol) {
|
|
83
|
+
const key = String(propertyKey);
|
|
84
|
+
Reflect.defineMetadata(VAULT_REFRESH_BUFFER_SYMBOL, seconds, target, key);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Mark secret as optional - fallback to environment variable if Vault unavailable
|
|
90
|
+
* Without this decorator, Vault secrets are required by default
|
|
91
|
+
*/
|
|
92
|
+
export function VaultOptional(): PropertyDecorator {
|
|
93
|
+
return function(target: unknown, propertyKey: string | symbol) {
|
|
94
|
+
const key = String(propertyKey);
|
|
95
|
+
Reflect.defineMetadata(VAULT_OPTIONAL_SYMBOL, true, target, key);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get Vault path for a property
|
|
101
|
+
*/
|
|
102
|
+
export function getVaultPath(target: any, propertyKey: string): string | undefined {
|
|
103
|
+
return Reflect.getMetadata(VAULT_PATH_SYMBOL, target, propertyKey);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get Vault engine for a property
|
|
108
|
+
*/
|
|
109
|
+
export function getVaultEngine(target: any, propertyKey: string): VaultEngineType | undefined {
|
|
110
|
+
return Reflect.getMetadata(VAULT_ENGINE_SYMBOL, target, propertyKey);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get Vault key for a property
|
|
115
|
+
*/
|
|
116
|
+
export function getVaultKey(target: any, propertyKey: string): string | undefined {
|
|
117
|
+
return Reflect.getMetadata(VAULT_KEY_SYMBOL, target, propertyKey);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get Vault refresh buffer for a property
|
|
122
|
+
*/
|
|
123
|
+
export function getVaultRefreshBuffer(target: any, propertyKey: string): number | undefined {
|
|
124
|
+
return Reflect.getMetadata(VAULT_REFRESH_BUFFER_SYMBOL, target, propertyKey);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if Vault secret is optional for a property
|
|
129
|
+
*/
|
|
130
|
+
export function isVaultOptional(target: any, propertyKey: string): boolean {
|
|
131
|
+
return Reflect.getMetadata(VAULT_OPTIONAL_SYMBOL, target, propertyKey) === true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Detect engine type from Vault path
|
|
136
|
+
*/
|
|
137
|
+
export function detectEngineFromPath(path: string): VaultEngineType {
|
|
138
|
+
// Common path patterns
|
|
139
|
+
if (path.startsWith('secret/data/')) {
|
|
140
|
+
return 'kv-v2';
|
|
141
|
+
}
|
|
142
|
+
if (path.startsWith('secret/') && !path.includes('/data/')) {
|
|
143
|
+
return 'kv-v1';
|
|
144
|
+
}
|
|
145
|
+
if (path.startsWith('database/creds/')) {
|
|
146
|
+
return 'database';
|
|
147
|
+
}
|
|
148
|
+
if (path.startsWith('aws/creds/')) {
|
|
149
|
+
return 'aws';
|
|
150
|
+
}
|
|
151
|
+
if (path.startsWith('azure/creds/')) {
|
|
152
|
+
return 'azure';
|
|
153
|
+
}
|
|
154
|
+
if (path.startsWith('gcp/')) {
|
|
155
|
+
return 'gcp';
|
|
156
|
+
}
|
|
157
|
+
if (path.startsWith('transit/')) {
|
|
158
|
+
return 'transit';
|
|
159
|
+
}
|
|
160
|
+
if (path.startsWith('pki/')) {
|
|
161
|
+
return 'pki';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Default to kv-v2 (most common)
|
|
165
|
+
return 'kv-v2';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get property type from metadata (basic implementation)
|
|
170
|
+
*/
|
|
171
|
+
function getPropertyType(target: any, propertyKey: string): string {
|
|
172
|
+
// Try to get type from reflect-metadata
|
|
173
|
+
const type = Reflect.getMetadata('design:type', target, propertyKey);
|
|
174
|
+
if (type) {
|
|
175
|
+
return type.name || 'unknown';
|
|
176
|
+
}
|
|
177
|
+
return 'unknown';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Build complete Vault metadata for a property from individual decorators
|
|
182
|
+
*/
|
|
183
|
+
export function buildVaultMetadata(
|
|
184
|
+
target: any,
|
|
185
|
+
propertyKey: string
|
|
186
|
+
): VaultPropertyMetadata | undefined {
|
|
187
|
+
const path = getVaultPath(target, propertyKey);
|
|
188
|
+
if (!path) {
|
|
189
|
+
return undefined; // Not a Vault property
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const engine = getVaultEngine(target, propertyKey) || detectEngineFromPath(path);
|
|
193
|
+
const key = getVaultKey(target, propertyKey) || kebabCase(propertyKey);
|
|
194
|
+
const refreshBuffer = getVaultRefreshBuffer(target, propertyKey);
|
|
195
|
+
const optional = isVaultOptional(target, propertyKey);
|
|
196
|
+
const propertyType = getPropertyType(target, propertyKey);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
path,
|
|
200
|
+
engine,
|
|
201
|
+
key,
|
|
202
|
+
refreshBuffer,
|
|
203
|
+
required: !optional,
|
|
204
|
+
propertyName: propertyKey,
|
|
205
|
+
propertyType
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get all Vault metadata for a class
|
|
211
|
+
*/
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
|
+
export function getAllVaultMetadata(target: any): Record<string, VaultPropertyMetadata> {
|
|
214
|
+
const metadata: Record<string, VaultPropertyMetadata> = {};
|
|
215
|
+
|
|
216
|
+
// Get registered Vault properties
|
|
217
|
+
const propertyKeys = getVaultPropertyNames(target);
|
|
218
|
+
const prototype = target.prototype || target;
|
|
219
|
+
|
|
220
|
+
for (const key of propertyKeys) {
|
|
221
|
+
const vaultMetadata = buildVaultMetadata(prototype, key);
|
|
222
|
+
if (vaultMetadata) {
|
|
223
|
+
metadata[key] = vaultMetadata;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return metadata;
|
|
228
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault Integration Module
|
|
3
|
+
* Exports for HashiCorp Vault integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Types
|
|
7
|
+
export * from './types';
|
|
8
|
+
|
|
9
|
+
// Decorators
|
|
10
|
+
export {
|
|
11
|
+
VaultPath,
|
|
12
|
+
VaultEngine,
|
|
13
|
+
VaultKey,
|
|
14
|
+
VaultRefreshBuffer,
|
|
15
|
+
VaultOptional,
|
|
16
|
+
getVaultPath,
|
|
17
|
+
getVaultEngine,
|
|
18
|
+
getVaultKey,
|
|
19
|
+
getVaultRefreshBuffer,
|
|
20
|
+
isVaultOptional,
|
|
21
|
+
detectEngineFromPath,
|
|
22
|
+
buildVaultMetadata,
|
|
23
|
+
getAllVaultMetadata,
|
|
24
|
+
getVaultPropertyNames
|
|
25
|
+
} from './decorators';
|
|
26
|
+
|
|
27
|
+
// Components
|
|
28
|
+
export { VaultProvider } from './vault-provider';
|
|
29
|
+
export { VaultCache } from './vault-cache';
|
|
30
|
+
export { SecretRefreshManager } from './secret-refresh-manager';
|
|
31
|
+
export { VaultIntegration } from './vault-integration';
|