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.
@@ -0,0 +1,5 @@
1
+ export declare function fetchUniversalAuthToken(options: {
2
+ baseUrl: string;
3
+ clientId: string;
4
+ clientSecret: string;
5
+ }): Promise<string>;
@@ -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
+ }
@@ -1,6 +1,6 @@
1
1
  export declare function fetchInfisicalSecrets(options: {
2
2
  baseUrl: string;
3
- token: string;
3
+ accessToken: string;
4
4
  projectId: string;
5
5
  environment: string;
6
6
  debug?: boolean;
@@ -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/v3/secrets/raw`);
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.token}`,
16
+ Authorization: `Bearer ${options.accessToken}`,
17
17
  Accept: 'application/json',
18
18
  },
19
19
  signal: controller.signal,
@@ -2,6 +2,8 @@ import { DotenvConfigOptions } from 'dotenv';
2
2
  export interface InfisicalOptions {
3
3
  baseUrl?: string;
4
4
  token?: string;
5
+ clientId?: string;
6
+ clientSecret?: string;
5
7
  projectId?: string;
6
8
  environment?: string;
7
9
  dotenv?: DotenvConfigOptions | false;
@@ -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️⃣ Load dotenv FIRST
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 ?? process.env.INFISICAL_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 === 4
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
- // 4️⃣ Partial config → warn & skip
39
- if (providedCount !== 4) {
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
- (0, infisical_logger_1.debugLog)(debug, 'Fetching Infisical secrets');
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
- token: resolved.token.trim(),
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.0.0",
4
- "description": "CLI-free Infisical HTTP integration for NestJS",
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": ">=9.0.0"
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 applications.
3
+ CLI-free Infisical secrets loader for NestJS.
4
4
 
5
- This package loads secrets from Infisical **before** your NestJS application boots,
6
- injects them into `process.env`, and then hands control back to NestJS.
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
+ ![npm](https://img.shields.io/npm/v/nestjs-infisical)
9
+ ![node](https://img.shields.io/node/v/nestjs-infisical)
10
+ ![license](https://img.shields.io/npm/l/nestjs-infisical)
10
11
 
11
- ## Why this package exists
12
12
 
13
- Earlier versions attempted to integrate Infisical directly inside NestJS modules.
14
- While technically possible, this approach is **not deterministic** because:
13
+ ---
15
14
 
16
- - NestJS does not guarantee global ordering of module side-effects
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
- This package now follows the same pattern used by:
24
- - AWS Parameter Store loaders
25
- - Vault integrations
26
- - Remote config systems
27
- - Feature flag bootstrappers
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 (Current)
35
+ ## Architecture (pre-bootstrap loader)
32
36
 
33
37
  Startup flow:
34
38
 
35
39
  1. Optional dotenv load
36
- 2. Infisical HTTP API call
37
- 3. Inject secrets into `process.env`
38
- 4. NestJS application bootstrap
39
- 5. `@nestjs/config` reads from `process.env`
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
- ## What changed from v1 to v2
53
+ ## Authentication modes
54
+
55
+ This package supports **two Infisical authentication methods**.
56
+
57
+ ### Recommended: Universal Authentication
48
58
 
49
- ### Old approach (v1 deprecated)
59
+ Uses a short-lived access token generated from a client ID and client secret.
50
60
 
51
- - Dynamic NestJS module
52
- - Async logic inside providers
53
- - Relied on NestJS lifecycle ordering
54
- - Hard to debug, non-deterministic
61
+ Best suited for:
62
+ - Production
63
+ - CI/CD pipelines
64
+ - Docker / Kubernetes
65
+ - Machine-to-machine access
55
66
 
56
- ### New approach (v2 – current)
67
+ Environment variables:
57
68
 
58
- - Explicit async loader
59
- - Called from `main.ts`
60
- - Fully deterministic
61
- - No NestJS lifecycle coupling
62
- - Easier to reason about and debug
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
- Node.js version requirement:
73
-
74
- - Node 18 or newer
103
+ Requirements:
104
+ - Node.js 18 or newer
75
105
 
76
106
  ---
77
107
 
78
- ## Basic usage (recommended)
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 environment variables:
141
+ Required configuration:
112
142
 
113
- - `INFISICAL_BASE_URL` (optional, defaults to Infisical Cloud)
114
- - `INFISICAL_TOKEN`
115
- - `INFISICAL_PROJECT_ID`
116
- - `INFISICAL_ENVIRONMENT`
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 all configuration is present in `.env`:
160
+ If everything is defined in `.env`:
123
161
 
124
- ```
125
- INFISICAL_TOKEN=xxx
126
- INFISICAL_PROJECT_ID=yyy
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 read from `process.env`.
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
- - `failFast: true` (default)
164
- - Application crashes if Infisical fails
165
- - `failFast: false`
166
- - Logs warning and continues without secrets
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 secret rotation
224
+ - No token rotation or refresh
174
225
  - No background polling
175
226
  - No caching
176
227
  - No retries
177
- - No NestJS module mutation
228
+ - No NestJS lifecycle hooks
178
229
  - No decorators
179
230
  - No health checks
180
231
 
181
232
  ---
182
233
 
183
- ## Compatibility with @nestjs/config
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
- All secrets are already present in `process.env`.
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
- ## Versioning note
242
+ ## Repository
199
243
 
200
- This architecture change is released as **v2.x**.
201
- If you were using the old module-based integration,
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