@rytass/secret-adapter-vault-nestjs 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +466 -14
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,30 +1,482 @@
1
- # Rytass Utils - Secret Manager Nestjs Module (Vault)
1
+ # Rytass Utils - Secret Adapter Vault NestJS
2
2
 
3
- ### Getting Started
3
+ NestJS module for HashiCorp Vault integration, providing secure secret management with automatic fallback to environment variables. Seamlessly integrates with NestJS dependency injection and configuration system.
4
4
 
5
- This module will get vault config from env with @nestjs/common, please set following env before use.
5
+ ## Features
6
6
 
7
- - VAULT_HOST (Vault service base url)
8
- - VAULT_ACCOUNT
9
- - VAULT_PASSWORD
10
- - VAULT_PATH (Vault secret path from root)
7
+ - [x] HashiCorp Vault integration for NestJS
8
+ - [x] Automatic fallback to environment variables
9
+ - [x] Global module support
10
+ - [x] TypeScript support with generics
11
+ - [x] Asynchronous secret retrieval
12
+ - [x] Secret writing and deletion
13
+ - [x] Online/offline sync support
14
+ - [x] Connection state management
15
+ - [x] Error handling with graceful degradation
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @rytass/secret-adapter-vault-nestjs @rytass/secret-adapter-vault
21
+ # or
22
+ yarn add @rytass/secret-adapter-vault-nestjs @rytass/secret-adapter-vault
23
+ ```
24
+
25
+ ## Environment Configuration
26
+
27
+ Configure the following environment variables for Vault connection:
28
+
29
+ ```bash
30
+ # Required for Vault connection
31
+ VAULT_HOST=https://vault.example.com:8200 # Vault service base URL
32
+ VAULT_ACCOUNT=your-username # Vault username
33
+ VAULT_PASSWORD=your-password # Vault password
34
+
35
+ # Optional - defaults to root path if not specified
36
+ VAULT_PATH=/secret/data/myapp # Vault secret path from root
37
+ ```
38
+
39
+ ## Basic Usage
40
+
41
+ ### Module Setup
11
42
 
12
43
  ```typescript
13
44
  import { Module } from '@nestjs/common';
14
- import { VaultService, VaultModule } from '@rytass/secret-adapter-vault-nestjs';
45
+ import { VaultModule } from '@rytass/secret-adapter-vault-nestjs';
46
+
47
+ @Module({
48
+ imports: [
49
+ VaultModule.forRoot({
50
+ path: '/secret/data/myapp', // Vault path for secrets
51
+ fallbackFile: '.env', // Optional: fallback env file
52
+ })
53
+ ],
54
+ })
55
+ export class AppModule {}
56
+ ```
57
+
58
+ ### Global Module Configuration
59
+
60
+ Make the VaultService available throughout your application:
61
+
62
+ ```typescript
63
+ import { Module } from '@nestjs/common';
64
+ import { VaultModule } from '@rytass/secret-adapter-vault-nestjs';
65
+
66
+ @Module({
67
+ imports: [
68
+ VaultModule.forRoot({
69
+ path: '/secret/data/myapp',
70
+ fallbackFile: '.env'
71
+ })
72
+ ],
73
+ })
74
+ export class AppModule {}
75
+ ```
76
+
77
+ ### Using VaultService
78
+
79
+ ```typescript
80
+ import { Injectable } from '@nestjs/common';
81
+ import { VaultService } from '@rytass/secret-adapter-vault-nestjs';
15
82
 
16
83
  @Injectable()
