aip-master-node-sumit 1.0.2 → 1.0.6
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 +83 -14
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
|
-
const AIP_MASTER_API = process.env.AIP_MASTER_URL || 'http://localhost:8080/api/v1';
|
|
3
|
-
const API_KEY = process.env.AIP_MASTER_API_KEY;
|
|
4
2
|
|
|
5
|
-
// 🛡️
|
|
6
|
-
|
|
7
|
-
|
|
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);
|
|
14
|
+
const API_KEY = process.env.AIP_MASTER_API_KEY;
|
|
15
|
+
|
|
8
16
|
if (API_KEY) {
|
|
9
17
|
const sendHeartbeat = async () => {
|
|
10
18
|
try {
|
|
11
19
|
await axios.post(`${AIP_MASTER_API}/iam/ping`, { module: 'middleware' }, {
|
|
12
20
|
headers: { 'Authorization': `Bearer ${API_KEY}` },
|
|
13
|
-
timeout: 5000
|
|
21
|
+
timeout: 5000
|
|
14
22
|
});
|
|
15
23
|
} catch (err) {
|
|
16
24
|
// Silently fail so the client's app doesn't crash if Master API is updating
|
|
17
25
|
}
|
|
18
26
|
};
|
|
19
27
|
|
|
20
|
-
sendHeartbeat();
|
|
21
|
-
setInterval(sendHeartbeat, 5 * 60 * 1000);
|
|
28
|
+
sendHeartbeat();
|
|
29
|
+
setInterval(sendHeartbeat, 5 * 60 * 1000);
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
const aipGuard = (options = { requireLogin: true }) => {
|
|
@@ -26,6 +34,7 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
26
34
|
|
|
27
35
|
// LAYER 1: INFRASTRUCTURE SHIELD
|
|
28
36
|
const MAX_PAYLOAD_BYTES = 2 * 1024 * 1024;
|
|
37
|
+
|
|
29
38
|
if (req.headers['transfer-encoding'] && req.headers['transfer-encoding'].includes('chunked')) {
|
|
30
39
|
return res.status(411).send("AIP Infrastructure Shield: Chunked encoding rejected.");
|
|
31
40
|
}
|
|
@@ -39,8 +48,8 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
39
48
|
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
40
49
|
res.setHeader('Access-Control-Allow-Origin', process.env.AIP_ALLOWED_ORIGIN || '*');
|
|
41
50
|
|
|
42
|
-
// Robust Token Extraction (Checks query, cookies, and Authorization headers)
|
|
43
51
|
let token = req.query.token || (req.cookies && req.cookies.aip_session);
|
|
52
|
+
|
|
44
53
|
if (!token && req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
|
|
45
54
|
token = req.headers.authorization.split(' ')[1];
|
|
46
55
|
} else if (!token && req.headers['x-aip-token']) {
|
|
@@ -50,22 +59,24 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
50
59
|
// LAYER 2: IDENTITY CHECK & RBAC
|
|
51
60
|
if (options.requireLogin) {
|
|
52
61
|
if (!token) return res.status(401).send("Unauthorized: Missing AIP Session Token");
|
|
62
|
+
|
|
53
63
|
try {
|
|
54
64
|
const currentAction = req.method + " " + (req.baseUrl + req.path);
|
|
65
|
+
|
|
55
66
|
const authRes = await axios.post(`${AIP_MASTER_API}/iam/verify-session`,
|
|
56
67
|
{ session_token: token, action: currentAction },
|
|
57
68
|
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
|
|
58
69
|
);
|
|
70
|
+
|
|
59
71
|
req.user = authRes.data.user;
|
|
60
72
|
|
|
61
73
|
const isProd = process.env.NODE_ENV === 'production';
|
|
62
74
|
res.cookie('aip_session', token, { httpOnly: true, secure: isProd });
|
|
75
|
+
|
|
63
76
|
} catch (error) {
|
|
64
77
|
const status = error.response?.status || 500;
|
|
65
78
|
const errMsg = error.response?.data?.error || "AIP Identity Blocked: Invalid Token.";
|
|
66
|
-
|
|
67
|
-
// 🛡️ CRITICAL FIX: Only clear the session cookie if the token is actually dead (401).
|
|
68
|
-
// If it is an RBAC block (403), we just deny the action but keep them logged in!
|
|
79
|
+
|
|
69
80
|
if (status === 401 && res.clearCookie) {
|
|
70
81
|
res.clearCookie('aip_session');
|
|
71
82
|
}
|
|
@@ -80,8 +91,10 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
80
91
|
body: req.body || {},
|
|
81
92
|
query: req.query || {}
|
|
82
93
|
});
|
|
94
|
+
|
|
83
95
|
const forwardedFor = req.headers['x-forwarded-for'];
|
|
84
96
|
let clientIp = req.ip || (req.connection && req.connection.remoteAddress) || "0.0.0.0";
|
|
97
|
+
|
|
85
98
|
if (forwardedFor) {
|
|
86
99
|
clientIp = Array.isArray(forwardedFor)
|
|
87
100
|
? forwardedFor[0].trim()
|
|
@@ -96,9 +109,9 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
96
109
|
payload: payloadData,
|
|
97
110
|
origin: req.headers.origin || "",
|
|
98
111
|
user_agent: req.headers['user-agent'] || "",
|
|
99
|
-
session_token: token || ""
|
|
112
|
+
session_token: token || ""
|
|
100
113
|
});
|
|
101
|
-
|
|
114
|
+
|
|
102
115
|
if (wafRes.data.action === "block") {
|
|
103
116
|
return res.status(403).send(`AIP Firewall Blocked Request: ${wafRes.data.reason}`);
|
|
104
117
|
}
|
|
@@ -112,4 +125,60 @@ const aipGuard = (options = { requireLogin: true }) => {
|
|
|
112
125
|
};
|
|
113
126
|
};
|
|
114
127
|
|
|
128
|
+
// ==========================================
|
|
129
|
+
// 🛡️ NEW: IaaS Admin Client SDK
|
|
130
|
+
// ==========================================
|
|
131
|
+
class AipAdminClient {
|
|
132
|
+
constructor(apiKey = process.env.AIP_MASTER_API_KEY, masterUrl = process.env.AIP_MASTER_URL) {
|
|
133
|
+
if (!apiKey) throw new Error("AIP Admin Client requires an API Key");
|
|
134
|
+
this.apiKey = apiKey;
|
|
135
|
+
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;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async deleteUser(userId) {
|
|
165
|
+
const res = await this.client.delete(`/sdk/admin/users/${userId}`);
|
|
166
|
+
return res.data;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// --- Role Management ---
|
|
170
|
+
async getRoles() {
|
|
171
|
+
const res = await this.client.get('/sdk/admin/roles');
|
|
172
|
+
return res.data;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async createRole({ name, slug, permissions }) {
|
|
176
|
+
const res = await this.client.post('/sdk/admin/roles', { name, slug, permissions });
|
|
177
|
+
return res.data;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 🛡️ Attach the Admin Client directly to the Guard export for backward compatibility
|
|
182
|
+
aipGuard.AipAdminClient = AipAdminClient;
|
|
183
|
+
|
|
115
184
|
module.exports = aipGuard;
|