aip-master-node-sumit 1.0.9 → 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 +120 -122
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -10,10 +10,9 @@ const getSafeMasterUrl = (url) => {
10
10
  return clean;
11
11
  };
12
12
 
13
- // 🛡️ THE FIX: Bulletproof Native Cookie Parser (Handles JWT padding '=')
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
  }
@@ -82,92 +82,47 @@ const aipGuard = (options = { requireLogin: true }) => {
82
82
  return res.status(413).json({ error: "AIP Infrastructure Shield: Payload Too Large." });
83
83
  }
84
84
 
85
- // 🛡️ THE FIX: Smart Cookie Extraction & Removed Vulnerable Query Tokens
86
- const cookies = req.cookies || parseRawCookies(req.headers.cookie);
87
- let token = cookies.aip_session;
88
-
89
- if (!token && req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
85
+ // ==========================================
86
+ // 🛡️ TOKEN EXTRACTION
87
+ // ==========================================
88
+ let token = null;
89
+ if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
90
90
  token = req.headers.authorization.split(' ')[1];
91
- } else if (!token && req.headers['x-aip-token']) {
91
+ } else if (req.headers['x-aip-token']) {
92
92
  token = req.headers['x-aip-token'];
93
+ } else {
94
+ const cookies = req.cookies || parseRawCookies(req.headers.cookie);
95
+ token = cookies.aip_session;
93
96
  }
94
97
 
95
- // ==========================================
96
- // 🛡️ LAYER 2: IDENTITY CHECK & RBAC
97
- // ==========================================
98
- if (options.requireLogin) {
99
- if (!token) {
100
- return res.status(401).json({ error: "Unauthorized: Missing AIP Session Token" });
101
- }
102
-
103
- try {
104
- const currentAction = req.method + " " + (req.baseUrl + req.path);
105
-
106
- const authRes = await axios.post(`${AIP_MASTER_API}/iam/verify-session`,
107
- { session_token: token, action: currentAction },
108
- { headers: { 'Authorization': `Bearer ${API_KEY}` } }
109
- );
110
-
111
- req.user = authRes.data.user;
112
-
113
- // 🛡️ THE FIX: Native Node.js Fallback for Set-Cookie (Framework Agnostic)
114
- const isProd = process.env.NODE_ENV === 'production';
115
-
116
- if (typeof res.cookie === 'function') {
117
- // Express.js environment
118
- res.cookie('aip_session', token, {
119
- httpOnly: true,
120
- secure: isProd,
121
- sameSite: isProd ? 'none' : 'lax',
122
- path: '/',
123
- maxAge: 12 * 60 * 60 * 1000
124
- });
125
- } else {
126
- // Raw Node.js or alternate framework environment
127
- const cookieStr = `aip_session=${token}; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=43200`;
128
- res.setHeader('Set-Cookie', cookieStr);
129
- }
130
-
131
- } catch (error) {
132
- const status = error.response?.status || 500;
133
- const errMsg = error.response?.data?.error || "AIP Identity Blocked: Invalid Token.";
134
- const isProd = process.env.NODE_ENV === 'production';
135
-
136
- if (status === 401) {
137
- if (typeof res.clearCookie === 'function') {
138
- res.clearCookie('aip_session', {
139
- path: '/',
140
- sameSite: isProd ? 'none' : 'lax',
141
- secure: isProd
142
- });
143
- } else {
144
- res.setHeader('Set-Cookie', `aip_session=; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=0`);
145
- }
146
- }
147
-
148
- return res.status(status === 401 ? 401 : 403).json({ error: errMsg });
149
- }
98
+ if (options.requireLogin && !token) {
99
+ return res.status(401).json({ error: "Unauthorized: Missing AIP Session Token" });
150
100
  }
151
101
 
152
102
  // ==========================================
153
- // 🛡️ LAYER 3: WAF THREAT SCAN
103
+ // 🛡️ THE FIX: PARALLEL LAYER 2 (IAM) & LAYER 3 (WAF)
104
+ // Cuts latency entirely in half!
154
105
  // ==========================================
155
- try {
156
- const payloadData = JSON.stringify({
157
- body: req.body || {},
158
- query: req.query || {}
159
- });
160
-
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 || {} });
161
119
  const forwardedFor = req.headers['x-forwarded-for'];
162
120
  let clientIp = req.ip || (req.connection && req.connection.remoteAddress) || "0.0.0.0";
163
-
164
121
  if (forwardedFor) {
165
- clientIp = Array.isArray(forwardedFor)
166
- ? forwardedFor[0].trim()
167
- : forwardedFor.split(',')[0].trim();
122
+ clientIp = Array.isArray(forwardedFor) ? forwardedFor[0].trim() : forwardedFor.split(',')[0].trim();
168
123
  }
169
124
 
