aip-master-node-sumit 1.0.7 → 1.0.9
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/index.js +147 -50
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
2
|
|
|
3
|
-
// 🛡️
|
|
3
|
+
// 🛡️ Smart URL Cleaner
|
|
4
4
|
const getSafeMasterUrl = (url) => {
|
|
5
5
|
if (!url) return 'http://localhost:8080/api/v1';
|
|
6
6
|
let clean = url.replace(/\/+$/, '');
|
|
@@ -10,6 +10,22 @@ const getSafeMasterUrl = (url) => {
|
|
|
10
10
|
return clean;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// 🛡️ THE FIX: Bulletproof Native Cookie Parser (Handles JWT padding '=')
|
|
14
|
+
const parseRawCookies = (cookieHeader) => {
|
|
15
|
+
if (!cookieHeader) return {};
|
|
16
|
+
|
|
17
|
+
return cookieHeader.split(';').reduce((acc, cookie) => {
|
|
18
|
+
const index = cookie.indexOf('=');
|
|
19
|
+
if (index < 0) return acc;
|
|
20
|
+
|
|
21
|
+
const key = decodeURIComponent(cookie.substring(0, index).trim());
|
|
22
|
+
const val = decodeURIComponent(cookie.substring(index + 1).trim());
|
|
23
|
+
|
|
24
|
+
acc[key] = val;
|
|
25
|
+
return acc;
|
|
26
|
+
}, {});
|
|
27
|
+
};
|
|
28
|
+
|
|
13
29
|
const AIP_MASTER_API = getSafeMasterUrl(process.env.AIP_MASTER_URL);
|
|
14
30
|
const API_KEY = process.env.AIP_MASTER_API_KEY;
|
|
15
31
|
|
|
@@ -24,7 +40,6 @@ if (API_KEY) {
|
|
|
24
40
|
// Silently fail so the client's app doesn't crash if Master API is updating
|
|
25
41
|
}
|
|
26
42
|
};
|
|
27
|
-
|
|
28
43
|
sendHeartbeat();
|
|
29
44
|
setInterval(sendHeartbeat, 5 * 60 * 1000);
|
|
30
45
|
}
|
|
@@ -38,11 +53,9 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
38
53
|
const origin = req.headers.origin || req.headers.Origin;
|
|
39
54
|
|
|
40
55
|
if (origin) {
|
|
41
|
-
// If origin is present, dynamically echo it to satisfy strict browser credential policies
|
|
42
56
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
43
57
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
44
58
|
} else {
|
|
45
|
-
// Fallback for non-browser clients (like Postman or cURL)
|
|
46
59
|
res.setHeader('Access-Control-Allow-Origin', process.env.AIP_ALLOWED_ORIGIN || '*');
|
|
47
60
|
}
|
|
48
61
|
|
|
@@ -65,12 +78,13 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
65
78
|
if (req.headers['transfer-encoding'] && req.headers['transfer-encoding'].includes('chunked')) {
|
|
66
79
|
return res.status(411).json({ error: "AIP Infrastructure Shield: Chunked encoding rejected." });
|
|
67
80
|
}
|
|
68
|
-
|
|
69
81
|
if (req.headers['content-length'] && parseInt(req.headers['content-length']) > MAX_PAYLOAD_BYTES) {
|
|
70
82
|
return res.status(413).json({ error: "AIP Infrastructure Shield: Payload Too Large." });
|
|
71
83
|
}
|
|
72
84
|
|
|
73
|
-
|
|
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;
|
|
74
88
|
|
|
75
89
|
if (!token && req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
|
|
76
90
|
token = req.headers.authorization.split(' ')[1];
|
|
@@ -82,11 +96,13 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
82
96
|
// 🛡️ LAYER 2: IDENTITY CHECK & RBAC
|
|
83
97
|
// ==========================================
|
|
84
98
|
if (options.requireLogin) {
|
|
85
|
-
if (!token)
|
|
99
|
+
if (!token) {
|
|
100
|
+
return res.status(401).json({ error: "Unauthorized: Missing AIP Session Token" });
|
|
101
|
+
}
|
|
86
102
|
|
|
87
103
|
try {
|
|
88
104
|
const currentAction = req.method + " " + (req.baseUrl + req.path);
|
|
89
|
-
|
|
105
|
+
|
|
90
106
|
const authRes = await axios.post(`${AIP_MASTER_API}/iam/verify-session`,
|
|
91
107
|
{ session_token: token, action: currentAction },
|
|
92
108
|
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
|
|
@@ -94,15 +110,39 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
94
110
|
|
|
95
111
|
req.user = authRes.data.user;
|
|
96
112
|
|
|
113
|
+
// 🛡️ THE FIX: Native Node.js Fallback for Set-Cookie (Framework Agnostic)
|
|
97
114
|
const isProd = process.env.NODE_ENV === 'production';
|
|
98
|
-
|
|
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
|
+
}
|
|
99
130
|
|
|
100
131
|
} catch (error) {
|
|
101
132
|
const status = error.response?.status || 500;
|
|
102
133
|
const errMsg = error.response?.data?.error || "AIP Identity Blocked: Invalid Token.";
|
|
134
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
103
135
|
|
|
104
|
-
if (status === 401
|
|
105
|
-
res.clearCookie
|
|
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
|
+
}
|
|
106
146
|
}
|
|
107
147
|
|
|
108
148
|
return res.status(status === 401 ? 401 : 403).json({ error: errMsg });
|
|
@@ -115,12 +155,12 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
115
155
|
try {
|
|
116
156
|
const payloadData = JSON.stringify({
|
|
117
157
|
body: req.body || {},
|
|
118
|
-
query: req.query || {}
|
|
158
|
+
query: req.query || {}
|
|
119
159
|
});
|
|
120
|
-
|
|
160
|
+
|
|
121
161
|
const forwardedFor = req.headers['x-forwarded-for'];
|
|
122
162
|
let clientIp = req.ip || (req.connection && req.connection.remoteAddress) || "0.0.0.0";
|
|
123
|
-
|
|
163
|
+
|
|
124
164
|
if (forwardedFor) {
|
|
125
165
|
clientIp = Array.isArray(forwardedFor)
|
|
126
166
|
? forwardedFor[0].trim()
|
|
@@ -128,12 +168,12 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
128
168
|
}
|
|
129
169
|
|
|
130
170
|
const wafRes = await axios.post(`${AIP_MASTER_API}/verify`, {
|
|
131
|
-
api_key: API_KEY,
|
|
132
|
-
ip: clientIp,
|
|
133
|
-
method: req.method,
|
|
171
|
+
api_key: API_KEY,
|
|
172
|
+
ip: clientIp,
|
|
173
|
+
method: req.method,
|
|
134
174
|
url: req.originalUrl,
|
|
135
|
-
payload: payloadData,
|
|
136
|
-
origin: req.headers.origin || "",
|
|
175
|
+
payload: payloadData,
|
|
176
|
+
origin: req.headers.origin || "",
|
|
137
177
|
user_agent: req.headers['user-agent'] || "",
|
|
138
178
|
session_token: token || ""
|
|
139
179
|
});
|
|
@@ -143,68 +183,125 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
143
183
|
}
|
|
144
184
|
|
|
145
185
|
} catch (error) {
|
|
146
|
-
console.error("WAF Engine Unreachable");
|
|
147
186
|
return res.status(500).json({ error: "Security verification failed." });
|
|
148
187
|
}
|
|
149
|
-
|
|
188
|
+
|
|
150
189
|
next();
|
|
151
190
|
};
|
|
152
191
|
};
|
|
153
192
|
|
|
154
193
|
// ==========================================
|
|
155
|
-
// 🛡️
|
|
194
|
+
// 🛡️ Admin Client SDK
|
|
156
195
|
// ==========================================
|
|
157
196
|
class AipAdminClient {
|
|
158
197
|
constructor(apiKey = process.env.AIP_MASTER_API_KEY, masterUrl = process.env.AIP_MASTER_URL) {
|
|
159
198
|
if (!apiKey) throw new Error("AIP Admin Client requires an API Key");
|
|
160
199
|
this.apiKey = apiKey;
|
|
161
200
|
this.baseUrl = getSafeMasterUrl(masterUrl);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
headers: { 'Authorization': `Bearer ${this.apiKey}` }
|
|
201
|
+
this.client = axios.create({
|
|
202
|
+
baseURL: this.baseUrl,
|
|
203
|
+
headers: { 'Authorization': `Bearer ${this.apiKey}` }
|
|
166
204
|
});
|
|
167
205
|
}
|
|
168
206
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return res.data;
|
|
207
|
+
async getUsers() {
|
|
208
|
+
const res = await this.client.get('/sdk/admin/users');
|
|
209
|
+
return res.data;
|
|
173
210
|
}
|
|
174
211
|
|
|
175
|
-
async createUser(
|
|
176
|
-
const res = await this.client.post('/sdk/admin/users',
|
|
177
|
-
return res.data;
|
|
212
|
+
async createUser(data) {
|
|
213
|
+
const res = await this.client.post('/sdk/admin/users', data);
|
|
214
|
+
return res.data;
|
|
178
215
|
}
|
|
179
216
|
|
|
180
|
-
async updateUser(userId,
|
|
181
|
-
const res = await this.client.put(`/sdk/admin/users/${userId}`,
|
|
182
|
-
return res.data;
|
|
217
|
+
async updateUser(userId, data) {
|
|
218
|
+
const res = await this.client.put(`/sdk/admin/users/${userId}`, data);
|
|
219
|
+
return res.data;
|
|
183
220
|
}
|
|
184
221
|
|
|
185
|
-
async updateRBAC(userId, permissions) {
|
|
186
|
-
const res = await this.client.put(`/sdk/admin/users/${userId}/rbac`, { permissions });
|
|
187
|
-
return res.data;
|
|
222
|
+
async updateRBAC(userId, permissions) {
|
|
223
|
+
const res = await this.client.put(`/sdk/admin/users/${userId}/rbac`, { permissions });
|
|
224
|
+
return res.data;
|
|
188
225
|
}
|
|
189
226
|
|
|
190
|
-
async deleteUser(userId) {
|
|
191
|
-
const res = await this.client.delete(`/sdk/admin/users/${userId}`);
|
|
192
|
-
return res.data;
|
|
227
|
+
async deleteUser(userId) {
|
|
228
|
+
const res = await this.client.delete(`/sdk/admin/users/${userId}`);
|
|
229
|
+
return res.data;
|
|
193
230
|
}
|
|
194
231
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return res.data;
|
|
232
|
+
async getRoles() {
|
|
233
|
+
const res = await this.client.get('/sdk/admin/roles');
|
|
234
|
+
return res.data;
|
|
199
235
|
}
|
|
200
236
|
|
|
201
|
-
async createRole(
|
|
202
|
-
const res = await this.client.post('/sdk/admin/roles',
|
|
203
|
-
return res.data;
|
|
237
|
+
async createRole(data) {
|
|
238
|
+
const res = await this.client.post('/sdk/admin/roles', data);
|
|
239
|
+
return res.data;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ==========================================
|
|
244
|
+
// 🛡️ End-User IAM Client SDK
|
|
245
|
+
// ==========================================
|
|
246
|
+
class AipIAMClient {
|
|
247
|
+
constructor(apiKey = process.env.AIP_MASTER_API_KEY, masterUrl = process.env.AIP_MASTER_URL) {
|
|
248
|
+
if (!apiKey) throw new Error("AIP IAM Client requires an API Key");
|
|
249
|
+
this.apiKey = apiKey;
|
|
250
|
+
this.baseUrl = getSafeMasterUrl(masterUrl);
|
|
251
|
+
this.client = axios.create({ baseURL: this.baseUrl });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async loginUser(req, res, credentials) {
|
|
255
|
+
let clientIp = req.ip || (req.connection && req.connection.remoteAddress) || "0.0.0.0";
|
|
256
|
+
const forwardedFor = req.headers['x-forwarded-for'];
|
|
257
|
+
|
|
258
|
+
if (forwardedFor) {
|
|
259
|
+
clientIp = Array.isArray(forwardedFor)
|
|
260
|
+
? forwardedFor[0].trim()
|
|
261
|
+
: forwardedFor.split(',')[0].trim();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const authRes = await this.client.post('/iam/auth', {
|
|
265
|
+
api_key: this.apiKey,
|
|
266
|
+
ip: clientIp,
|
|
267
|
+
...credentials
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (authRes.data.session_token) {
|
|
271
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
272
|
+
|
|
273
|
+
if (typeof res.cookie === 'function') {
|
|
274
|
+
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
|
|
280
|
+
});
|
|
281
|
+
} else {
|
|
282
|
+
res.setHeader('Set-Cookie', `aip_session=${authRes.data.session_token}; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=43200`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return authRes.data;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
logoutUser(res) {
|
|
289
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
290
|
+
|
|
291
|
+
if (typeof res.clearCookie === 'function') {
|
|
292
|
+
res.clearCookie('aip_session', {
|
|
293
|
+
path: '/',
|
|
294
|
+
sameSite: isProd ? 'none' : 'lax',
|
|
295
|
+
secure: isProd
|
|
296
|
+
});
|
|
297
|
+
} else {
|
|
298
|
+
res.setHeader('Set-Cookie', `aip_session=; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=0`);
|
|
299
|
+
}
|
|
204
300
|
}
|
|
205
301
|
}
|
|
206
302
|
|
|
207
|
-
//
|
|
303
|
+
// Attach the clients directly to the Guard export for easy importing
|
|
208
304
|
aipGuard.AipAdminClient = AipAdminClient;
|
|
305
|
+
aipGuard.AipIAMClient = AipIAMClient;
|
|
209
306
|
|
|
210
307
|
module.exports = aipGuard;
|