nestjs-infisical 2.0.0 → 2.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/infisical.auth.d.ts +5 -0
- package/dist/infisical.auth.js +25 -0
- package/dist/infisical.http.d.ts +1 -1
- package/dist/infisical.http.js +2 -2
- package/dist/infisical.types.d.ts +2 -0
- package/dist/load-infisical.js +30 -9
- package/package.json +21 -11
- package/readme.md +115 -72
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchUniversalAuthToken = fetchUniversalAuthToken;
|
|
4
|
+
async function fetchUniversalAuthToken(options) {
|
|
5
|
+
const response = await fetch(`${options.baseUrl}/api/v1/auth/universal-auth/login`, {
|
|
6
|
+
method: 'POST',
|
|
7
|
+
headers: {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
Accept: 'application/json',
|
|
10
|
+
},
|
|
11
|
+
body: JSON.stringify({
|
|
12
|
+
clientId: options.clientId,
|
|
13
|
+
clientSecret: options.clientSecret,
|
|
14
|
+
}),
|
|
15
|
+
});
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
const text = await response.text();
|
|
18
|
+
throw new Error(`Infisical Universal Auth failed (${response.status}): ${text}`);
|
|
19
|
+
}
|
|
20
|
+
const json = (await response.json());
|
|
21
|
+
if (!json.accessToken) {
|
|
22
|
+
throw new Error('Infisical Universal Auth response missing accessToken');
|
|
23
|
+
}
|
|
24
|
+
return json.accessToken;
|
|
25
|
+
}
|
package/dist/infisical.http.d.ts
CHANGED
package/dist/infisical.http.js
CHANGED
|
@@ -7,13 +7,13 @@ async function fetchInfisicalSecrets(options) {
|
|
|
7
7
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
8
8
|
try {
|
|
9
9
|
(0, infisical_logger_1.debugLog)(options.debug, 'Calling Infisical HTTP API');
|
|
10
|
-
const url = new URL(`${options.baseUrl}/api/
|
|
10
|
+
const url = new URL(`${options.baseUrl}/api/v4/secrets`);
|
|
11
11
|
url.searchParams.set('projectId', options.projectId);
|
|
12
12
|
url.searchParams.set('environment', options.environment);
|
|
13
13
|
const response = await fetch(url.toString(), {
|
|
14
14
|
method: 'GET',
|
|
15
15
|
headers: {
|
|
16
|
-
Authorization: `Bearer ${options.
|
|
16
|
+
Authorization: `Bearer ${options.accessToken}`,
|
|
17
17
|
Accept: 'application/json',
|
|
18
18
|
},
|
|
19
19
|
signal: controller.signal,
|
package/dist/load-infisical.js
CHANGED
|
@@ -6,11 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.loadInfisical = loadInfisical;
|
|
7
7
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
8
8
|
const infisical_http_1 = require("./infisical.http");
|
|
9
|
+
const infisical_auth_1 = require("./infisical.auth");
|
|
9
10
|
const infisical_logger_1 = require("./infisical.logger");
|
|
10
11
|
const LOG_PREFIX = '[nestjs-infisical]';
|
|
11
12
|
async function loadInfisical(options = {}) {
|
|
12
13
|
const { dotenv: dotenvOptions, override = true, failFast = true, debug = false, } = options;
|
|
13
|
-
// 1️⃣
|
|
14
|
+
// 1️⃣ dotenv first
|
|
14
15
|
if (dotenvOptions !== false) {
|
|
15
16
|
(0, infisical_logger_1.debugLog)(debug, 'Loading dotenv configuration');
|
|
16
17
|
dotenv_1.default.config(dotenvOptions);
|
|
@@ -21,30 +22,50 @@ async function loadInfisical(options = {}) {
|
|
|
21
22
|
process.env.INFISICAL_BASE_URL ??
|
|
22
23
|
'https://app.infisical.com',
|
|
23
24
|
token: options.token ?? process.env.INFISICAL_TOKEN,
|
|
25
|
+
clientId: options.clientId ?? process.env.INFISICAL_CLIENT_ID,
|
|
26
|
+
clientSecret: options.clientSecret ??
|
|
27
|
+
process.env.INFISICAL_CLIENT_SECRET,
|
|
24
28
|
projectId: options.projectId ?? process.env.INFISICAL_PROJECT_ID,
|
|
25
|
-
environment: options.environment ??
|
|
29
|
+
environment: options.environment ??
|
|
30
|
+
process.env.INFISICAL_ENVIRONMENT,
|
|
26
31
|
};
|
|
27
32
|
const providedCount = Object.values(resolved).filter(Boolean).length;
|
|
28
33
|
(0, infisical_logger_1.debugLog)(debug, `Infisical config resolved: ${providedCount === 0
|
|
29
34
|
? 'none'
|
|
30
|
-
: providedCount
|
|
35
|
+
: providedCount >= 4
|
|
31
36
|
? 'complete'
|
|
32
37
|
: 'partial'}`);
|
|
33
|
-
// 3️⃣ No config → silently skip
|
|
34
38
|
if (providedCount === 0) {
|
|
35
39
|
(0, infisical_logger_1.debugLog)(debug, 'No Infisical configuration provided. Skipping.');
|
|
36
40
|
return;
|
|
37
41
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
console.warn(`${LOG_PREFIX} Partial Infisical configuration detected. Secrets will not be loaded.`);
|
|
42
|
+
if (!resolved.projectId || !resolved.environment) {
|
|
43
|
+
console.warn(`${LOG_PREFIX} Missing projectId or environment. Secrets will not be loaded.`);
|
|
41
44
|
return;
|
|
42
45
|
}
|
|
43
46
|
try {
|
|
44
|
-
|
|
47
|
+
// 3️⃣ Resolve authentication
|
|
48
|
+
let accessToken;
|
|
49
|
+
if (resolved.token) {
|
|
50
|
+
(0, infisical_logger_1.debugLog)(debug, 'Using Infisical service token');
|
|
51
|
+
accessToken = resolved.token.trim();
|
|
52
|
+
}
|
|
53
|
+
else if (resolved.clientId && resolved.clientSecret) {
|
|
54
|
+
(0, infisical_logger_1.debugLog)(debug, 'Using Infisical Universal Authentication');
|
|
55
|
+
accessToken = await (0, infisical_auth_1.fetchUniversalAuthToken)({
|
|
56
|
+
baseUrl: resolved.baseUrl,
|
|
57
|
+
clientId: resolved.clientId,
|
|
58
|
+
clientSecret: resolved.clientSecret,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.warn(`${LOG_PREFIX} No valid Infisical authentication provided. Skipping.`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// 4️⃣ Fetch secrets
|
|
45
66
|
const secrets = await (0, infisical_http_1.fetchInfisicalSecrets)({
|
|
46
67
|
baseUrl: resolved.baseUrl,
|
|
47
|
-
|
|
68
|
+
accessToken,
|
|
48
69
|
projectId: resolved.projectId,
|
|
49
70
|
environment: resolved.environment,
|
|
50
71
|
debug,
|
package/package.json
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestjs-infisical",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "CLI-free Infisical
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "CLI-free Infisical secrets loader for NestJS that fetches secrets via the HTTP API before application bootstrap and injects them into process.env.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"files": [
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/kgoel085/nest-js-infisical-module"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/kgoel085/nest-js-infisical-module#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/kgoel085/nest-js-infisical-module/issues"
|
|
18
|
+
},
|
|
11
19
|
"scripts": {
|
|
12
20
|
"build": "tsc",
|
|
13
21
|
"prepublishOnly": "npm run build"
|
|
14
22
|
},
|
|
15
23
|
"dependencies": {
|
|
16
|
-
"dotenv": "^16.4.5"
|
|
17
|
-
"typescript": "^5.9.3"
|
|
24
|
+
"dotenv": "^16.4.5"
|
|
18
25
|
},
|
|
19
26
|
"peerDependencies": {
|
|
20
|
-
"@nestjs/common": "
|
|
21
|
-
},
|
|
22
|
-
"peerDependenciesMeta": {
|
|
23
|
-
"@nestjs/common": {
|
|
24
|
-
"optional": false
|
|
25
|
-
}
|
|
27
|
+
"@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0"
|
|
26
28
|
},
|
|
27
29
|
"engines": {
|
|
28
30
|
"node": ">=18"
|
|
@@ -31,14 +33,22 @@
|
|
|
31
33
|
"access": "public"
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|
|
36
|
+
"typescript": "^5.9.3",
|
|
34
37
|
"@types/node": "^25.0.3"
|
|
35
38
|
},
|
|
36
39
|
"keywords": [
|
|
37
40
|
"nestjs",
|
|
38
41
|
"infisical",
|
|
39
42
|
"secrets",
|
|
43
|
+
"secret-management",
|
|
44
|
+
"environment-variables",
|
|
40
45
|
"configuration",
|
|
46
|
+
"config",
|
|
41
47
|
"dotenv",
|
|
42
|
-
"http"
|
|
48
|
+
"http",
|
|
49
|
+
"ci-cd",
|
|
50
|
+
"bootstrap",
|
|
51
|
+
"nodejs",
|
|
52
|
+
"typescript"
|
|
43
53
|
]
|
|
44
54
|
}
|
package/readme.md
CHANGED
|
@@ -1,65 +1,96 @@
|
|
|
1
1
|
# nestjs-infisical
|
|
2
2
|
|
|
3
|
-
CLI-free Infisical secrets loader for NestJS
|
|
3
|
+
CLI-free Infisical secrets loader for NestJS.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
injects them into `process.env`, and
|
|
7
|
-
It is intentionally boring, deterministic, and production-safe.
|
|
5
|
+
Loads secrets from Infisical via the HTTP API **before application bootstrap**,
|
|
6
|
+
injects them into `process.env`, and works seamlessly with `@nestjs/config`.
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
10
11
|
|
|
11
|
-
## Why this package exists
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
While technically possible, this approach is **not deterministic** because:
|
|
13
|
+
---
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
- Async work inside modules can interleave with bootstrap
|
|
18
|
-
- Debugging startup order becomes extremely difficult
|
|
15
|
+
## Why nestjs-infisical exists
|
|
19
16
|
|
|
20
17
|
Secrets loading is **global, side-effectful, and order-sensitive**.
|
|
21
|
-
The correct place for it is **before NestJS starts**.
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
|
|
19
|
+
Running Infisical logic inside NestJS modules, providers, or lifecycle hooks leads to:
|
|
20
|
+
- Non-deterministic startup order
|
|
21
|
+
- Race conditions between modules
|
|
22
|
+
- Hard-to-debug configuration issues
|
|
23
|
+
|
|
24
|
+
`nestjs-infisical` follows the same approach used by mature infrastructure tooling:
|
|
25
|
+
**load secrets before the framework starts**.
|
|
26
|
+
|
|
27
|
+
This guarantees predictable startup behavior across:
|
|
28
|
+
- Local development
|
|
29
|
+
- Docker
|
|
30
|
+
- CI/CD
|
|
31
|
+
- Production environments
|
|
28
32
|
|
|
29
33
|
---
|
|
30
34
|
|
|
31
|
-
## Architecture (
|
|
35
|
+
## Architecture (pre-bootstrap loader)
|
|
32
36
|
|
|
33
37
|
Startup flow:
|
|
34
38
|
|
|
35
39
|
1. Optional dotenv load
|
|
36
|
-
2.
|
|
37
|
-
3.
|
|
38
|
-
4.
|
|
39
|
-
5.
|
|
40
|
+
2. Resolve configuration (options → environment variables)
|
|
41
|
+
3. Authenticate with Infisical
|
|
42
|
+
4. Fetch secrets via Infisical HTTP API
|
|
43
|
+
5. Inject secrets into `process.env`
|
|
44
|
+
6. Bootstrap NestJS
|
|
45
|
+
7. `@nestjs/config` reads from `process.env`
|
|
40
46
|
|
|
41
47
|
Diagram:
|
|
42
48
|
|
|
43
|
-
dotenv → Infisical HTTP API → process.env → NestJS → ConfigModule
|
|
49
|
+
dotenv → Infisical Auth → Infisical HTTP API → process.env → NestJS → ConfigModule
|
|
44
50
|
|
|
45
51
|
---
|
|
46
52
|
|
|
47
|
-
##
|
|
53
|
+
## Authentication modes
|
|
54
|
+
|
|
55
|
+
This package supports **two Infisical authentication methods**.
|
|
56
|
+
|
|
57
|
+
### Recommended: Universal Authentication
|
|
48
58
|
|
|
49
|
-
|
|
59
|
+
Uses a short-lived access token generated from a client ID and client secret.
|
|
50
60
|
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
61
|
+
Best suited for:
|
|
62
|
+
- Production
|
|
63
|
+
- CI/CD pipelines
|
|
64
|
+
- Docker / Kubernetes
|
|
65
|
+
- Machine-to-machine access
|
|
55
66
|
|
|
56
|
-
|
|
67
|
+
Environment variables:
|
|
57
68
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
INFISICAL_CLIENT_ID
|
|
70
|
+
INFISICAL_CLIENT_SECRET
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### Service / Personal Token (supported)
|
|
75
|
+
|
|
76
|
+
Uses a long-lived Infisical token.
|
|
77
|
+
|
|
78
|
+
Best suited for:
|
|
79
|
+
- Local development
|
|
80
|
+
- Prototyping
|
|
81
|
+
- Backward compatibility
|
|
82
|
+
|
|
83
|
+
Environment variable:
|
|
84
|
+
|
|
85
|
+
INFISICAL_TOKEN
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### Authentication priority
|
|
90
|
+
|
|
91
|
+
If both authentication methods are provided:
|
|
92
|
+
- Service token is used
|
|
93
|
+
- A warning is logged
|
|
63
94
|
|
|
64
95
|
---
|
|
65
96
|
|
|
@@ -69,13 +100,12 @@ dotenv → Infisical HTTP API → process.env → NestJS → ConfigModule
|
|
|
69
100
|
npm install nestjs-infisical
|
|
70
101
|
```
|
|
71
102
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
- Node 18 or newer
|
|
103
|
+
Requirements:
|
|
104
|
+
- Node.js 18 or newer
|
|
75
105
|
|
|
76
106
|
---
|
|
77
107
|
|
|
78
|
-
##
|
|
108
|
+
## Usage (recommended)
|
|
79
109
|
|
|
80
110
|
### main.ts
|
|
81
111
|
|
|
@@ -108,27 +138,34 @@ Configuration is resolved in this order:
|
|
|
108
138
|
2. Explicit options passed to `loadInfisical`
|
|
109
139
|
3. Environment variables (`process.env`)
|
|
110
140
|
|
|
111
|
-
Required
|
|
141
|
+
Required configuration:
|
|
112
142
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
143
|
+
INFISICAL_PROJECT_ID
|
|
144
|
+
INFISICAL_ENVIRONMENT
|
|
145
|
+
|
|
146
|
+
Authentication (choose one):
|
|
147
|
+
|
|
148
|
+
INFISICAL_TOKEN
|
|
149
|
+
or
|
|
150
|
+
INFISICAL_CLIENT_ID + INFISICAL_CLIENT_SECRET
|
|
151
|
+
|
|
152
|
+
Optional:
|
|
153
|
+
|
|
154
|
+
INFISICAL_BASE_URL (defaults to Infisical Cloud)
|
|
117
155
|
|
|
118
156
|
---
|
|
119
157
|
|
|
120
158
|
## Zero-config usage
|
|
121
159
|
|
|
122
|
-
If
|
|
160
|
+
If everything is defined in `.env`:
|
|
123
161
|
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
|
|
162
|
+
```env
|
|
163
|
+
INFISICAL_CLIENT_ID=xxx
|
|
164
|
+
INFISICAL_CLIENT_SECRET=yyy
|
|
165
|
+
INFISICAL_PROJECT_ID=zzz
|
|
127
166
|
INFISICAL_ENVIRONMENT=dev
|
|
128
167
|
```
|
|
129
168
|
|
|
130
|
-
You can simply call:
|
|
131
|
-
|
|
132
169
|
```ts
|
|
133
170
|
await loadInfisical();
|
|
134
171
|
```
|
|
@@ -144,7 +181,7 @@ await loadInfisical({
|
|
|
144
181
|
});
|
|
145
182
|
```
|
|
146
183
|
|
|
147
|
-
Missing values are automatically
|
|
184
|
+
Missing values are automatically resolved from `process.env`.
|
|
148
185
|
|
|
149
186
|
---
|
|
150
187
|
|
|
@@ -160,46 +197,52 @@ await loadInfisical({
|
|
|
160
197
|
|
|
161
198
|
## Failure behavior
|
|
162
199
|
|
|
163
|
-
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
200
|
+
- **failFast: true** (default)
|
|
201
|
+
Application startup fails if Infisical cannot be reached
|
|
202
|
+
|
|
203
|
+
- **failFast: false**
|
|
204
|
+
Logs a warning and continues without secrets
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Compatibility with @nestjs/config
|
|
209
|
+
|
|
210
|
+
Because secrets are loaded **before NestJS starts**,
|
|
211
|
+
`@nestjs/config` works automatically with no special integration.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
ConfigModule.forRoot({
|
|
215
|
+
isGlobal: true
|
|
216
|
+
});
|
|
217
|
+
```
|
|
167
218
|
|
|
168
219
|
---
|
|
169
220
|
|
|
170
221
|
## What this package does NOT do
|
|
171
222
|
|
|
172
223
|
- No Infisical CLI usage
|
|
173
|
-
- No
|
|
224
|
+
- No token rotation or refresh
|
|
174
225
|
- No background polling
|
|
175
226
|
- No caching
|
|
176
227
|
- No retries
|
|
177
|
-
- No NestJS
|
|
228
|
+
- No NestJS lifecycle hooks
|
|
178
229
|
- No decorators
|
|
179
230
|
- No health checks
|
|
180
231
|
|
|
181
232
|
---
|
|
182
233
|
|
|
183
|
-
##
|
|
184
|
-
|
|
185
|
-
Because secrets are loaded **before** NestJS starts,
|
|
186
|
-
`@nestjs/config` works automatically without any integration.
|
|
187
|
-
|
|
188
|
-
```ts
|
|
189
|
-
ConfigModule.forRoot({
|
|
190
|
-
isGlobal: true
|
|
191
|
-
});
|
|
192
|
-
```
|
|
234
|
+
## Versioning note
|
|
193
235
|
|
|
194
|
-
|
|
236
|
+
v2.x introduces a **pre-bootstrap loader architecture**.
|
|
237
|
+
If you were using a module-based approach, migrate by moving Infisical loading
|
|
238
|
+
into `main.ts`.
|
|
195
239
|
|
|
196
240
|
---
|
|
197
241
|
|
|
198
|
-
##
|
|
242
|
+
## Repository
|
|
199
243
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
migrate by moving Infisical loading to `main.ts`.
|
|
244
|
+
GitHub:
|
|
245
|
+
https://github.com/kgoel085/nest-js-infisical-module
|
|
203
246
|
|
|
204
247
|
---
|
|
205
248
|
|