aip-master-node-sumit 1.0.6 → 1.0.8
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 +116 -85
- package/package.json +2 -2
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
|
|
|
@@ -20,11 +36,8 @@ if (API_KEY) {
|
|
|
20
36
|
headers: { 'Authorization': `Bearer ${API_KEY}` },
|
|
21
37
|
timeout: 5000
|
|
22
38
|
});
|
|
23
|
-
} catch (err) {
|
|
24
|
-
// Silently fail so the client's app doesn't crash if Master API is updating
|
|
25
|
-
}
|
|
39
|
+
} catch (err) {}
|
|
26
40
|
};
|
|
27
|
-
|
|
28
41
|
sendHeartbeat();
|
|
29
42
|
setInterval(sendHeartbeat, 5 * 60 * 1000);
|
|
30
43
|
}
|
|
@@ -32,23 +45,43 @@ if (API_KEY) {
|
|
|
32
45
|
const aipGuard = (options = { requireLogin: true }) => {
|
|
33
46
|
return async (req, res, next) => {
|
|
34
47
|
|
|
35
|
-
//
|
|
48
|
+
// ==========================================
|
|
49
|
+
// 🛡️ LAYER 0: DYNAMIC CORS & SECURITY HEADERS
|
|
50
|
+
// ==========================================
|
|
51
|
+
const origin = req.headers.origin || req.headers.Origin;
|
|
52
|
+
|
|
53
|
+
if (origin) {
|
|
54
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
55
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
56
|
+
} else {
|
|
57
|
+
res.setHeader('Access-Control-Allow-Origin', process.env.AIP_ALLOWED_ORIGIN || '*');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
61
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
62
|
+
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
63
|
+
|
|
64
|
+
if (req.method === 'OPTIONS') {
|
|
65
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
|
66
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-AIP-Token, X-Requested-With, Accept');
|
|
67
|
+
return res.status(200).end();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ==========================================
|
|
71
|
+
// 🛡️ LAYER 1: INFRASTRUCTURE SHIELD
|
|
72
|
+
// ==========================================
|
|
36
73
|
const MAX_PAYLOAD_BYTES = 2 * 1024 * 1024;
|
|
37
74
|
|
|
38
75
|
if (req.headers['transfer-encoding'] && req.headers['transfer-encoding'].includes('chunked')) {
|
|
39
|
-
return res.status(411).
|
|
76
|
+
return res.status(411).json({ error: "AIP Infrastructure Shield: Chunked encoding rejected." });
|
|
40
77
|
}
|
|
41
|
-
|
|
42
78
|
if (req.headers['content-length'] && parseInt(req.headers['content-length']) > MAX_PAYLOAD_BYTES) {
|
|
43
|
-
return res.status(413).
|
|
79
|
+
return res.status(413).json({ error: "AIP Infrastructure Shield: Payload Too Large." });
|
|
44
80
|
}
|
|
45
|
-
|
|
46
|
-
res.setHeader('X-Frame-Options', 'DENY');
|
|
47
|
-
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
48
|
-
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
49
|
-
res.setHeader('Access-Control-Allow-Origin', process.env.AIP_ALLOWED_ORIGIN || '*');
|
|
50
81
|
|
|
51
|
-
|
|
82
|
+
// 🛡️ THE FIX: Smart Cookie Extraction & Removed Vulnerable Query Tokens
|
|
83
|
+
const cookies = req.cookies || parseRawCookies(req.headers.cookie);
|
|
84
|
+
let token = cookies.aip_session;
|
|
52
85
|
|
|
53
86
|
if (!token && req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
|
|
54
87
|
token = req.headers.authorization.split(' ')[1];
|
|
@@ -56,13 +89,14 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
56
89
|
token = req.headers['x-aip-token'];
|
|
57
90
|
}
|
|
58
91
|
|
|
59
|
-
//
|
|
92
|
+
// ==========================================
|
|
93
|
+
// 🛡️ LAYER 2: IDENTITY CHECK & RBAC
|
|
94
|
+
// ==========================================
|
|
60
95
|
if (options.requireLogin) {
|
|
61
|
-
if (!token) return res.status(401).
|
|
96
|
+
if (!token) return res.status(401).json({ error: "Unauthorized: Missing AIP Session Token" });
|
|
62
97
|
|
|
63
98
|
try {
|
|
64
99
|
const currentAction = req.method + " " + (req.baseUrl + req.path);
|
|
65
|
-
|
|
66
100
|
const authRes = await axios.post(`${AIP_MASTER_API}/iam/verify-session`,
|
|
67
101
|
{ session_token: token, action: currentAction },
|
|
68
102
|
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
|
|
@@ -70,115 +104,112 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
70
104
|
|
|
71
105
|
req.user = authRes.data.user;
|
|
72
106
|
|
|
107
|
+
// 🛡️ THE FIX: Native Node.js Fallback for Set-Cookie (Framework Agnostic)
|
|
73
108
|
const isProd = process.env.NODE_ENV === 'production';
|
|
74
|
-
res.cookie
|
|
109
|
+
if (typeof res.cookie === 'function') {
|
|
110
|
+
res.cookie('aip_session', token, {
|
|
111
|
+
httpOnly: true, secure: isProd, sameSite: isProd ? 'none' : 'lax', path: '/', maxAge: 12 * 60 * 60 * 1000
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
const cookieStr = `aip_session=${token}; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=43200`;
|
|
115
|
+
res.setHeader('Set-Cookie', cookieStr);
|
|
116
|
+
}
|
|
75
117
|
|
|
76
118
|
} catch (error) {
|
|
77
119
|
const status = error.response?.status || 500;
|
|
78
120
|
const errMsg = error.response?.data?.error || "AIP Identity Blocked: Invalid Token.";
|
|
79
121
|
|
|
80
|
-
|
|
81
|
-
|
|
122
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
123
|
+
if (status === 401) {
|
|
124
|
+
if (typeof res.clearCookie === 'function') {
|
|
125
|
+
res.clearCookie('aip_session', { path: '/', sameSite: isProd ? 'none' : 'lax', secure: isProd });
|
|
126
|
+
} else {
|
|
127
|
+
res.setHeader('Set-Cookie', `aip_session=; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=0`);
|
|
128
|
+
}
|
|
82
129
|
}
|
|
83
130
|
|
|
84
|
-
return res.status(status === 401 ? 401 : 403).
|
|
131
|
+
return res.status(status === 401 ? 401 : 403).json({ error: errMsg });
|
|
85
132
|
}
|
|
86
133
|
}
|
|
87
134
|
|
|
88
|
-
//
|
|
135
|
+
// ==========================================
|
|
136
|
+
// 🛡️ LAYER 3: WAF THREAT SCAN
|
|
137
|
+
// ==========================================
|
|
89
138
|
try {
|
|
90
|
-
const payloadData = JSON.stringify({
|
|
91
|
-
body: req.body || {},
|
|
92
|
-
query: req.query || {}
|
|
93
|
-
});
|
|
94
|
-
|
|
139
|
+
const payloadData = JSON.stringify({ body: req.body || {}, query: req.query || {} });
|
|
95
140
|
const forwardedFor = req.headers['x-forwarded-for'];
|
|
96
141
|
let clientIp = req.ip || (req.connection && req.connection.remoteAddress) || "0.0.0.0";
|
|
97
|
-
|
|
98
142
|
if (forwardedFor) {
|
|
99
|
-
clientIp = Array.isArray(forwardedFor)
|
|
100
|
-
? forwardedFor[0].trim()
|
|
101
|
-
: forwardedFor.split(',')[0].trim();
|
|
143
|
+
clientIp = Array.isArray(forwardedFor) ? forwardedFor[0].trim() : forwardedFor.split(',')[0].trim();
|
|
102
144
|
}
|
|
103
145
|
|
|
104
146
|
const wafRes = await axios.post(`${AIP_MASTER_API}/verify`, {
|
|
105
|
-
api_key: API_KEY,
|
|
106
|
-
|
|
107
|
-
method: req.method,
|
|
108
|
-
url: req.originalUrl,
|
|
109
|
-
payload: payloadData,
|
|
110
|
-
origin: req.headers.origin || "",
|
|
111
|
-
user_agent: req.headers['user-agent'] || "",
|
|
147
|
+
api_key: API_KEY, ip: clientIp, method: req.method, url: req.originalUrl,
|
|
148
|
+
payload: payloadData, origin: req.headers.origin || "", user_agent: req.headers['user-agent'] || "",
|
|
112
149
|
session_token: token || ""
|
|
113
150
|
});
|
|
114
151
|
|
|
115
152
|
if (wafRes.data.action === "block") {
|
|
116
|
-
return res.status(403).
|
|
153
|
+
return res.status(403).json({ error: `AIP Firewall Blocked Request: ${wafRes.data.reason}` });
|
|
117
154
|
}
|
|
118
155
|
|
|
119
156
|
} catch (error) {
|
|
120
|
-
|
|
121
|
-
return res.status(500).send("Security verification failed.");
|
|
157
|
+
return res.status(500).json({ error: "Security verification failed." });
|
|
122
158
|
}
|
|
123
|
-
|
|
124
159
|
next();
|
|
125
160
|
};
|
|
126
161
|
};
|
|
127
162
|
|
|
128
|
-
// ==========================================
|
|
129
|
-
// 🛡️ NEW: IaaS Admin Client SDK
|
|
130
|
-
// ==========================================
|
|
131
163
|
class AipAdminClient {
|
|
132
164
|
constructor(apiKey = process.env.AIP_MASTER_API_KEY, masterUrl = process.env.AIP_MASTER_URL) {
|
|
133
165
|
if (!apiKey) throw new Error("AIP Admin Client requires an API Key");
|
|
134
166
|
this.apiKey = apiKey;
|
|
135
167
|
this.baseUrl = getSafeMasterUrl(masterUrl);
|
|
136
|
-
|
|
137
|
-
this.client = axios.create({
|
|
138
|
-
baseURL: this.baseUrl,
|
|
139
|
-
headers: { 'Authorization': `Bearer ${this.apiKey}` }
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// --- User Management ---
|
|
144
|
-
async getUsers() {
|
|
145
|
-
const res = await this.client.get('/sdk/admin/users');
|
|
146
|
-
return res.data;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async createUser({ name, email, password, role_id, send_email = false }) {
|
|
150
|
-
const res = await this.client.post('/sdk/admin/users', { name, email, password, role_id, send_email });
|
|
151
|
-
return res.data;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async updateUser(userId, { name, role_id }) {
|
|
155
|
-
const res = await this.client.put(`/sdk/admin/users/${userId}`, { name, role_id });
|
|
156
|
-
return res.data;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async updateRBAC(userId, permissions) {
|
|
160
|
-
const res = await this.client.put(`/sdk/admin/users/${userId}/rbac`, { permissions });
|
|
161
|
-
return res.data;
|
|
168
|
+
this.client = axios.create({ baseURL: this.baseUrl, headers: { 'Authorization': `Bearer ${this.apiKey}` } });
|
|
162
169
|
}
|
|
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
|
+
}
|
|
163
178
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
179
|
+
class AipIAMClient {
|
|
180
|
+
constructor(apiKey = process.env.AIP_MASTER_API_KEY, masterUrl = process.env.AIP_MASTER_URL) {
|
|
181
|
+
if (!apiKey) throw new Error("AIP IAM Client requires an API Key");
|
|
182
|
+
this.apiKey = apiKey;
|
|
183
|
+
this.baseUrl = getSafeMasterUrl(masterUrl);
|
|
184
|
+
this.client = axios.create({ baseURL: this.baseUrl });
|
|
167
185
|
}
|
|
186
|
+
async loginUser(req, res, credentials) {
|
|
187
|
+
let clientIp = req.ip || (req.connection && req.connection.remoteAddress) || "0.0.0.0";
|
|
188
|
+
const forwardedFor = req.headers['x-forwarded-for'];
|
|
189
|
+
if (forwardedFor) clientIp = Array.isArray(forwardedFor) ? forwardedFor[0].trim() : forwardedFor.split(',')[0].trim();
|
|
168
190
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
191
|
+
const authRes = await this.client.post('/iam/auth', { api_key: this.apiKey, ip: clientIp, ...credentials });
|
|
192
|
+
|
|
193
|
+
if (authRes.data.session_token) {
|
|
194
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
195
|
+
if (typeof res.cookie === 'function') {
|
|
196
|
+
res.cookie('aip_session', authRes.data.session_token, { httpOnly: true, secure: isProd, sameSite: isProd ? 'none' : 'lax', path: '/', maxAge: 12 * 60 * 60 * 1000 });
|
|
197
|
+
} else {
|
|
198
|
+
res.setHeader('Set-Cookie', `aip_session=${authRes.data.session_token}; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=43200`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return authRes.data;
|
|
173
202
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
203
|
+
logoutUser(res) {
|
|
204
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
205
|
+
if (typeof res.clearCookie === 'function') {
|
|
206
|
+
res.clearCookie('aip_session', { path: '/', sameSite: isProd ? 'none' : 'lax', secure: isProd });
|
|
207
|
+
} else {
|
|
208
|
+
res.setHeader('Set-Cookie', `aip_session=; HttpOnly; ${isProd ? 'Secure; SameSite=None' : 'SameSite=Lax'}; Path=/; Max-Age=0`);
|
|
209
|
+
}
|
|
178
210
|
}
|
|
179
211
|
}
|
|
180
212
|
|
|
181
|
-
// 🛡️ Attach the Admin Client directly to the Guard export for backward compatibility
|
|
182
213
|
aipGuard.AipAdminClient = AipAdminClient;
|
|
183
|
-
|
|
214
|
+
aipGuard.AipIAMClient = AipIAMClient;
|
|
184
215
|
module.exports = aipGuard;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aip-master-node-sumit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Enterprise-grade WAF and IAM security middleware for Node.js.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,4 +17,4 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"axios": "^1.13.6"
|
|
19
19
|
}
|
|
20
|
-
}
|
|
20
|
+
}
|