aip-master-node-sumit 1.0.8 → 1.0.11
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 +127 -28
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -10,7 +10,7 @@ const getSafeMasterUrl = (url) => {
|
|
|
10
10
|
return clean;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
// 🛡️
|
|
13
|
+
// 🛡️ Bulletproof Native Cookie Parser (Handles JWT padding '=')
|
|
14
14
|
const parseRawCookies = (cookieHeader) => {
|
|
15
15
|
if (!cookieHeader) return {};
|
|
16
16
|
|
|
@@ -36,7 +36,9 @@ if (API_KEY) {
|
|
|
36
36
|
headers: { 'Authorization': `Bearer ${API_KEY}` },
|
|
37
37
|
timeout: 5000
|
|
38
38
|
});
|
|
39
|
-
} catch (err) {
|
|
39
|
+
} catch (err) {
|
|
40
|
+
// Silently fail so the client's app doesn't crash if Master API is updating
|
|
41
|
+
}
|
|
40
42
|
};
|
|
41
43
|
sendHeartbeat();
|
|
42
44
|
setInterval(sendHeartbeat, 5 * 60 * 1000);
|
|
@@ -61,6 +63,7 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
61
63
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
62
64
|
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
63
65
|
|
|
66
|
+
// Handle preflight requests gracefully
|
|
64
67
|
if (req.method === 'OPTIONS') {
|
|
65
68
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
|
66
69
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-AIP-Token, X-Requested-With, Accept');
|
|
@@ -79,24 +82,34 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
79
82
|
return res.status(413).json({ error: "AIP Infrastructure Shield: Payload Too Large." });
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
// ==========================================
|
|
86
|
+
// 🛡️ THE FIX: Strict Header Priority (Bearer overrides Cookies)
|
|
87
|
+
// ==========================================
|
|
88
|
+
let token = null;
|
|
85
89
|
|
|
86
|
-
|
|
90
|
+
// 1. Check Explicit Headers FIRST
|
|
91
|
+
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
|
|
87
92
|
token = req.headers.authorization.split(' ')[1];
|
|
88
|
-
} else if (
|
|
93
|
+
} else if (req.headers['x-aip-token']) {
|
|
89
94
|
token = req.headers['x-aip-token'];
|
|
95
|
+
}
|
|
96
|
+
// 2. Fallback to browser cookies ONLY if no explicit headers exist
|
|
97
|
+
else {
|
|
98
|
+
const cookies = req.cookies || parseRawCookies(req.headers.cookie);
|
|
99
|
+
token = cookies.aip_session;
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
// ==========================================
|
|
93
103
|
// 🛡️ LAYER 2: IDENTITY CHECK & RBAC
|
|
94
104
|
// ==========================================
|
|
95
105
|
if (options.requireLogin) {
|
|
96
|
-
if (!token)
|
|
106
|
+
if (!token) {
|
|
107
|
+
return res.status(401).json({ error: "Unauthorized: Missing AIP Session Token" });
|
|
108
|
+
}
|
|
97
109
|
|
|
98
110
|
try {
|
|
99
111
|
const currentAction = req.method + " " + (req.baseUrl + req.path);
|
|
112
|
+
|
|
100
113
|
const authRes = await axios.post(`${AIP_MASTER_API}/iam/verify-session`,
|
|
101
114
|
{ session_token: token, action: currentAction },
|
|
102
115
|
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
|
|
@@ -104,13 +117,20 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
104
117
|
|
|
105
118
|
req.user = authRes.data.user;
|
|
106
119
|
|
|
107
|
-
// 🛡️
|
|
120
|
+
// 🛡️ Native Node.js Fallback for Set-Cookie (Framework Agnostic)
|
|
108
121
|
const isProd = process.env.NODE_ENV === 'production';
|
|
122
|
+
|
|
109
123
|
if (typeof res.cookie === 'function') {
|
|
124
|
+
// Express.js environment
|
|
110
125
|
res.cookie('aip_session', token, {
|
|
111
|
-
httpOnly: true,
|
|
126
|
+
httpOnly: true,
|
|
127
|
+
secure: isProd,
|
|
128
|
+
sameSite: isProd ? 'none' : 'lax',
|
|
129
|
+
path: '/',
|
|
130
|
+
maxAge: 12 * 60 * 60 * 1000
|
|
112
131
|
});
|
|
113
132
|
} else {
|
|
133
|
+
// Raw Node.js or alternate framework environment
|
|
114
134
|
const cookieStr = `aip_session=${token}; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=43200`;
|
|
115
135
|
res.setHeader('Set-Cookie', cookieStr);
|
|
116
136
|
}
|
|
@@ -118,11 +138,15 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
118
138
|
} catch (error) {
|
|
119
139
|
const status = error.response?.status || 500;
|
|
120
140
|
const errMsg = error.response?.data?.error || "AIP Identity Blocked: Invalid Token.";
|
|
121
|
-
|
|
122
141
|
const isProd = process.env.NODE_ENV === 'production';
|
|
142
|
+
|
|
123
143
|
if (status === 401) {
|
|
124
144
|
if (typeof res.clearCookie === 'function') {
|
|
125
|
-
res.clearCookie('aip_session', {
|
|
145
|
+
res.clearCookie('aip_session', {
|
|
146
|
+
path: '/',
|
|
147
|
+
sameSite: isProd ? 'none' : 'lax',
|
|
148
|
+
secure: isProd
|
|
149
|
+
});
|
|
126
150
|
} else {
|
|
127
151
|
res.setHeader('Set-Cookie', `aip_session=; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=0`);
|
|
128
152
|
}
|
|
@@ -136,16 +160,28 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
136
160
|
// 🛡️ LAYER 3: WAF THREAT SCAN
|
|
137
161
|
// ==========================================
|
|
138
162
|
try {
|
|
139
|
-
const payloadData = JSON.stringify({
|
|
163
|
+
const payloadData = JSON.stringify({
|
|
164
|
+
body: req.body || {},
|
|
165
|
+
query: req.query || {}
|
|
166
|
+
});
|
|
167
|
+
|
|
140
168
|
const forwardedFor = req.headers['x-forwarded-for'];
|
|
141
169
|
let clientIp = req.ip || (req.connection && req.connection.remoteAddress) || "0.0.0.0";
|
|
170
|
+
|
|
142
171
|
if (forwardedFor) {
|
|
143
|
-
clientIp = Array.isArray(forwardedFor)
|
|
172
|
+
clientIp = Array.isArray(forwardedFor)
|
|
173
|
+
? forwardedFor[0].trim()
|
|
174
|
+
: forwardedFor.split(',')[0].trim();
|
|
144
175
|
}
|
|
145
176
|
|
|
146
177
|
const wafRes = await axios.post(`${AIP_MASTER_API}/verify`, {
|
|
147
|
-
api_key: API_KEY,
|
|
148
|
-
|
|
178
|
+
api_key: API_KEY,
|
|
179
|
+
ip: clientIp,
|
|
180
|
+
method: req.method,
|
|
181
|
+
url: req.originalUrl,
|
|
182
|
+
payload: payloadData,
|
|
183
|
+
origin: req.headers.origin || "",
|
|
184
|
+
user_agent: req.headers['user-agent'] || "",
|
|
149
185
|
session_token: token || ""
|
|
150
186
|
});
|
|
151
187
|
|
|
@@ -156,26 +192,64 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
156
192
|
} catch (error) {
|
|
157
193
|
return res.status(500).json({ error: "Security verification failed." });
|
|
158
194
|
}
|
|
195
|
+
|
|
159
196
|
next();
|
|
160
197
|
};
|
|
161
198
|
};
|
|
162
199
|
|
|
200
|
+
// ==========================================
|
|
201
|
+
// 🛡️ Admin Client SDK
|
|
202
|
+
// ==========================================
|
|
163
203
|
class AipAdminClient {
|
|
164
204
|
constructor(apiKey = process.env.AIP_MASTER_API_KEY, masterUrl = process.env.AIP_MASTER_URL) {
|
|
165
205
|
if (!apiKey) throw new Error("AIP Admin Client requires an API Key");
|
|
166
206
|
this.apiKey = apiKey;
|
|
167
207
|
this.baseUrl = getSafeMasterUrl(masterUrl);
|
|
168
|
-
this.client = axios.create({
|
|
208
|
+
this.client = axios.create({
|
|
209
|
+
baseURL: this.baseUrl,
|
|
210
|
+
headers: { 'Authorization': `Bearer ${this.apiKey}` }
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async getUsers() {
|
|
215
|
+
const res = await this.client.get('/sdk/admin/users');
|
|
216
|
+
return res.data;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async createUser(data) {
|
|
220
|
+
const res = await this.client.post('/sdk/admin/users', data);
|
|
221
|
+
return res.data;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async updateUser(userId, data) {
|
|
225
|
+
const res = await this.client.put(`/sdk/admin/users/${userId}`, data);
|
|
226
|
+
return res.data;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async updateRBAC(userId, permissions) {
|
|
230
|
+
const res = await this.client.put(`/sdk/admin/users/${userId}/rbac`, { permissions });
|
|
231
|
+
return res.data;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async deleteUser(userId) {
|
|
235
|
+
const res = await this.client.delete(`/sdk/admin/users/${userId}`);
|
|
236
|
+
return res.data;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async getRoles() {
|
|
240
|
+
const res = await this.client.get('/sdk/admin/roles');
|
|
241
|
+
return res.data;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async createRole(data) {
|
|
245
|
+
const res = await this.client.post('/sdk/admin/roles', data);
|
|
246
|
+
return res.data;
|
|
169
247
|
}
|
|
170
|
-
async getUsers() { const res = await this.client.get('/sdk/admin/users'); return res.data; }
|
|
171
|
-
async createUser(data) { const res = await this.client.post('/sdk/admin/users', data); return res.data; }
|
|
172
|
-
async updateUser(userId, data) { const res = await this.client.put(`/sdk/admin/users/${userId}`, data); return res.data; }
|
|
173
|
-
async updateRBAC(userId, permissions) { const res = await this.client.put(`/sdk/admin/users/${userId}/rbac`, { permissions }); return res.data; }
|
|
174
|
-
async deleteUser(userId) { const res = await this.client.delete(`/sdk/admin/users/${userId}`); return res.data; }
|
|
175
|
-
async getRoles() { const res = await this.client.get('/sdk/admin/roles'); return res.data; }
|
|
176
|
-
async createRole(data) { const res = await this.client.post('/sdk/admin/roles', data); return res.data; }
|
|
177
248
|
}
|
|
178
249
|
|
|
250
|
+
// ==========================================
|
|
251
|
+
// 🛡️ End-User IAM Client SDK
|
|
252
|
+
// ==========================================
|
|
179
253
|
class AipIAMClient {
|
|
180
254
|
constructor(apiKey = process.env.AIP_MASTER_API_KEY, masterUrl = process.env.AIP_MASTER_URL) {
|
|
181
255
|
if (!apiKey) throw new Error("AIP IAM Client requires an API Key");
|
|
@@ -183,33 +257,58 @@ class AipIAMClient {
|
|
|
183
257
|
this.baseUrl = getSafeMasterUrl(masterUrl);
|
|
184
258
|
this.client = axios.create({ baseURL: this.baseUrl });
|
|
185
259
|
}
|
|
260
|
+
|
|
186
261
|
async loginUser(req, res, credentials) {
|
|
187
262
|
let clientIp = req.ip || (req.connection && req.connection.remoteAddress) || "0.0.0.0";
|
|
188
263
|
const forwardedFor = req.headers['x-forwarded-for'];
|
|
189
|
-
|
|
264
|
+
|
|
265
|
+
if (forwardedFor) {
|
|
266
|
+
clientIp = Array.isArray(forwardedFor)
|
|
267
|
+
? forwardedFor[0].trim()
|
|
268
|
+
: forwardedFor.split(',')[0].trim();
|
|
269
|
+
}
|
|
190
270
|
|
|
191
|
-
const authRes = await this.client.post('/iam/auth', {
|
|
271
|
+
const authRes = await this.client.post('/iam/auth', {
|
|
272
|
+
api_key: this.apiKey,
|
|
273
|
+
ip: clientIp,
|
|
274
|
+
...credentials
|
|
275
|
+
});
|
|
192
276
|
|
|
193
277
|
if (authRes.data.session_token) {
|
|
194
278
|
const isProd = process.env.NODE_ENV === 'production';
|
|
279
|
+
|
|
195
280
|
if (typeof res.cookie === 'function') {
|
|
196
|
-
res.cookie('aip_session', authRes.data.session_token, {
|
|
281
|
+
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
|
|
287
|
+
});
|
|
197
288
|
} else {
|
|
198
289
|
res.setHeader('Set-Cookie', `aip_session=${authRes.data.session_token}; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=43200`);
|
|
199
290
|
}
|
|
200
291
|
}
|
|
201
292
|
return authRes.data;
|
|
202
293
|
}
|
|
294
|
+
|
|
203
295
|
logoutUser(res) {
|
|
204
296
|
const isProd = process.env.NODE_ENV === 'production';
|
|
297
|
+
|
|
205
298
|
if (typeof res.clearCookie === 'function') {
|
|
206
|
-
res.clearCookie('aip_session', {
|
|
299
|
+
res.clearCookie('aip_session', {
|
|
300
|
+
path: '/',
|
|
301
|
+
sameSite: isProd ? 'none' : 'lax',
|
|
302
|
+
secure: isProd
|
|
303
|
+
});
|
|
207
304
|
} else {
|
|
208
305
|
res.setHeader('Set-Cookie', `aip_session=; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=0`);
|
|
209
306
|
}
|
|
210
307
|
}
|
|
211
308
|
}
|
|
212
309
|
|
|
310
|
+
// Attach the clients directly to the Guard export for easy importing
|
|
213
311
|
aipGuard.AipAdminClient = AipAdminClient;
|
|
214
312
|
aipGuard.AipIAMClient = AipIAMClient;
|
|
313
|
+
|
|
215
314
|
module.exports = aipGuard;
|