aip-master-node-sumit 1.0.0 → 1.0.3
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 +54 -39
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
2
|
|
|
3
|
-
// 🛡️ THE FIX:
|
|
4
|
-
const
|
|
3
|
+
// 🛡️ THE FIX: Smart URL Cleaner ensures the base API URL is exactly right
|
|
4
|
+
const getSafeMasterUrl = (url) => {
|
|
5
|
+
if (!url) return 'http://localhost:8080/api/v1';
|
|
6
|
+
let clean = url.replace(/\/+$/, '');
|
|
7
|
+
if (clean.endsWith('/iam')) {
|
|
8
|
+
clean = clean.substring(0, clean.length - 4);
|
|
9
|
+
}
|
|
10
|
+
return clean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const AIP_MASTER_API = getSafeMasterUrl(process.env.AIP_MASTER_URL);
|
|
5
14
|
const API_KEY = process.env.AIP_MASTER_API_KEY;
|
|
6
15
|
|
|
7
|
-
// Startup Ping Verification (Only runs if they provided an API key)
|
|
8
16
|
if (API_KEY) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
const sendHeartbeat = async () => {
|
|
18
|
+
try {
|
|
19
|
+
await axios.post(`${AIP_MASTER_API}/iam/ping`, { module: 'middleware' }, {
|
|
20
|
+
headers: { 'Authorization': `Bearer ${API_KEY}` },
|
|
21
|
+
timeout: 5000
|
|
22
|
+
});
|
|
23
|
+
} catch (err) {
|
|
24
|
+
// Silently fail so the client's app doesn't crash if Master API is updating
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
sendHeartbeat();
|
|
28
|
+
setInterval(sendHeartbeat, 5 * 60 * 1000);
|
|
12
29
|
}
|
|
13
30
|
|
|
14
|
-
/**
|
|
15
|
-
* Universal Security Guard (God-Mode)
|
|
16
|
-
* @param {Object} options - { requireLogin: boolean }
|
|
17
|
-
*/
|
|
18
31
|
const aipGuard = (options = { requireLogin: true }) => {
|
|
19
32
|
return async (req, res, next) => {
|
|
20
33
|
|
|
21
|
-
//
|
|
22
|
-
const MAX_PAYLOAD_BYTES = 2 * 1024 * 1024;
|
|
23
|
-
|
|
24
|
-
// Block advanced Chunked Encoding bypass attacks
|
|
34
|
+
// LAYER 1: INFRASTRUCTURE SHIELD
|
|
35
|
+
const MAX_PAYLOAD_BYTES = 2 * 1024 * 1024;
|
|
36
|
+
|
|
25
37
|
if (req.headers['transfer-encoding'] && req.headers['transfer-encoding'].includes('chunked')) {
|
|
26
38
|
return res.status(411).send("AIP Infrastructure Shield: Chunked encoding rejected.");
|
|
27
39
|
}
|
|
@@ -30,53 +42,56 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
30
42
|
return res.status(413).send("AIP Infrastructure Shield: Payload Too Large.");
|
|
31
43
|
}
|
|
32
44
|
|
|
33
|
-
// Inject Secure Anti-Hacker Headers into the browser
|
|
34
45
|
res.setHeader('X-Frame-Options', 'DENY');
|
|
35
46
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
36
47
|
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
37
48
|
res.setHeader('Access-Control-Allow-Origin', process.env.AIP_ALLOWED_ORIGIN || '*');
|
|
38
49
|
|
|
39
|
-
|
|
50
|
+
let token = req.query.token || (req.cookies && req.cookies.aip_session);
|
|
51
|
+
if (!token && req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
|
|
52
|
+
token = req.headers.authorization.split(' ')[1];
|
|
53
|
+
} else if (!token && req.headers['x-aip-token']) {
|
|
54
|
+
token = req.headers['x-aip-token'];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// LAYER 2: IDENTITY CHECK & RBAC
|
|
40
58
|
if (options.requireLogin) {
|
|
41
|
-
|
|
42
|
-
if (!token) return res.redirect('/login');
|
|
59
|
+
if (!token) return res.status(401).send("Unauthorized: Missing AIP Session Token");
|
|
43
60
|
|
|
44
61
|
try {
|
|
45
|
-
// The Scout: Automatically grabs the route and sends it to AIP Master for RBAC Scanning
|
|
46
62
|
const currentAction = req.method + " " + (req.baseUrl + req.path);
|
|
47
63
|
const authRes = await axios.post(`${AIP_MASTER_API}/iam/verify-session`,
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
action: currentAction
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
headers: { 'Authorization': `Bearer ${API_KEY}` }
|
|
54
|
-
}
|
|
64
|
+
{ session_token: token, action: currentAction },
|
|
65
|
+
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
|
|
55
66
|
);
|
|
56
|
-
|
|
67
|
+
|
|
57
68
|
req.user = authRes.data.user;
|
|
58
|
-
|
|
69
|
+
|
|
70
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
71
|
+
res.cookie('aip_session', token, { httpOnly: true, secure: isProd });
|
|
59
72
|
|
|
60
73
|
} catch (error) {
|
|
61
|
-
|
|
74
|
+
const status = error.response?.status || 500;
|
|
62
75
|
const errMsg = error.response?.data?.error || "AIP Identity Blocked: Invalid Token.";
|
|
63
|
-
|
|
76
|
+
|
|
77
|
+
if (status === 401 && res.clearCookie) {
|
|
78
|
+
res.clearCookie('aip_session');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return res.status(status === 401 ? 401 : 403).send(errMsg);
|
|
64
82
|
}
|
|
65
83
|
}
|
|
66
84
|
|
|
67
|
-
//
|
|
85
|
+
// LAYER 3: WAF THREAT SCAN
|
|
68
86
|
try {
|
|
69
87
|
const payloadData = JSON.stringify({
|
|
70
|
-
body: req.body,
|
|
71
|
-
query: req.query
|
|
88
|
+
body: req.body || {},
|
|
89
|
+
query: req.query || {}
|
|
72
90
|
});
|
|
73
|
-
|
|
74
|
-
// Smart IP Resolver: Protects clients hosted on Vercel, Heroku, or AWS
|
|
75
91
|
const forwardedFor = req.headers['x-forwarded-for'];
|
|
76
92
|
let clientIp = req.ip || (req.connection && req.connection.remoteAddress) || "0.0.0.0";
|
|
77
|
-
|
|
93
|
+
|
|
78
94
|
if (forwardedFor) {
|
|
79
|
-
// Handle both string and array formats safely
|
|
80
95
|
clientIp = Array.isArray(forwardedFor)
|
|
81
96
|
? forwardedFor[0].trim()
|
|
82
97
|
: forwardedFor.split(',')[0].trim();
|
|
@@ -89,7 +104,8 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
89
104
|
url: req.originalUrl,
|
|
90
105
|
payload: payloadData,
|
|
91
106
|
origin: req.headers.origin || "",
|
|
92
|
-
user_agent: req.headers['user-agent'] || ""
|
|
107
|
+
user_agent: req.headers['user-agent'] || "",
|
|
108
|
+
session_token: token || ""
|
|
93
109
|
});
|
|
94
110
|
|
|
95
111
|
if (wafRes.data.action === "block") {
|
|
@@ -101,7 +117,6 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
101
117
|
return res.status(500).send("Security verification failed.");
|
|
102
118
|
}
|
|
103
119
|
|
|
104
|
-
// Passed all 3 Layers perfectly!
|
|
105
120
|
next();
|
|
106
121
|
};
|
|
107
122
|
};
|