aip-master-node-sumit 1.0.11 → 1.0.12

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/index.js +112 -121
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -13,7 +13,6 @@ const getSafeMasterUrl = (url) => {
13
13
  // 🛡️ Bulletproof Native Cookie Parser (Handles JWT padding '=')
14
14
  const parseRawCookies = (cookieHeader) => {
15
15
  if (!cookieHeader) return {};
16
-
17
16
  return cookieHeader.split(';').reduce((acc, cookie) => {
18
17
  const index = cookie.indexOf('=');
19
18
  if (index < 0) return acc;
@@ -44,37 +43,38 @@ if (API_KEY) {
44
43
  setInterval(sendHeartbeat, 5 * 60 * 1000);
45
44
  }
46
45
 
47
- const aipGuard = (options = { requireLogin: true }) => {
46
+ // 🛡️ THE FIX: Added manageCors and enableWaf options for total developer control
47
+ const aipGuard = (options = { requireLogin: true, manageCors: false, enableWaf: true }) => {
48
48
  return async (req, res, next) => {
49
49
 
50
50
  // ==========================================
51
- // 🛡️ LAYER 0: DYNAMIC CORS & SECURITY HEADERS
51
+ // 🛡️ LAYER 0: DYNAMIC CORS (Opt-in only!)
52
52
  // ==========================================
53
- const origin = req.headers.origin || req.headers.Origin;
54
-
55
- if (origin) {
56
- res.setHeader('Access-Control-Allow-Origin', origin);
57
- res.setHeader('Access-Control-Allow-Credentials', 'true');
58
- } else {
59
- res.setHeader('Access-Control-Allow-Origin', process.env.AIP_ALLOWED_ORIGIN || '*');
60
- }
61
-
62
- res.setHeader('X-Frame-Options', 'DENY');
63
- res.setHeader('X-Content-Type-Options', 'nosniff');
64
- res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
65
-
66
- // Handle preflight requests gracefully
67
- if (req.method === 'OPTIONS') {
68
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
69
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-AIP-Token, X-Requested-With, Accept');
70
- return res.status(200).end();
53
+ if (options.manageCors) {
54
+ const origin = req.headers.origin || req.headers.Origin;
55
+
56
+ if (origin) {
57
+ res.setHeader('Access-Control-Allow-Origin', origin);
58
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
59
+ } else {
60
+ res.setHeader('Access-Control-Allow-Origin', process.env.AIP_ALLOWED_ORIGIN || '*');
61
+ }
62
+
63
+ res.setHeader('X-Frame-Options', 'DENY');
64
+ res.setHeader('X-Content-Type-Options', 'nosniff');
65
+ res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
66
+
67
+ if (req.method === 'OPTIONS') {
68
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
69
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-AIP-Token, X-Requested-With, Accept');
70
+ return res.status(200).end();
71
+ }
71
72
  }
72
73
 
73
74
  // ==========================================
74
75
  // 🛡️ LAYER 1: INFRASTRUCTURE SHIELD
75
76
  // ==========================================
76
77
  const MAX_PAYLOAD_BYTES = 2 * 1024 * 1024;
77
-
78
78
  if (req.headers['transfer-encoding'] && req.headers['transfer-encoding'].includes('chunked')) {
79
79
  return res.status(411).json({ error: "AIP Infrastructure Shield: Chunked encoding rejected." });
80
80
  }
@@ -83,98 +83,46 @@ const aipGuard = (options = { requireLogin: true }) => {
83
83
  }
84
84
 
85
85
  // ==========================================
86
- // 🛡️ THE FIX: Strict Header Priority (Bearer overrides Cookies)
86
+ // 🛡️ TOKEN EXTRACTION
87
87
  // ==========================================
88
88
  let token = null;
89
-
90
- // 1. Check Explicit Headers FIRST
91
89
  if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
92
90
  token = req.headers.authorization.split(' ')[1];
93
91
  } else if (req.headers['x-aip-token']) {
94
92
  token = req.headers['x-aip-token'];
95
- }
96
- // 2. Fallback to browser cookies ONLY if no explicit headers exist
97
- else {
93
+ } else {
98
94
  const cookies = req.cookies || parseRawCookies(req.headers.cookie);
99
95
  token = cookies.aip_session;
100
96
  }
101
97
 
102
- // ==========================================
103
- // 🛡️ LAYER 2: IDENTITY CHECK & RBAC
104
- // ==========================================
105
- if (options.requireLogin) {
106
- if (!token) {
107
- return res.status(401).json({ error: "Unauthorized: Missing AIP Session Token" });
108
- }
109
-
110
- try {
111
- const currentAction = req.method + " " + (req.baseUrl + req.path);
112
-
113
- const authRes = await axios.post(`${AIP_MASTER_API}/iam/verify-session`,
114
- { session_token: token, action: currentAction },
115
- { headers: { 'Authorization': `Bearer ${API_KEY}` } }
116
- );
117
-
118
- req.user = authRes.data.user;
119
-
120
- // 🛡️ Native Node.js Fallback for Set-Cookie (Framework Agnostic)
121
- const isProd = process.env.NODE_ENV === 'production';
122
-
123
- if (typeof res.cookie === 'function') {
124
- // Express.js environment
125
- res.cookie('aip_session', token, {
126
- httpOnly: true,
127
- secure: isProd,
128
- sameSite: isProd ? 'none' : 'lax',
129
- path: '/',
130
- maxAge: 12 * 60 * 60 * 1000
131
- });
132
- } else {
133
- // Raw Node.js or alternate framework environment
134
- const cookieStr = `aip_session=${token}; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=43200`;
135
- res.setHeader('Set-Cookie', cookieStr);
136
- }
137
-
138
- } catch (error) {
139
- const status = error.response?.status || 500;
140
- const errMsg = error.response?.data?.error || "AIP Identity Blocked: Invalid Token.";
141
- const isProd = process.env.NODE_ENV === 'production';
142
-
143
- if (status === 401) {
144
- if (typeof res.clearCookie === 'function') {
145
- res.clearCookie('aip_session', {
146
- path: '/',
147
- sameSite: isProd ? 'none' : 'lax',
148
- secure: isProd
149
- });
150
- } else {
151
- res.setHeader('Set-Cookie', `aip_session=; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=0`);
152
- }
153
- }
154
-
155
- return res.status(status === 401 ? 401 : 403).json({ error: errMsg });
156
- }
98
+ if (options.requireLogin && !token) {
99
+ return res.status(401).json({ error: "Unauthorized: Missing AIP Session Token" });
157
100
  }
158
101
 
159
102
  // ==========================================
160
- // 🛡️ LAYER 3: WAF THREAT SCAN
103
+ // 🛡️ THE FIX: PARALLEL LAYER 2 (IAM) & LAYER 3 (WAF)
104
+ // Cuts latency entirely in half!
161
105
  // ==========================================
162
- try {
163
- const payloadData = JSON.stringify({
164
- body: req.body || {},
165
- query: req.query || {}
166
- });
167
-
106
+ let authPromise = null;
107
+ let wafPromise = null;
108
+
109
+ if (options.requireLogin && token) {
110
+ const currentAction = req.method + " " + (req.baseUrl + req.path);
111
+ authPromise = axios.post(`${AIP_MASTER_API}/iam/verify-session`,
112
+ { session_token: token, action: currentAction },
113
+ { headers: { 'Authorization': `Bearer ${API_KEY}` } }
114
+ );
115
+ }
116
+
117
+ if (options.enableWaf !== false) {
118
+ const payloadData = JSON.stringify({ body: req.body || {}, query: req.query || {} });
168
119
  const forwardedFor = req.headers['x-forwarded-for'];
169
120
  let clientIp = req.ip || (req.connection && req.connection.remoteAddress) || "0.0.0.0";
170
-
171
121
  if (forwardedFor) {
172
- clientIp = Array.isArray(forwardedFor)
173
- ? forwardedFor[0].trim()
174
- : forwardedFor.split(',')[0].trim();
122
+ clientIp = Array.isArray(forwardedFor) ? forwardedFor[0].trim() : forwardedFor.split(',')[0].trim();
175
123
  }
176
124
 
177
- const wafRes = await axios.post(`${AIP_MASTER_API}/verify`, {
125
+ wafPromise = axios.post(`${AIP_MASTER_API}/verify`, {
178
126
  api_key: API_KEY,
179
127
  ip: clientIp,
180
128
  method: req.method,
@@ -184,13 +132,58 @@ const aipGuard = (options = { requireLogin: true }) => {
184
132
  user_agent: req.headers['user-agent'] || "",
185
133
  session_token: token || ""
186
134
  });
135
+ }
136
+
137
+ try {
138
+ // Execute both promises at the exact same time
139
+ const [authOutcome, wafOutcome] = await Promise.allSettled([
140
+ authPromise || Promise.resolve(null),
141
+ wafPromise || Promise.resolve(null)
142
+ ]);
143
+
144
+ // Handle Identity Rejections (401s)
145
+ if (options.requireLogin && authOutcome.status === 'rejected') {
146
+ const status = authOutcome.reason.response?.status || 500;
147
+ const errMsg = authOutcome.reason.response?.data?.error || "AIP Identity Blocked: Invalid Token.";
148
+ const isProd = process.env.NODE_ENV === 'production';
149
+
150
+ if (status === 401) {
151
+ if (typeof res.clearCookie === 'function') {
152
+ res.clearCookie('aip_session', { path: '/', sameSite: isProd ? 'none' : 'lax', secure: isProd });
153
+ } else {
154
+ res.setHeader('Set-Cookie', `aip_session=; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=0`);
155
+ }
156
+ }
157
+ return res.status(status === 401 ? 401 : 403).json({ error: errMsg });
158
+ }
187
159
 
188
- if (wafRes.data.action === "block") {
189
- return res.status(403).json({ error: `AIP Firewall Blocked Request: ${wafRes.data.reason}` });
160
+ // Bind User Data to Request if successful
161
+ if (authOutcome.status === 'fulfilled' && authOutcome.value) {
162
+ req.user = authOutcome.value.data.user;
163
+
164
+ // Refresh Cookie
165
+ const isProd = process.env.NODE_ENV === 'production';
166
+ if (typeof res.cookie === 'function') {
167
+ res.cookie('aip_session', token, {
168
+ httpOnly: true, secure: isProd, sameSite: isProd ? 'none' : 'lax', path: '/', maxAge: 12 * 60 * 60 * 1000
169
+ });
170
+ } else {
171
+ res.setHeader('Set-Cookie', `aip_session=${token}; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=43200`);
172
+ }
173
+ }
174
+
175
+ // Handle WAF Blocks (400, 403, 500s)
176
+ if (options.enableWaf !== false) {
177
+ if (wafOutcome.status === 'rejected') {
178
+ return res.status(500).json({ error: "Security verification failed to connect to AIP Master." });
179
+ }
180
+ if (wafOutcome.status === 'fulfilled' && wafOutcome.value && wafOutcome.value.data.action === "block") {
181
+ return res.status(403).json({ error: `AIP Firewall Blocked Request: ${wafOutcome.value.data.reason}` });
182
+ }
190
183
  }
191
184
 
192
185
  } catch (error) {
193
- return res.status(500).json({ error: "Security verification failed." });
186
+ return res.status(500).json({ error: "Critical internal security failure." });
194
187
  }
195
188
 
196
189
  next();
@@ -212,37 +205,37 @@ class AipAdminClient {
212
205
  }
213
206
 
214
207
  async getUsers() {
215
- const res = await this.client.get('/sdk/admin/users');
208
+ const res = await this.client.get('/sdk/admin/users');
216
209
  return res.data;
217
210
  }
218
211
 
219
212
  async createUser(data) {
220
- const res = await this.client.post('/sdk/admin/users', data);
213
+ const res = await this.client.post('/sdk/admin/users', data);
221
214
  return res.data;
222
215
  }
223
216
 
224
217
  async updateUser(userId, data) {
225
- const res = await this.client.put(`/sdk/admin/users/${userId}`, data);
218
+ const res = await this.client.put(`/sdk/admin/users/${userId}`, data);
226
219
  return res.data;
227
220
  }
228
221
 
229
222
  async updateRBAC(userId, permissions) {
230
- const res = await this.client.put(`/sdk/admin/users/${userId}/rbac`, { permissions });
223
+ const res = await this.client.put(`/sdk/admin/users/${userId}/rbac`, { permissions });
231
224
  return res.data;
232
225
  }
233
226
 
234
227
  async deleteUser(userId) {
235
- const res = await this.client.delete(`/sdk/admin/users/${userId}`);
228
+ const res = await this.client.delete(`/sdk/admin/users/${userId}`);
236
229
  return res.data;
237
230
  }
238
231
 
239
232
  async getRoles() {
240
- const res = await this.client.get('/sdk/admin/roles');
233
+ const res = await this.client.get('/sdk/admin/roles');
241
234
  return res.data;
242
235
  }
243
236
 
244
237
  async createRole(data) {
245
- const res = await this.client.post('/sdk/admin/roles', data);
238
+ const res = await this.client.post('/sdk/admin/roles', data);
246
239
  return res.data;
247
240
  }
248
241
  }
@@ -263,9 +256,7 @@ class AipIAMClient {
263
256
  const forwardedFor = req.headers['x-forwarded-for'];
264
257
 
265
258
  if (forwardedFor) {
266
- clientIp = Array.isArray(forwardedFor)
267
- ? forwardedFor[0].trim()
268
- : forwardedFor.split(',')[0].trim();
259
+ clientIp = Array.isArray(forwardedFor) ? forwardedFor[0].trim() : forwardedFor.split(',')[0].trim();
269
260
  }
270
261
 
271
262
  const authRes = await this.client.post('/iam/auth', {
@@ -273,17 +264,12 @@ class AipIAMClient {
273
264
  ip: clientIp,
274
265
  ...credentials
275
266
  });
276
-
267
+
277
268
  if (authRes.data.session_token) {
278
269
  const isProd = process.env.NODE_ENV === 'production';
279
-
280
270
  if (typeof res.cookie === 'function') {
281
271
  res.cookie('aip_session', authRes.data.session_token, {
282
- httpOnly: true,
283
- secure: isProd,
284
- sameSite: isProd ? 'none' : 'lax',
285
- path: '/',
286
- maxAge: 12 * 60 * 60 * 1000
272
+ httpOnly: true, secure: isProd, sameSite: isProd ? 'none' : 'lax', path: '/', maxAge: 12 * 60 * 60 * 1000
287
273
  });
288
274
  } else {
289
275
  res.setHeader('Set-Cookie', `aip_session=${authRes.data.session_token}; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=43200`);
@@ -292,15 +278,20 @@ class AipIAMClient {
292
278
  return authRes.data;
293
279
  }
294
280
 
281
+ // 🛡️ THE FIX: Solves the 404 error! Developers can now cleanly verify a token manually.
282
+ async verifyToken(token) {
283
+ if (!token) throw new Error("Token is required");
284
+ const res = await this.client.post('/iam/verify-session',
285
+ { session_token: token },
286
+ { headers: { 'Authorization': `Bearer ${this.apiKey}` } }
287
+ );
288
+ return res.data;
289
+ }
290
+
295
291
  logoutUser(res) {
296
292
  const isProd = process.env.NODE_ENV === 'production';
297
-
298
293
  if (typeof res.clearCookie === 'function') {
299
- res.clearCookie('aip_session', {
300
- path: '/',
301
- sameSite: isProd ? 'none' : 'lax',
302
- secure: isProd
303
- });
294
+ res.clearCookie('aip_session', { path: '/', sameSite: isProd ? 'none' : 'lax', secure: isProd });
304
295
  } else {
305
296
  res.setHeader('Set-Cookie', `aip_session=; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=0`);
306
297
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aip-master-node-sumit",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Enterprise-grade WAF and IAM security middleware for Node.js.",
5
5
  "main": "index.js",
6
6
  "scripts": {