aip-master-node-sumit 1.0.7 → 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 +79 -74
- 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
|
|
|
@@ -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
|
}
|
|
@@ -38,11 +51,9 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
38
51
|
const origin = req.headers.origin || req.headers.Origin;
|
|
39
52
|
|
|
40
53
|
if (origin) {
|
|
41
|
-
// If origin is present, dynamically echo it to satisfy strict browser credential policies
|
|
42
54
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
43
55
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
44
56
|
} else {
|
|
45
|
-
// Fallback for non-browser clients (like Postman or cURL)
|
|
46
57
|
res.setHeader('Access-Control-Allow-Origin', process.env.AIP_ALLOWED_ORIGIN || '*');
|
|
47
58
|
}
|
|
48
59
|
|
|
@@ -50,7 +61,6 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
50
61
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
51
62
|
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
52
63
|
|
|
53
|
-
// Handle preflight requests gracefully
|
|
54
64
|
if (req.method === 'OPTIONS') {
|
|
55
65
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
|
56
66
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-AIP-Token, X-Requested-With, Accept');
|
|
@@ -65,12 +75,13 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
65
75
|
if (req.headers['transfer-encoding'] && req.headers['transfer-encoding'].includes('chunked')) {
|
|
66
76
|
return res.status(411).json({ error: "AIP Infrastructure Shield: Chunked encoding rejected." });
|
|
67
77
|
}
|
|
68
|
-
|
|
69
78
|
if (req.headers['content-length'] && parseInt(req.headers['content-length']) > MAX_PAYLOAD_BYTES) {
|
|
70
79
|
return res.status(413).json({ error: "AIP Infrastructure Shield: Payload Too Large." });
|
|
71
80
|
}
|
|
72
81
|
|
|
73
|
-
|
|
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;
|
|
74
85
|
|
|
75
86
|
if (!token && req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
|
|
76
87
|
token = req.headers.authorization.split(' ')[1];
|
|
@@ -86,7 +97,6 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
86
97
|
|
|
87
98
|
try {
|
|
88
99
|
const currentAction = req.method + " " + (req.baseUrl + req.path);
|
|
89
|
-
|
|
90
100
|
const authRes = await axios.post(`${AIP_MASTER_API}/iam/verify-session`,
|
|
91
101
|
{ session_token: token, action: currentAction },
|
|
92
102
|
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
|
|
@@ -94,15 +104,28 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
94
104
|
|
|
95
105
|
req.user = authRes.data.user;
|
|
96
106
|
|
|
107
|
+
// 🛡️ THE FIX: Native Node.js Fallback for Set-Cookie (Framework Agnostic)
|
|
97
108
|
const isProd = process.env.NODE_ENV === 'production';
|
|
98
|
-
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
|
+
}
|
|
99
117
|
|
|
100
118
|
} catch (error) {
|
|
101
119
|
const status = error.response?.status || 500;
|
|
102
120
|
const errMsg = error.response?.data?.error || "AIP Identity Blocked: Invalid Token.";
|
|
103
121
|
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
}
|
|
106
129
|
}
|
|
107
130
|
|
|
108
131
|
return res.status(status === 401 ? 401 : 403).json({ error: errMsg });
|
|
@@ -113,28 +136,16 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
113
136
|
// 🛡️ LAYER 3: WAF THREAT SCAN
|
|
114
137
|
// ==========================================
|
|
115
138
|
try {
|
|
116
|
-
const payloadData = JSON.stringify({
|
|
117
|
-
body: req.body || {},
|
|
118
|
-
query: req.query || {}
|
|
119
|
-
});
|
|
120
|
-
|
|
139
|
+
const payloadData = JSON.stringify({ body: req.body || {}, query: req.query || {} });
|
|
121
140
|
const forwardedFor = req.headers['x-forwarded-for'];
|
|
122
141
|
let clientIp = req.ip || (req.connection && req.connection.remoteAddress) || "0.0.0.0";
|
|
123
|
-
|
|
124
142
|
if (forwardedFor) {
|
|
125
|
-
clientIp = Array.isArray(forwardedFor)
|
|
126
|
-
? forwardedFor[0].trim()
|
|
127
|
-
: forwardedFor.split(',')[0].trim();
|
|
143
|
+
clientIp = Array.isArray(forwardedFor) ? forwardedFor[0].trim() : forwardedFor.split(',')[0].trim();
|
|
128
144
|
}
|
|
129
145
|
|
|
130
146
|
const wafRes = await axios.post(`${AIP_MASTER_API}/verify`, {
|
|
131
|
-
api_key: API_KEY,
|
|
132
|
-
|
|
133
|
-
method: req.method,
|
|
134
|
-
url: req.originalUrl,
|
|
135
|
-
payload: payloadData,
|
|
136
|
-
origin: req.headers.origin || "",
|
|
137
|
-
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'] || "",
|
|
138
149
|
session_token: token || ""
|
|
139
150
|
});
|
|
140
151
|
|
|
@@ -143,68 +154,62 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
143
154
|
}
|
|
144
155
|
|
|
145
156
|
} catch (error) {
|
|
146
|
-
console.error("WAF Engine Unreachable");
|
|
147
157
|
return res.status(500).json({ error: "Security verification failed." });
|
|
148
158
|
}
|
|
149
|
-
|
|
150
159
|
next();
|
|
151
160
|
};
|
|
152
161
|
};
|
|
153
162
|
|
|
154
|
-
// ==========================================
|
|
155
|
-
// 🛡️ NEW: IaaS Admin Client SDK
|
|
156
|
-
// ==========================================
|
|
157
163
|
class AipAdminClient {
|
|
158
164
|
constructor(apiKey = process.env.AIP_MASTER_API_KEY, masterUrl = process.env.AIP_MASTER_URL) {
|
|
159
165
|
if (!apiKey) throw new Error("AIP Admin Client requires an API Key");
|
|
160
166
|
this.apiKey = apiKey;
|
|
161
167
|
this.baseUrl = getSafeMasterUrl(masterUrl);
|
|
162
|
-
|
|
163
|
-
this.client = axios.create({
|
|
164
|
-
baseURL: this.baseUrl,
|
|
165
|
-
headers: { 'Authorization': `Bearer ${this.apiKey}` }
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// --- User Management ---
|
|
170
|
-
async getUsers() {
|
|
171
|
-
const res = await this.client.get('/sdk/admin/users');
|
|
172
|
-
return res.data;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async createUser({ name, email, password, role_id, send_email = false }) {
|
|
176
|
-
const res = await this.client.post('/sdk/admin/users', { name, email, password, role_id, send_email });
|
|
177
|
-
return res.data;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async updateUser(userId, { name, role_id }) {
|
|
181
|
-
const res = await this.client.put(`/sdk/admin/users/${userId}`, { name, role_id });
|
|
182
|
-
return res.data;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async updateRBAC(userId, permissions) {
|
|
186
|
-
const res = await this.client.put(`/sdk/admin/users/${userId}/rbac`, { permissions });
|
|
187
|
-
return res.data;
|
|
168
|
+
this.client = axios.create({ baseURL: this.baseUrl, headers: { 'Authorization': `Bearer ${this.apiKey}` } });
|
|
188
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
|
+
}
|
|
189
178
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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 });
|
|
193
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();
|
|
194
190
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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;
|
|
199
202
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
}
|
|
204
210
|
}
|
|
205
211
|
}
|
|
206
212
|
|
|
207
|
-
// 🛡️ Attach the Admin Client directly to the Guard export for backward compatibility
|
|
208
213
|
aipGuard.AipAdminClient = AipAdminClient;
|
|
209
|
-
|
|
214
|
+
aipGuard.AipIAMClient = AipIAMClient;
|
|
210
215
|
module.exports = aipGuard;
|