env-secrets 0.1.10 → 0.3.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/.devcontainer/devcontainer.json +33 -0
- package/.dockerignore +9 -0
- package/.eslintignore +4 -2
- package/.github/dependabot.yml +4 -0
- package/.github/workflows/build-main.yml +6 -2
- package/.github/workflows/deploy-docs.yml +50 -0
- package/.github/workflows/e2e-tests.yaml +54 -0
- package/.github/workflows/lint.yaml +6 -2
- package/.github/workflows/release.yml +13 -3
- package/.github/workflows/snyk.yaml +5 -1
- package/.github/workflows/unittests.yaml +18 -6
- package/.lintstagedrc +2 -7
- package/.prettierignore +6 -0
- package/AGENTS.md +149 -0
- package/Dockerfile +14 -0
- package/README.md +507 -36
- package/__e2e__/README.md +160 -0
- package/__e2e__/index.test.ts +339 -0
- package/__e2e__/setup.ts +58 -0
- package/__e2e__/utils/debug-logger.ts +45 -0
- package/__e2e__/utils/test-utils.ts +645 -0
- package/__tests__/index.test.ts +573 -31
- package/__tests__/vaults/secretsmanager.test.ts +460 -0
- package/__tests__/vaults/utils.test.ts +183 -0
- package/__tests__/version.test.ts +8 -0
- package/dist/index.js +36 -10
- package/dist/vaults/secretsmanager.js +44 -43
- package/dist/vaults/utils.js +2 -2
- package/docker-compose.yaml +29 -0
- package/docs/AWS.md +257 -0
- package/jest.config.js +4 -1
- package/jest.e2e.config.js +8 -0
- package/package.json +18 -10
- package/src/index.ts +44 -10
- package/src/vaults/secretsmanager.ts +48 -48
- package/src/vaults/utils.ts +6 -4
- package/website/docs/advanced-usage.mdx +399 -0
- package/website/docs/best-practices.mdx +416 -0
- package/website/docs/cli-reference.mdx +204 -0
- package/website/docs/examples.mdx +960 -0
- package/website/docs/faq.mdx +302 -0
- package/website/docs/index.mdx +56 -0
- package/website/docs/installation.mdx +30 -0
- package/website/docs/overview.mdx +17 -0
- package/website/docs/production-deployment.mdx +622 -0
- package/website/docs/providers/aws-secrets-manager.mdx +28 -0
- package/website/docs/security.mdx +122 -0
- package/website/docs/troubleshooting.mdx +236 -0
- package/website/docs/tutorials/local-dev/devcontainer-localstack.mdx +31 -0
- package/website/docs/tutorials/local-dev/docker-compose.mdx +22 -0
- package/website/docs/tutorials/local-dev/nextjs.mdx +18 -0
- package/website/docs/tutorials/local-dev/node-python-go.mdx +39 -0
- package/website/docs/tutorials/local-dev/quickstart.mdx +23 -0
- package/website/docusaurus.config.ts +89 -0
- package/website/package.json +21 -0
- package/website/sidebars.ts +33 -0
- package/website/src/css/custom.css +1 -0
- package/website/static/img/env-secrets.png +0 -0
- package/website/static/img/favicon.ico +0 -0
- package/website/static/img/logo.svg +4 -0
- package/website/yarn.lock +8764 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SecretsManagerClient,
|
|
3
|
+
GetSecretValueCommand
|
|
4
|
+
} from '@aws-sdk/client-secrets-manager';
|
|
5
|
+
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
|
6
|
+
import { fromIni } from '@aws-sdk/credential-providers';
|
|
7
|
+
|
|
8
|
+
// Mock the AWS SDK and dependencies
|
|
9
|
+
jest.mock('@aws-sdk/client-secrets-manager');
|
|
10
|
+
jest.mock('@aws-sdk/client-sts');
|
|
11
|
+
jest.mock('@aws-sdk/credential-providers');
|
|
12
|
+
jest.mock('debug', () => {
|
|
13
|
+
const mockDebug = jest.fn();
|
|
14
|
+
mockDebug.mockReturnValue(jest.fn());
|
|
15
|
+
return mockDebug;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Import the module under test after mocking
|
|
19
|
+
import { secretsmanager } from '../../src/vaults/secretsmanager';
|
|
20
|
+
|
|
21
|
+
// Type the mocked functions
|
|
22
|
+
const mockSecretsManagerClient = SecretsManagerClient as jest.MockedClass<
|
|
23
|
+
typeof SecretsManagerClient
|
|
24
|
+
>;
|
|
25
|
+
const mockSTSClient = STSClient as jest.MockedClass<typeof STSClient>;
|
|
26
|
+
const mockFromIni = fromIni as jest.MockedFunction<typeof fromIni>;
|
|
27
|
+
|
|
28
|
+
describe('secretsmanager', () => {
|
|
29
|
+
let originalEnv: NodeJS.ProcessEnv;
|
|
30
|
+
let mockSecretsManagerSend: jest.MockedFunction<any>;
|
|
31
|
+
let mockSTSSend: jest.MockedFunction<any>;
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
// Clear all mocks
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
|
|
37
|
+
// Reset process.env
|
|
38
|
+
originalEnv = { ...process.env };
|
|
39
|
+
process.env = { ...originalEnv };
|
|
40
|
+
|
|
41
|
+
// Setup AWS client mocks
|
|
42
|
+
mockSecretsManagerSend = jest.fn();
|
|
43
|
+
mockSTSSend = jest.fn();
|
|
44
|
+
|
|
45
|
+
mockSecretsManagerClient.prototype.send = mockSecretsManagerSend;
|
|
46
|
+
mockSTSClient.prototype.send = mockSTSSend;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
// Restore process.env
|
|
51
|
+
process.env = originalEnv;
|
|
52
|
+
jest.restoreAllMocks();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('AWS connection', () => {
|
|
56
|
+
it('should successfully connect to AWS and retrieve secrets', async () => {
|
|
57
|
+
// Mock successful STS connection
|
|
58
|
+
mockSTSSend.mockResolvedValueOnce({
|
|
59
|
+
Account: '123456789012',
|
|
60
|
+
UserId: 'test-user'
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Mock successful secret retrieval
|
|
64
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
65
|
+
SecretString:
|
|
66
|
+
'{"API_KEY": "test-key", "DATABASE_URL": "postgres://localhost:5432/db"}'
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const result = await secretsmanager({
|
|
70
|
+
secret: 'my-secret',
|
|
71
|
+
region: 'us-east-1'
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(mockSTSSend).toHaveBeenCalledWith(
|
|
75
|
+
expect.any(GetCallerIdentityCommand)
|
|
76
|
+
);
|
|
77
|
+
expect(mockSecretsManagerSend).toHaveBeenCalledWith(
|
|
78
|
+
expect.any(GetSecretValueCommand)
|
|
79
|
+
);
|
|
80
|
+
expect(result).toEqual({
|
|
81
|
+
API_KEY: 'test-key',
|
|
82
|
+
DATABASE_URL: 'postgres://localhost:5432/db'
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should return empty object when AWS connection fails', async () => {
|
|
87
|
+
// Mock failed STS connection
|
|
88
|
+
mockSTSSend.mockRejectedValueOnce(new Error('AWS credentials not found'));
|
|
89
|
+
|
|
90
|
+
const result = await secretsmanager({
|
|
91
|
+
secret: 'my-secret',
|
|
92
|
+
region: 'us-east-1'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(mockSTSSend).toHaveBeenCalledWith(
|
|
96
|
+
expect.any(GetCallerIdentityCommand)
|
|
97
|
+
);
|
|
98
|
+
expect(mockSecretsManagerSend).not.toHaveBeenCalled();
|
|
99
|
+
expect(result).toEqual({});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('credential handling', () => {
|
|
104
|
+
beforeEach(() => {
|
|
105
|
+
// Setup successful connection for credential tests
|
|
106
|
+
mockSTSSend.mockResolvedValue({
|
|
107
|
+
Account: '123456789012'
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should use profile credentials when profile is provided', async () => {
|
|
112
|
+
const mockCredentials = jest.fn().mockResolvedValue({
|
|
113
|
+
accessKeyId: 'test',
|
|
114
|
+
secretAccessKey: 'test'
|
|
115
|
+
});
|
|
116
|
+
mockFromIni.mockReturnValueOnce(mockCredentials);
|
|
117
|
+
|
|
118
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
119
|
+
SecretString: '{"KEY": "value"}'
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await secretsmanager({
|
|
123
|
+
secret: 'test-secret',
|
|
124
|
+
profile: 'my-profile',
|
|
125
|
+
region: 'us-east-1'
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(mockFromIni).toHaveBeenCalledWith({ profile: 'my-profile' });
|
|
129
|
+
expect(mockSecretsManagerClient).toHaveBeenCalledWith({
|
|
130
|
+
region: 'us-east-1',
|
|
131
|
+
credentials: mockCredentials
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should use environment variables when AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are set', async () => {
|
|
136
|
+
process.env.AWS_ACCESS_KEY_ID = 'env-access-key';
|
|
137
|
+
process.env.AWS_SECRET_ACCESS_KEY = 'env-secret-key';
|
|
138
|
+
|
|
139
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
140
|
+
SecretString: '{"KEY": "value"}'
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await secretsmanager({
|
|
144
|
+
secret: 'test-secret',
|
|
145
|
+
region: 'us-east-1'
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(mockSecretsManagerClient).toHaveBeenCalledWith({
|
|
149
|
+
region: 'us-east-1',
|
|
150
|
+
credentials: undefined
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should use default profile when no profile or environment variables are provided', async () => {
|
|
155
|
+
const mockCredentials = jest.fn().mockResolvedValue({
|
|
156
|
+
accessKeyId: 'default',
|
|
157
|
+
secretAccessKey: 'default'
|
|
158
|
+
});
|
|
159
|
+
mockFromIni.mockReturnValueOnce(mockCredentials);
|
|
160
|
+
|
|
161
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
162
|
+
SecretString: '{"KEY": "value"}'
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await secretsmanager({
|
|
166
|
+
secret: 'test-secret',
|
|
167
|
+
region: 'us-east-1'
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(mockFromIni).toHaveBeenCalledWith({ profile: 'default' });
|
|
171
|
+
expect(mockSecretsManagerClient).toHaveBeenCalledWith({
|
|
172
|
+
region: 'us-east-1',
|
|
173
|
+
credentials: mockCredentials
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('secret retrieval', () => {
|
|
179
|
+
beforeEach(() => {
|
|
180
|
+
// Setup successful connection for secret retrieval tests
|
|
181
|
+
mockSTSSend.mockResolvedValue({
|
|
182
|
+
Account: '123456789012'
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should retrieve and parse JSON secret successfully', async () => {
|
|
187
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
188
|
+
SecretString:
|
|
189
|
+
'{"API_KEY": "test-api-key", "DATABASE_URL": "postgres://localhost:5432/db"}'
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const result = await secretsmanager({
|
|
193
|
+
secret: 'my-secret',
|
|
194
|
+
region: 'us-east-1'
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(result).toEqual({
|
|
198
|
+
API_KEY: 'test-api-key',
|
|
199
|
+
DATABASE_URL: 'postgres://localhost:5432/db'
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should handle non-JSON secret values', async () => {
|
|
204
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
205
|
+
SecretString: 'plain-text-secret'
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const result = await secretsmanager({
|
|
209
|
+
secret: 'my-secret',
|
|
210
|
+
region: 'us-east-1'
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(result).toEqual({});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should handle empty secret values', async () => {
|
|
217
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
218
|
+
SecretString: ''
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const result = await secretsmanager({
|
|
222
|
+
secret: 'my-secret',
|
|
223
|
+
region: 'us-east-1'
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(result).toEqual({});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should handle undefined secret values', async () => {
|
|
230
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
231
|
+
SecretString: undefined
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const result = await secretsmanager({
|
|
235
|
+
secret: 'my-secret',
|
|
236
|
+
region: 'us-east-1'
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
expect(result).toEqual({});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should handle malformed JSON in secret values', async () => {
|
|
243
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
244
|
+
SecretString: '{"invalid": json}'
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const result = await secretsmanager({
|
|
248
|
+
secret: 'my-secret',
|
|
249
|
+
region: 'us-east-1'
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
expect(result).toEqual({});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should handle secrets with special characters', async () => {
|
|
256
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
257
|
+
SecretString: '{"SPECIAL_KEY": "value with spaces & symbols!@#$%"}'
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const result = await secretsmanager({
|
|
261
|
+
secret: 'my-secret',
|
|
262
|
+
region: 'us-east-1'
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(result).toEqual({
|
|
266
|
+
SPECIAL_KEY: 'value with spaces & symbols!@#$%'
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should handle secrets with numeric and boolean values', async () => {
|
|
271
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
272
|
+
SecretString: '{"PORT": 5432, "DEBUG": true, "TIMEOUT": 30000}'
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const result = await secretsmanager({
|
|
276
|
+
secret: 'my-secret',
|
|
277
|
+
region: 'us-east-1'
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
expect(result).toEqual({
|
|
281
|
+
PORT: 5432,
|
|
282
|
+
DEBUG: true,
|
|
283
|
+
TIMEOUT: 30000
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('error handling', () => {
|
|
289
|
+
beforeEach(() => {
|
|
290
|
+
// Setup successful connection for error handling tests
|
|
291
|
+
mockSTSSend.mockResolvedValue({
|
|
292
|
+
Account: '123456789012'
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should handle ResourceNotFoundException', async () => {
|
|
297
|
+
const error = new Error('Secret not found');
|
|
298
|
+
(error as any).name = 'ResourceNotFoundException';
|
|
299
|
+
|
|
300
|
+
mockSecretsManagerSend.mockRejectedValueOnce(error);
|
|
301
|
+
|
|
302
|
+
const result = await secretsmanager({
|
|
303
|
+
secret: 'non-existent-secret',
|
|
304
|
+
region: 'us-east-1'
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
expect(result).toEqual({});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should handle ConfigError', async () => {
|
|
311
|
+
const error = new Error('Invalid configuration');
|
|
312
|
+
(error as any).name = 'ConfigError';
|
|
313
|
+
(error as any).message = 'Invalid AWS configuration';
|
|
314
|
+
|
|
315
|
+
mockSecretsManagerSend.mockRejectedValueOnce(error);
|
|
316
|
+
|
|
317
|
+
const result = await secretsmanager({
|
|
318
|
+
secret: 'my-secret',
|
|
319
|
+
region: 'us-east-1'
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
expect(result).toEqual({});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should handle generic AWS errors', async () => {
|
|
326
|
+
const error = new Error('Generic AWS error');
|
|
327
|
+
(error as any).name = 'GenericError';
|
|
328
|
+
|
|
329
|
+
mockSecretsManagerSend.mockRejectedValueOnce(error);
|
|
330
|
+
|
|
331
|
+
const result = await secretsmanager({
|
|
332
|
+
secret: 'my-secret',
|
|
333
|
+
region: 'us-east-1'
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
expect(result).toEqual({});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should handle errors without name property', async () => {
|
|
340
|
+
const error = new Error('Error without name');
|
|
341
|
+
|
|
342
|
+
mockSecretsManagerSend.mockRejectedValueOnce(error);
|
|
343
|
+
|
|
344
|
+
const result = await secretsmanager({
|
|
345
|
+
secret: 'my-secret',
|
|
346
|
+
region: 'us-east-1'
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
expect(result).toEqual({});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should handle non-Error objects', async () => {
|
|
353
|
+
const error = 'String error';
|
|
354
|
+
|
|
355
|
+
mockSecretsManagerSend.mockRejectedValueOnce(error);
|
|
356
|
+
|
|
357
|
+
const result = await secretsmanager({
|
|
358
|
+
secret: 'my-secret',
|
|
359
|
+
region: 'us-east-1'
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(result).toEqual({});
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('region handling', () => {
|
|
367
|
+
beforeEach(() => {
|
|
368
|
+
mockSTSSend.mockResolvedValue({
|
|
369
|
+
Account: '123456789012'
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should use provided region', async () => {
|
|
374
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
375
|
+
SecretString: '{"KEY": "value"}'
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
await secretsmanager({
|
|
379
|
+
secret: 'my-secret',
|
|
380
|
+
region: 'ap-southeast-1'
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
expect(mockSecretsManagerClient).toHaveBeenCalledWith({
|
|
384
|
+
region: 'ap-southeast-1',
|
|
385
|
+
credentials: undefined
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should handle undefined region', async () => {
|
|
390
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
391
|
+
SecretString: '{"KEY": "value"}'
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
await secretsmanager({
|
|
395
|
+
secret: 'my-secret'
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
expect(mockSecretsManagerClient).toHaveBeenCalledWith({
|
|
399
|
+
region: undefined,
|
|
400
|
+
credentials: undefined
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
describe('integration scenarios', () => {
|
|
406
|
+
it('should handle complete successful flow with profile credentials', async () => {
|
|
407
|
+
const mockCredentials = jest.fn().mockResolvedValue({
|
|
408
|
+
accessKeyId: 'test',
|
|
409
|
+
secretAccessKey: 'test'
|
|
410
|
+
});
|
|
411
|
+
mockFromIni.mockReturnValueOnce(mockCredentials);
|
|
412
|
+
|
|
413
|
+
mockSTSSend.mockResolvedValueOnce({
|
|
414
|
+
Account: '123456789012',
|
|
415
|
+
UserId: 'test-user'
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
419
|
+
SecretString: '{"API_KEY": "test-key", "DB_PASSWORD": "secret123"}'
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const result = await secretsmanager({
|
|
423
|
+
secret: 'my-app-secrets',
|
|
424
|
+
profile: 'production',
|
|
425
|
+
region: 'us-west-2'
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
expect(mockFromIni).toHaveBeenCalledWith({ profile: 'production' });
|
|
429
|
+
expect(mockSTSClient).toHaveBeenCalledWith({ region: 'us-west-2' });
|
|
430
|
+
expect(mockSecretsManagerClient).toHaveBeenCalledWith({
|
|
431
|
+
region: 'us-west-2',
|
|
432
|
+
credentials: mockCredentials
|
|
433
|
+
});
|
|
434
|
+
expect(result).toEqual({
|
|
435
|
+
API_KEY: 'test-key',
|
|
436
|
+
DB_PASSWORD: 'secret123'
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should handle complete flow with environment variables', async () => {
|
|
441
|
+
process.env.AWS_ACCESS_KEY_ID = 'env-key';
|
|
442
|
+
process.env.AWS_SECRET_ACCESS_KEY = 'env-secret';
|
|
443
|
+
|
|
444
|
+
mockSTSSend.mockResolvedValueOnce({
|
|
445
|
+
Account: '123456789012'
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
449
|
+
SecretString: '{"ENV_VAR": "env-value"}'
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const result = await secretsmanager({
|
|
453
|
+
secret: 'env-secrets',
|
|
454
|
+
region: 'eu-central-1'
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
expect(result).toEqual({ ENV_VAR: 'env-value' });
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import * as os from 'os';
|
|
2
|
+
import {
|
|
3
|
+
replaceWithAstrisk,
|
|
4
|
+
objectToExport,
|
|
5
|
+
objectToEnv
|
|
6
|
+
} from '../../src/vaults/utils';
|
|
7
|
+
|
|
8
|
+
describe('vaults utils', () => {
|
|
9
|
+
describe('replaceWithAstrisk', () => {
|
|
10
|
+
test('should return undefined for undefined input', () => {
|
|
11
|
+
expect(replaceWithAstrisk(undefined)).toBeUndefined();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('should return undefined for empty string', () => {
|
|
15
|
+
expect(replaceWithAstrisk('')).toBeUndefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should mask middle characters for strings longer than 4 characters', () => {
|
|
19
|
+
expect(replaceWithAstrisk('password123')).toBe('p******d123');
|
|
20
|
+
expect(replaceWithAstrisk('secretkey')).toBe('s****tkey');
|
|
21
|
+
expect(replaceWithAstrisk('verylongpassword')).toBe('v***********word');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('should not mask characters for strings 4 characters or shorter', () => {
|
|
25
|
+
expect(replaceWithAstrisk('1234')).toBe('1234');
|
|
26
|
+
expect(replaceWithAstrisk('abc')).toBe('abc');
|
|
27
|
+
expect(replaceWithAstrisk('a')).toBe('a');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should preserve first and last 4 characters for longer strings', () => {
|
|
31
|
+
expect(replaceWithAstrisk('abcdefghijklmnop')).toBe('a***********mnop');
|
|
32
|
+
expect(replaceWithAstrisk('1234567890123456')).toBe('1***********3456');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('should handle special characters and unicode', () => {
|
|
36
|
+
expect(replaceWithAstrisk('p@ssw0rd!')).toBe('p****0rd!');
|
|
37
|
+
expect(replaceWithAstrisk('🔑secret🔒')).toBe('🔑*****t🔒');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('objectToExport', () => {
|
|
42
|
+
test('should convert object to env vars', () => {
|
|
43
|
+
const obj = {
|
|
44
|
+
API_KEY: 'abc123',
|
|
45
|
+
DATABASE_URL: 'postgres://localhost:5432/db',
|
|
46
|
+
DEBUG: 'true'
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const result = objectToExport(obj);
|
|
50
|
+
const expected = `API_KEY=abc123${os.EOL}DATABASE_URL=postgres://localhost:5432/db${os.EOL}DEBUG=true${os.EOL}`;
|
|
51
|
+
|
|
52
|
+
expect(result).toBe(expected);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('should handle empty object', () => {
|
|
56
|
+
const obj = {};
|
|
57
|
+
const result = objectToExport(obj);
|
|
58
|
+
expect(result).toBe('');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('should handle object with various value types', () => {
|
|
62
|
+
const obj = {
|
|
63
|
+
STRING: 'hello',
|
|
64
|
+
NUMBER: 42,
|
|
65
|
+
BOOLEAN: true,
|
|
66
|
+
NULL: null,
|
|
67
|
+
UNDEFINED: undefined
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const result = objectToExport(obj);
|
|
71
|
+
const expected = `STRING=hello${os.EOL}NUMBER=42${os.EOL}BOOLEAN=true${os.EOL}NULL=null${os.EOL}UNDEFINED=undefined${os.EOL}`;
|
|
72
|
+
|
|
73
|
+
expect(result).toBe(expected);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should handle object with special characters in keys and values', () => {
|
|
77
|
+
const obj = {
|
|
78
|
+
'API-KEY': 'abc-123',
|
|
79
|
+
DATABASE_URL: 'postgres://user:pass@localhost:5432/db',
|
|
80
|
+
DEBUG_MODE: 'true'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const result = objectToExport(obj);
|
|
84
|
+
const expected = `API-KEY=abc-123${os.EOL}DATABASE_URL=postgres://user:pass@localhost:5432/db${os.EOL}DEBUG_MODE=true${os.EOL}`;
|
|
85
|
+
|
|
86
|
+
expect(result).toBe(expected);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('objectToEnv', () => {
|
|
91
|
+
let originalEnv: NodeJS.ProcessEnv;
|
|
92
|
+
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
originalEnv = { ...process.env };
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterEach(() => {
|
|
98
|
+
process.env = originalEnv;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('should set environment variables from object', () => {
|
|
102
|
+
const obj = {
|
|
103
|
+
API_KEY: 'abc123',
|
|
104
|
+
DATABASE_URL: 'postgres://localhost:5432/db',
|
|
105
|
+
DEBUG: 'true'
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const result = objectToEnv(obj);
|
|
109
|
+
|
|
110
|
+
expect(process.env.API_KEY).toBe('abc123');
|
|
111
|
+
expect(process.env.DATABASE_URL).toBe('postgres://localhost:5432/db');
|
|
112
|
+
expect(process.env.DEBUG).toBe('true');
|
|
113
|
+
expect(result).toEqual([
|
|
114
|
+
'abc123',
|
|
115
|
+
'postgres://localhost:5432/db',
|
|
116
|
+
'true'
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('should handle empty object', () => {
|
|
121
|
+
const obj = {};
|
|
122
|
+
const result = objectToEnv(obj);
|
|
123
|
+
|
|
124
|
+
expect(result).toEqual([]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('should handle object with various value types', () => {
|
|
128
|
+
const obj = {
|
|
129
|
+
STRING: 'hello',
|
|
130
|
+
NUMBER: 42,
|
|
131
|
+
BOOLEAN: true,
|
|
132
|
+
NULL: null,
|
|
133
|
+
UNDEFINED: undefined
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const result = objectToEnv(obj);
|
|
137
|
+
|
|
138
|
+
expect(process.env.STRING).toBe('hello');
|
|
139
|
+
expect(process.env.NUMBER).toBe('42');
|
|
140
|
+
expect(process.env.BOOLEAN).toBe('true');
|
|
141
|
+
expect(process.env.NULL).toBe('null');
|
|
142
|
+
expect(process.env.UNDEFINED).toBe('undefined');
|
|
143
|
+
expect(result).toEqual(['hello', '42', 'true', 'null', 'undefined']);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('should overwrite existing environment variables', () => {
|
|
147
|
+
// Set initial environment variable
|
|
148
|
+
process.env.EXISTING_VAR = 'old_value';
|
|
149
|
+
|
|
150
|
+
const obj = {
|
|
151
|
+
EXISTING_VAR: 'new_value',
|
|
152
|
+
NEW_VAR: 'new_var_value'
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const result = objectToEnv(obj);
|
|
156
|
+
|
|
157
|
+
expect(process.env.EXISTING_VAR).toBe('new_value');
|
|
158
|
+
expect(process.env.NEW_VAR).toBe('new_var_value');
|
|
159
|
+
expect(result).toEqual(['new_value', 'new_var_value']);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('should handle object with special characters in keys and values', () => {
|
|
163
|
+
const obj = {
|
|
164
|
+
'API-KEY': 'abc-123',
|
|
165
|
+
DATABASE_URL: 'postgres://user:pass@localhost:5432/db',
|
|
166
|
+
DEBUG_MODE: 'true'
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result = objectToEnv(obj);
|
|
170
|
+
|
|
171
|
+
expect(process.env['API-KEY']).toBe('abc-123');
|
|
172
|
+
expect(process.env['DATABASE_URL']).toBe(
|
|
173
|
+
'postgres://user:pass@localhost:5432/db'
|
|
174
|
+
);
|
|
175
|
+
expect(process.env['DEBUG_MODE']).toBe('true');
|
|
176
|
+
expect(result).toEqual([
|
|
177
|
+
'abc-123',
|
|
178
|
+
'postgres://user:pass@localhost:5432/db',
|
|
179
|
+
'true'
|
|
180
|
+
]);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|