170
- const wafRes = await axios.post(`${AIP_MASTER_API}/verify`, {
125
+ wafPromise = axios.post(`${AIP_MASTER_API}/verify`, {
171
126
  api_key: API_KEY,
172
127
  ip: clientIp,
173
128
  method: req.method,
@@ -177,13 +132,58 @@ const aipGuard = (options = { requireLogin: true }) => {
177
132
  user_agent: req.headers['user-agent'] || "",
178
133
  session_token: token || ""
179
134
  });
135
+ }
180
136
 
181
- if (wafRes.data.action === "block") {
182
- return res.status(403).json({ error: `AIP Firewall Blocked Request: ${wafRes.data.reason}` });
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
+ }
159
+
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
+ }
183
183
  }
184
184
 
185
185
  } catch (error) {
186
- return res.status(500).json({ error: "Security verification failed." });
186
+ return res.status(500).json({ error: "Critical internal security failure." });
187
187
  }
188
188
 
189
189
  next();
@@ -205,37 +205,37 @@ class AipAdminClient {
205
205
  }
206
206
 
207
207
  async getUsers() {
208
- const res = await this.client.get('/sdk/admin/users');
208
+ const res = await this.client.get('/sdk/admin/users');
209
209
  return res.data;
210
210
  }
211
211
 
212
212
  async createUser(data) {
213
- const res = await this.client.post('/sdk/admin/users', data);
213
+ const res = await this.client.post('/sdk/admin/users', data);
214
214
  return res.data;
215
215
  }
216
216
 
217
217
  async updateUser(userId, data) {
218
- const res = await this.client.put(`/sdk/admin/users/${userId}`, data);
218
+ const res = await this.client.put(`/sdk/admin/users/${userId}`, data);
219
219
  return res.data;
220
220
  }
221
221
 
222
222
  async updateRBAC(userId, permissions) {
223
- 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 });
224
224
  return res.data;
225
225
  }
226
226
 
227
227
  async deleteUser(userId) {
228
- const res = await this.client.delete(`/sdk/admin/users/${userId}`);
228
+ const res = await this.client.delete(`/sdk/admin/users/${userId}`);
229
229
  return res.data;
230
230
  }
231
231
 
232
232
  async getRoles() {
233
- const res = await this.client.get('/sdk/admin/roles');
233
+ const res = await this.client.get('/sdk/admin/roles');
234
234
  return res.data;
235
235
  }
236
236
 
237
237
  async createRole(data) {
238
- const res = await this.client.post('/sdk/admin/roles', data);
238
+ const res = await this.client.post('/sdk/admin/roles', data);
239
239
  return res.data;
240
240
  }
241
241
  }
@@ -256,9 +256,7 @@ class AipIAMClient {
256
256
  const forwardedFor = req.headers['x-forwarded-for'];
257
257
 
258
258
  if (forwardedFor) {
259
- clientIp = Array.isArray(forwardedFor)
260
- ? forwardedFor[0].trim()
261
- : forwardedFor.split(',')[0].trim();
259
+ clientIp = Array.isArray(forwardedFor) ? forwardedFor[0].trim() : forwardedFor.split(',')[0].trim();
262
260
  }
263
261
 
264
262
  const authRes = await this.client.post('/iam/auth', {
@@ -266,17 +264,12 @@ class AipIAMClient {
266
264
  ip: clientIp,
267
265
  ...credentials
268
266
  });
269
-
267
+
270
268
  if (authRes.data.session_token) {
271
269
  const isProd = process.env.NODE_ENV === 'production';
272
-
273
270
  if (typeof res.cookie === 'function') {
274
271
  res.cookie('aip_session', authRes.data.session_token, {
275
- httpOnly: true,
276
- secure: isProd,
277
- sameSite: isProd ? 'none' : 'lax',
278
- path: '/',
279
- maxAge: 12 * 60 * 60 * 1000
272
+ httpOnly: true, secure: isProd, sameSite: isProd ? 'none' : 'lax', path: '/', maxAge: 12 * 60 * 60 * 1000
280
273
  });
281
274
  } else {
282
275
  res.setHeader('Set-Cookie', `aip_session=${authRes.data.session_token}; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=43200`);
@@ -285,15 +278,20 @@ class AipIAMClient {
285
278
  return authRes.data;
286
279
  }
287
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
+
288
291
  logoutUser(res) {
289
292
  const isProd = process.env.NODE_ENV === 'production';
290
-
291
293
  if (typeof res.clearCookie === 'function') {
292
- res.clearCookie('aip_session', {
293
- path: '/',
294
- sameSite: isProd ? 'none' : 'lax',
295
- secure: isProd
296
- });
294
+ res.clearCookie('aip_session', { path: '/', sameSite: isProd ? 'none' : 'lax', secure: isProd });
297
295
  } else {
298
296
  res.setHeader('Set-Cookie', `aip_session=; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=0`);
299
297
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aip-master-node-sumit",
3
- "version": "1.0.9",
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": {