17
- class TestService {
84
+ export class ConfigurationService {
18
85
  constructor(private readonly vault: VaultService) {}
19
86
 
20
- async getValue() {
21
- return vault.get<string>('key');
87
+ async getDatabaseConfig() {
88
+ const host = await this.vault.get<string>('DB_HOST');
89
+ const port = await this.vault.get<number>('DB_PORT');
90
+ const username = await this.vault.get<string>('DB_USERNAME');
91
+ const password = await this.vault.get<string>('DB_PASSWORD');
92
+
93
+ return {
94
+ host,
95
+ port,
96
+ username,
97
+ password
98
+ };
99
+ }
100
+
101
+ async getApiKey(): Promise<string> {
102
+ return this.vault.get<string>('API_KEY');
103
+ }
104
+
105
+ async updateApiKey(newKey: string): Promise<void> {
106
+ // Update locally and sync to Vault
107
+ await this.vault.set('API_KEY', newKey, true);
108
+ }
109
+ }
110
+ ```
111
+
112
+ ## Advanced Usage
113
+
114
+ ### Type-Safe Secret Retrieval
115
+
116
+ ```typescript
117
+ interface DatabaseConfig {
118
+ host: string;
119
+ port: number;
120
+ username: string;
121
+ password: string;
122
+ ssl: boolean;
123
+ }
124
+
125
+ @Injectable()
126
+ export class DatabaseService {
127
+ constructor(private readonly vault: VaultService) {}
128
+
129
+ async getConfig(): Promise<DatabaseConfig> {
130
+ // Retrieve complex objects
131
+ return this.vault.get<DatabaseConfig>('database');
132
+ }
133
+
134
+ async getConnectionString(): Promise<string> {
135
+ const config = await this.getConfig();
136
+ return `postgresql://${config.username}:${config.password}@${config.host}:${config.port}/mydb`;
22
137
  }
23
138
  }
139
+ ```
140
+
141
+ ### Managing Secrets
142
+
143
+ ```typescript
144
+ @Injectable()
145
+ export class SecretManagementService {
146
+ constructor(private readonly vault: VaultService) {}
147
+
148
+ // Create or update a secret
149
+ async createSecret(key: string, value: any): Promise<void> {
150
+ // Save locally only
151
+ await this.vault.set(key, value, false);
152
+ }
153
+
154
+ // Create and sync to Vault immediately
155
+ async createAndSyncSecret(key: string, value: any): Promise<void> {
156
+ await this.vault.set(key, value, true);
157
+ }
158
+
159
+ // Delete a secret
160
+ async removeSecret(key: string): Promise<void> {
161
+ // Delete locally only
162
+ await this.vault.delete(key, false);
163
+ }
164
+
165
+ // Delete and sync removal to Vault
166
+ async removeAndSyncSecret(key: string): Promise<void> {
167
+ await this.vault.delete(key, true);
168
+ }
169
+ }
170
+ ```
171
+
172
+ ### Fallback Mechanism
173
+
174
+ When Vault is unavailable, the service automatically falls back to environment variables:
175
+
176
+ ```typescript
177
+ @Injectable()
178
+ export class ResilientConfigService {
179
+ constructor(private readonly vault: VaultService) {}
180
+
181
+ async getConfig() {
182
+ // If Vault is unavailable, this will read from process.env
183
+ const apiUrl = await this.vault.get<string>('API_URL');
184
+ const apiKey = await this.vault.get<string>('API_KEY');
185
+
186
+ return {
187
+ apiUrl,
188
+ apiKey
189
+ };
190
+ }
191
+ }
192
+ ```
193
+
194
+ ### Environment File Configuration
195
+
196
+ Specify a fallback environment file for when Vault is unavailable:
197
+
198
+ ```typescript
199
+ // app.module.ts
200
+ @Module({
201
+ imports: [
202
+ VaultModule.forRoot({
203
+ path: '/secret/data/production',
204
+ fallbackFile: '.env.production' // Fallback to .env.production file
205
+ })
206
+ ],
207
+ })
208
+ export class AppModule {}
209
+ ```
210
+
211
+ ## Integration Examples
212
+
213
+ ### With TypeORM
214
+
215
+ ```typescript
216
+ import { Module } from '@nestjs/common';
217
+ import { TypeOrmModule } from '@nestjs/typeorm';
218
+ import { VaultModule, VaultService } from '@rytass/secret-adapter-vault-nestjs';
24
219
 
25
220
  @Module({
26
- imports: [VaultModule.forRoot({ path: '/', isGlobal: true })],
27
- providers: [TestService],
221
+ imports: [
222
+ VaultModule.forRoot({
223
+ path: '/secret/data/database'
224
+ }),
225
+ TypeOrmModule.forRootAsync({
226
+ imports: [VaultModule],
227
+ inject: [VaultService],
228
+ useFactory: async (vault: VaultService) => ({
229
+ type: 'postgres',
230
+ host: await vault.get<string>('DB_HOST'),
231
+ port: await vault.get<number>('DB_PORT'),
232
+ username: await vault.get<string>('DB_USERNAME'),
233
+ password: await vault.get<string>('DB_PASSWORD'),
234
+ database: await vault.get<string>('DB_NAME'),
235
+ synchronize: false,
236
+ logging: true,
237
+ })
238
+ })
239
+ ],
28
240
  })
29
- class TestModule {};
241
+ export class DatabaseModule {}
30
242
  ```
243
+
244
+ ### With JWT Module
245
+
246
+ ```typescript
247
+ import { Module } from '@nestjs/common';
248
+ import { JwtModule } from '@nestjs/jwt';
249
+ import { VaultModule, VaultService } from '@rytass/secret-adapter-vault-nestjs';
250
+
251
+ @Module({
252
+ imports: [
253
+ VaultModule.forRoot({
254
+ path: '/secret/data/auth'
255
+ }),
256
+ JwtModule.registerAsync({
257
+ imports: [VaultModule],
258
+ inject: [VaultService],
259
+ useFactory: async (vault: VaultService) => ({
260
+ secret: await vault.get<string>('JWT_SECRET'),
261
+ signOptions: {
262
+ expiresIn: await vault.get<string>('JWT_EXPIRY') || '1h'
263
+ }
264
+ })
265
+ })
266
+ ],
267
+ })
268
+ export class AuthModule {}
269
+ ```
270
+
271
+ ### With Microservices
272
+
273
+ ```typescript
274
+ import { NestFactory } from '@nestjs/core';
275
+ import { Transport } from '@nestjs/microservices';
276
+ import { VaultService } from '@rytass/secret-adapter-vault-nestjs';
277
+
278
+ async function bootstrap() {
279
+ const app = await NestFactory.create(AppModule);
280
+ const vault = app.get(VaultService);
281
+
282
+ const microservice = await NestFactory.createMicroservice({
283
+ transport: Transport.REDIS,
284
+ options: {
285
+ host: await vault.get<string>('REDIS_HOST'),
286
+ port: await vault.get<number>('REDIS_PORT'),
287
+ password: await vault.get<string>('REDIS_PASSWORD'),
288
+ }
289
+ });
290
+
291
+ await app.startAllMicroservices();
292
+ await app.listen(3000);
293
+ }
294
+ ```
295
+
296
+ ## Error Handling
297
+
298
+ ```typescript
299
+ @Injectable()
300
+ export class SafeConfigService {
301
+ constructor(private readonly vault: VaultService) {}
302
+
303
+ async getSensitiveConfig() {
304
+ try {
305
+ const secret = await this.vault.get<string>('SENSITIVE_KEY');
306
+
307
+ if (!secret) {
308
+ throw new Error('Sensitive key not found');
309
+ }
310
+
311
+ return secret;
312
+ } catch (error) {
313
+ // When Vault is down, it falls back to env vars automatically
314
+ console.error('Failed to retrieve secret:', error);
315
+
316
+ // You can implement additional fallback logic
317
+ return process.env.FALLBACK_SENSITIVE_KEY || 'default-value';
318
+ }
319
+ }
320
+
321
+ async trySaveSecret(key: string, value: string): Promise<boolean> {
322
+ try {
323
+ await this.vault.set(key, value, true);
324
+ return true;
325
+ } catch (error) {
326
+ // Cannot save when in fallback mode
327
+ console.error('Failed to save secret:', error.message);
328
+ return false;
329
+ }
330
+ }
331
+ }
332
+ ```
333
+
334
+ ## Best Practices
335
+
336
+ ### 1. Always Use Type Parameters
337
+
338
+ ```typescript
339
+ // Good - Type-safe
340
+ const port = await vault.get<number>('PORT');
341
+ const config = await vault.get<DatabaseConfig>('db_config');
342
+
343
+ // Avoid - Returns any
344
+ const value = await vault.get('SOME_KEY');
345
+ ```
346
+
347
+ ### 2. Handle Fallback Scenarios
348
+
349
+ ```typescript
350
+ @Injectable()
351
+ export class ConfigService {
352
+ constructor(private readonly vault: VaultService) {}
353
+
354
+ async initialize() {
355
+ try {
356
+ // Try to save a test value to check if Vault is writable
357
+ await this.vault.set('health_check', 'ok', true);
358
+ console.log('Vault is connected and writable');
359
+ } catch (error) {
360
+ console.warn('Running in read-only mode with environment variables');
361
+ }
362
+ }
363
+ }
364
+ ```
365
+
366
+ ### 3. Organize Secrets by Path
367
+
368
+ ```typescript
369
+ // auth.module.ts
370
+ VaultModule.forRoot({ path: '/secret/data/auth' })
371
+
372
+ // database.module.ts
373
+ VaultModule.forRoot({ path: '/secret/data/database' })
374
+
375
+ // api.module.ts
376
+ VaultModule.forRoot({ path: '/secret/data/external-apis' })
377
+ ```
378
+
379
+ ### 4. Use Environment-Specific Paths
380
+
381
+ ```typescript
382
+ const environment = process.env.NODE_ENV || 'development';
383
+
384
+ @Module({
385
+ imports: [
386
+ VaultModule.forRoot({
387
+ path: `/secret/data/${environment}`,
388
+ fallbackFile: `.env.${environment}`
389
+ })
390
+ ],
391
+ })
392
+ export class AppModule {}
393
+ ```
394
+
395
+ ## API Reference
396
+
397
+ ### VaultModule
398
+
399
+ #### `forRoot(options: VaultModuleOptions): DynamicModule`
400
+
401
+ Configure the Vault module.
402
+
403
+ **Options:**
404
+ - `path` (string, required): Vault secret path from root
405
+ - `fallbackFile` (string, optional): Path to fallback environment file
406
+
407
+ ### VaultService
408
+
409
+ #### `get<T>(key: string): Promise<T>`
410
+
411
+ Retrieve a secret value.
412
+
413
+ **Parameters:**
414
+ - `key`: Secret key name
415
+
416
+ **Returns:** Promise resolving to the secret value
417
+
418
+ #### `set<T>(key: string, value: T, syncToOnline?: boolean): Promise<void>`
419
+
420
+ Store a secret value.
421
+
422
+ **Parameters:**
423
+ - `key`: Secret key name
424
+ - `value`: Value to store
425
+ - `syncToOnline`: Whether to sync immediately to Vault (default: false)
426
+
427
+ #### `delete(key: string, syncToOnline?: boolean): Promise<void>`
428
+
429
+ Delete a secret.
430
+
431
+ **Parameters:**
432
+ - `key`: Secret key name
433
+ - `syncToOnline`: Whether to sync deletion to Vault (default: false)
434
+
435
+ ## Migration from ConfigService
436
+
437
+ ```typescript
438
+ // Before - Using ConfigService
439
+ @Injectable()
440
+ export class OldService {
441
+ constructor(private config: ConfigService) {}
442
+
443
+ getValue() {
444
+ return this.config.get('MY_KEY');
445
+ }
446
+ }
447
+
448
+ // After - Using VaultService
449
+ @Injectable()
450
+ export class NewService {
451
+ constructor(private vault: VaultService) {}
452
+
453
+ async getValue() {
454
+ return this.vault.get<string>('MY_KEY');
455
+ }
456
+ }
457
+ ```
458
+
459
+ ## Troubleshooting
460
+
461
+ ### Vault Connection Issues
462
+
463
+ If you see fallback warnings:
464
+ 1. Check `VAULT_HOST` is accessible
465
+ 2. Verify `VAULT_ACCOUNT` and `VAULT_PASSWORD` are correct
466
+ 3. Ensure `VAULT_PATH` exists in Vault
467
+ 4. Check network connectivity to Vault server
468
+
469
+ ### Type Safety
470
+
471
+ Always specify type parameters for better TypeScript support:
472
+
473
+ ```typescript
474
+ // Explicit types prevent runtime errors
475
+ const port = await vault.get<number>('PORT');
476
+ const features = await vault.get<string[]>('FEATURE_FLAGS');
477
+ const config = await vault.get<AppConfig>('APP_CONFIG');
478
+ ```
479
+
480
+ ## License
481
+
482
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rytass/secret-adapter-vault-nestjs",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "description": "Rytass Sceret Vault nestjs adapter",
5
5
  "keywords": [
6
6
  "rytass",
@@ -24,7 +24,7 @@
24
24
  "reflect-metadata": "*"
25
25
  },
26
26
  "dependencies": {
27
- "@rytass/secret-adapter-vault": "^0.3.2",
27
+ "@rytass/secret-adapter-vault": "^0.4.1",
28
28
  "regenerator-runtime": "^0.14.1"
29
29
  },
30
30
  "main": "./index.cjs.js",