strapi-plugin-magic-sessionmanager 4.2.4 → 4.2.5
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/dist/server/index.js +1 -1
- package/dist/server/index.mjs +1 -1
- package/package.json +1 -3
- package/admin/jsconfig.json +0 -10
- package/admin/src/components/Initializer.jsx +0 -11
- package/admin/src/components/LicenseGuard.jsx +0 -591
- package/admin/src/components/OnlineUsersWidget.jsx +0 -212
- package/admin/src/components/PluginIcon.jsx +0 -8
- package/admin/src/components/SessionDetailModal.jsx +0 -449
- package/admin/src/components/SessionInfoCard.jsx +0 -151
- package/admin/src/components/SessionInfoPanel.jsx +0 -385
- package/admin/src/components/index.jsx +0 -5
- package/admin/src/hooks/useLicense.js +0 -103
- package/admin/src/index.js +0 -149
- package/admin/src/pages/ActiveSessions.jsx +0 -12
- package/admin/src/pages/Analytics.jsx +0 -735
- package/admin/src/pages/App.jsx +0 -12
- package/admin/src/pages/HomePage.jsx +0 -1212
- package/admin/src/pages/License.jsx +0 -603
- package/admin/src/pages/Settings.jsx +0 -1646
- package/admin/src/pages/SettingsNew.jsx +0 -1204
- package/admin/src/pages/UpgradePage.jsx +0 -448
- package/admin/src/pages/index.jsx +0 -3
- package/admin/src/pluginId.js +0 -4
- package/admin/src/translations/de.json +0 -299
- package/admin/src/translations/en.json +0 -299
- package/admin/src/translations/es.json +0 -287
- package/admin/src/translations/fr.json +0 -287
- package/admin/src/translations/pt.json +0 -287
- package/admin/src/utils/getTranslation.js +0 -5
- package/admin/src/utils/index.js +0 -2
- package/admin/src/utils/parseUserAgent.js +0 -79
- package/admin/src/utils/theme.js +0 -85
- package/server/jsconfig.json +0 -10
- package/server/src/bootstrap.js +0 -492
- package/server/src/config/index.js +0 -23
- package/server/src/content-types/index.js +0 -9
- package/server/src/content-types/session/schema.json +0 -84
- package/server/src/controllers/controller.js +0 -11
- package/server/src/controllers/index.js +0 -11
- package/server/src/controllers/license.js +0 -266
- package/server/src/controllers/session.js +0 -433
- package/server/src/controllers/settings.js +0 -122
- package/server/src/destroy.js +0 -22
- package/server/src/index.js +0 -23
- package/server/src/middlewares/index.js +0 -5
- package/server/src/middlewares/last-seen.js +0 -62
- package/server/src/policies/index.js +0 -3
- package/server/src/register.js +0 -36
- package/server/src/routes/admin.js +0 -149
- package/server/src/routes/content-api.js +0 -60
- package/server/src/routes/index.js +0 -9
- package/server/src/services/geolocation.js +0 -182
- package/server/src/services/index.js +0 -13
- package/server/src/services/license-guard.js +0 -316
- package/server/src/services/notifications.js +0 -319
- package/server/src/services/service.js +0 -7
- package/server/src/services/session.js +0 -393
- package/server/src/utils/encryption.js +0 -121
- package/server/src/utils/getClientIp.js +0 -118
- package/server/src/utils/logger.js +0 -84
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* License Guard Service for Magic Session Manager
|
|
3
|
-
* Handles license creation, verification, and ping tracking
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const crypto = require('crypto');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
const pluginPkg = require('../../../package.json');
|
|
9
|
-
const { createLogger } = require('../utils/logger');
|
|
10
|
-
|
|
11
|
-
// FIXED LICENSE SERVER URL
|
|
12
|
-
const LICENSE_SERVER_URL = 'https://magicapi.fitlex.me';
|
|
13
|
-
|
|
14
|
-
module.exports = ({ strapi }) => {
|
|
15
|
-
const log = createLogger(strapi);
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
/**
|
|
19
|
-
* Get license server URL
|
|
20
|
-
*/
|
|
21
|
-
getLicenseServerUrl() {
|
|
22
|
-
return LICENSE_SERVER_URL;
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Generate device ID
|
|
27
|
-
*/
|
|
28
|
-
generateDeviceId() {
|
|
29
|
-
try {
|
|
30
|
-
const networkInterfaces = os.networkInterfaces();
|
|
31
|
-
const macAddresses = [];
|
|
32
|
-
|
|
33
|
-
Object.values(networkInterfaces).forEach(interfaces => {
|
|
34
|
-
interfaces?.forEach(iface => {
|
|
35
|
-
if (iface.mac && iface.mac !== '00:00:00:00:00:00') {
|
|
36
|
-
macAddresses.push(iface.mac);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const identifier = `${macAddresses.join('-')}-${os.hostname()}`;
|
|
42
|
-
return crypto.createHash('sha256').update(identifier).digest('hex').substring(0, 32);
|
|
43
|
-
} catch (error) {
|
|
44
|
-
return crypto.randomBytes(16).toString('hex');
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
|
|
48
|
-
getDeviceName() {
|
|
49
|
-
try {
|
|
50
|
-
return os.hostname() || 'Unknown Device';
|
|
51
|
-
} catch (error) {
|
|
52
|
-
return 'Unknown Device';
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
getIpAddress() {
|
|
57
|
-
try {
|
|
58
|
-
const networkInterfaces = os.networkInterfaces();
|
|
59
|
-
for (const name of Object.keys(networkInterfaces)) {
|
|
60
|
-
const interfaces = networkInterfaces[name];
|
|
61
|
-
if (interfaces) {
|
|
62
|
-
for (const iface of interfaces) {
|
|
63
|
-
if (iface.family === 'IPv4' && !iface.internal) {
|
|
64
|
-
return iface.address;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return '127.0.0.1';
|
|
70
|
-
} catch (error) {
|
|
71
|
-
return '127.0.0.1';
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
getUserAgent() {
|
|
76
|
-
const pluginVersion = pluginPkg.version || '1.0.0';
|
|
77
|
-
const strapiVersion = strapi.config.get('info.strapi') || '5.0.0';
|
|
78
|
-
return `MagicSessionManager/${pluginVersion} Strapi/${strapiVersion} Node/${process.version} ${os.platform()}/${os.release()}`;
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
async createLicense({ email, firstName, lastName }) {
|
|
82
|
-
try {
|
|
83
|
-
const deviceId = this.generateDeviceId();
|
|
84
|
-
const deviceName = this.getDeviceName();
|
|
85
|
-
const ipAddress = this.getIpAddress();
|
|
86
|
-
const userAgent = this.getUserAgent();
|
|
87
|
-
|
|
88
|
-
const licenseServerUrl = this.getLicenseServerUrl();
|
|
89
|
-
const response = await fetch(`${licenseServerUrl}/api/licenses/create`, {
|
|
90
|
-
method: 'POST',
|
|
91
|
-
headers: { 'Content-Type': 'application/json' },
|
|
92
|
-
body: JSON.stringify({
|
|
93
|
-
email,
|
|
94
|
-
firstName,
|
|
95
|
-
lastName,
|
|
96
|
-
deviceName,
|
|
97
|
-
deviceId,
|
|
98
|
-
ipAddress,
|
|
99
|
-
userAgent,
|
|
100
|
-
pluginName: 'magic-sessionmanager',
|
|
101
|
-
productName: 'Magic Session Manager - Premium Session Tracking',
|
|
102
|
-
}),
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const data = await response.json();
|
|
106
|
-
|
|
107
|
-
if (data.success) {
|
|
108
|
-
log.info('[SUCCESS] License created:', data.data.licenseKey);
|
|
109
|
-
return data.data;
|
|
110
|
-
} else {
|
|
111
|
-
log.error('[ERROR] License creation failed:', data);
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
} catch (error) {
|
|
115
|
-
log.error('[ERROR] Error creating license:', error);
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
async verifyLicense(licenseKey, allowGracePeriod = false) {
|
|
121
|
-
try {
|
|
122
|
-
const controller = new AbortController();
|
|
123
|
-
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
124
|
-
|
|
125
|
-
const licenseServerUrl = this.getLicenseServerUrl();
|
|
126
|
-
const response = await fetch(`${licenseServerUrl}/api/licenses/verify`, {
|
|
127
|
-
method: 'POST',
|
|
128
|
-
headers: { 'Content-Type': 'application/json' },
|
|
129
|
-
body: JSON.stringify({
|
|
130
|
-
licenseKey,
|
|
131
|
-
pluginName: 'magic-sessionmanager',
|
|
132
|
-
productName: 'Magic Session Manager - Premium Session Tracking',
|
|
133
|
-
}),
|
|
134
|
-
signal: controller.signal,
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
clearTimeout(timeoutId);
|
|
138
|
-
const data = await response.json();
|
|
139
|
-
|
|
140
|
-
if (data.success && data.data) {
|
|
141
|
-
return { valid: true, data: data.data, gracePeriod: false };
|
|
142
|
-
} else {
|
|
143
|
-
return { valid: false, data: null };
|
|
144
|
-
}
|
|
145
|
-
} catch (error) {
|
|
146
|
-
if (allowGracePeriod) {
|
|
147
|
-
return { valid: true, data: null, gracePeriod: true };
|
|
148
|
-
}
|
|
149
|
-
return { valid: false, data: null };
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
async getLicenseByKey(licenseKey) {
|
|
154
|
-
try {
|
|
155
|
-
const licenseServerUrl = this.getLicenseServerUrl();
|
|
156
|
-
const url = `${licenseServerUrl}/api/licenses/key/${licenseKey}`;
|
|
157
|
-
|
|
158
|
-
log.debug(`[magic-sessionmanager/license-guard] Fetching license from: ${url}`);
|
|
159
|
-
|
|
160
|
-
const response = await fetch(url, {
|
|
161
|
-
method: 'GET',
|
|
162
|
-
headers: { 'Content-Type': 'application/json' },
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const data = await response.json();
|
|
166
|
-
|
|
167
|
-
if (data.success && data.data) {
|
|
168
|
-
log.debug(`[magic-sessionmanager/license-guard] License fetched: ${data.data.email}, featurePremium: ${data.data.featurePremium}`);
|
|
169
|
-
return data.data;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
log.warn(`[magic-sessionmanager/license-guard] License API returned no data`);
|
|
173
|
-
return null;
|
|
174
|
-
} catch (error) {
|
|
175
|
-
log.error('[magic-sessionmanager/license-guard] Error fetching license by key:', error);
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
|
|
180
|
-
async pingLicense(licenseKey) {
|
|
181
|
-
try {
|
|
182
|
-
const deviceId = this.generateDeviceId();
|
|
183
|
-
const deviceName = this.getDeviceName();
|
|
184
|
-
const ipAddress = this.getIpAddress();
|
|
185
|
-
const userAgent = this.getUserAgent();
|
|
186
|
-
|
|
187
|
-
const licenseServerUrl = this.getLicenseServerUrl();
|
|
188
|
-
const response = await fetch(`${licenseServerUrl}/api/licenses/ping`, {
|
|
189
|
-
method: 'POST',
|
|
190
|
-
headers: { 'Content-Type': 'application/json' },
|
|
191
|
-
body: JSON.stringify({
|
|
192
|
-
licenseKey,
|
|
193
|
-
deviceId,
|
|
194
|
-
deviceName,
|
|
195
|
-
ipAddress,
|
|
196
|
-
userAgent,
|
|
197
|
-
pluginName: 'magic-sessionmanager',
|
|
198
|
-
}),
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
const data = await response.json();
|
|
202
|
-
return data.success ? data.data : null;
|
|
203
|
-
} catch (error) {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
async storeLicenseKey(licenseKey) {
|
|
209
|
-
const pluginStore = strapi.store({
|
|
210
|
-
type: 'plugin',
|
|
211
|
-
name: 'magic-sessionmanager'
|
|
212
|
-
});
|
|
213
|
-
await pluginStore.set({ key: 'licenseKey', value: licenseKey });
|
|
214
|
-
log.info(`[SUCCESS] License key stored: ${licenseKey.substring(0, 8)}...`);
|
|
215
|
-
},
|
|
216
|
-
|
|
217
|
-
startPinging(licenseKey, intervalMinutes = 15) {
|
|
218
|
-
log.info(`[TIME] Starting license pings every ${intervalMinutes} minutes`);
|
|
219
|
-
|
|
220
|
-
// Immediate ping
|
|
221
|
-
this.pingLicense(licenseKey);
|
|
222
|
-
|
|
223
|
-
const interval = setInterval(async () => {
|
|
224
|
-
try {
|
|
225
|
-
await this.pingLicense(licenseKey);
|
|
226
|
-
} catch (error) {
|
|
227
|
-
log.error('Ping error:', error);
|
|
228
|
-
}
|
|
229
|
-
}, intervalMinutes * 60 * 1000);
|
|
230
|
-
|
|
231
|
-
return interval;
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Initialize license guard
|
|
236
|
-
* Checks for existing license and starts pinging
|
|
237
|
-
*/
|
|
238
|
-
async initialize() {
|
|
239
|
-
try {
|
|
240
|
-
log.info('[SECURE] Initializing License Guard...');
|
|
241
|
-
|
|
242
|
-
// Check if license key exists in plugin store
|
|
243
|
-
const pluginStore = strapi.store({
|
|
244
|
-
type: 'plugin',
|
|
245
|
-
name: 'magic-sessionmanager'
|
|
246
|
-
});
|
|
247
|
-
const licenseKey = await pluginStore.get({ key: 'licenseKey' });
|
|
248
|
-
|
|
249
|
-
if (!licenseKey) {
|
|
250
|
-
log.info('[INFO] No license found - Running in demo mode');
|
|
251
|
-
return {
|
|
252
|
-
valid: false,
|
|
253
|
-
demo: true,
|
|
254
|
-
data: null,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Check last validation timestamp for grace period
|
|
259
|
-
const lastValidated = await pluginStore.get({ key: 'lastValidated' });
|
|
260
|
-
const now = new Date();
|
|
261
|
-
const gracePeriodHours = 24;
|
|
262
|
-
let withinGracePeriod = false;
|
|
263
|
-
|
|
264
|
-
if (lastValidated) {
|
|
265
|
-
const lastValidatedDate = new Date(lastValidated);
|
|
266
|
-
const hoursSinceValidation = (now.getTime() - lastValidatedDate.getTime()) / (1000 * 60 * 60);
|
|
267
|
-
withinGracePeriod = hoursSinceValidation < gracePeriodHours;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Verify license (allow grace period if we have a last validation)
|
|
271
|
-
const verification = await this.verifyLicense(licenseKey, withinGracePeriod);
|
|
272
|
-
|
|
273
|
-
if (verification.valid) {
|
|
274
|
-
// Update last validated timestamp
|
|
275
|
-
await pluginStore.set({
|
|
276
|
-
key: 'lastValidated',
|
|
277
|
-
value: now.toISOString()
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// Start automatic pinging
|
|
281
|
-
const pingInterval = this.startPinging(licenseKey, 15);
|
|
282
|
-
|
|
283
|
-
// Store interval globally so we can clean it up
|
|
284
|
-
strapi.licenseGuard = {
|
|
285
|
-
licenseKey,
|
|
286
|
-
pingInterval,
|
|
287
|
-
data: verification.data,
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
return {
|
|
291
|
-
valid: true,
|
|
292
|
-
demo: false,
|
|
293
|
-
data: verification.data,
|
|
294
|
-
gracePeriod: verification.gracePeriod || false,
|
|
295
|
-
};
|
|
296
|
-
} else {
|
|
297
|
-
log.error('[ERROR] License validation failed');
|
|
298
|
-
return {
|
|
299
|
-
valid: false,
|
|
300
|
-
demo: true,
|
|
301
|
-
error: 'Invalid or expired license',
|
|
302
|
-
data: null,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
} catch (error) {
|
|
306
|
-
log.error('[ERROR] Error initializing License Guard:', error);
|
|
307
|
-
return {
|
|
308
|
-
valid: false,
|
|
309
|
-
demo: true,
|
|
310
|
-
error: error.message,
|
|
311
|
-
data: null,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
};
|
|
316
|
-
};
|
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Notifications Service (ADVANCED Feature)
|
|
3
|
-
* Send email alerts for session events
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
module.exports = ({ strapi }) => ({
|
|
7
|
-
/**
|
|
8
|
-
* Get email templates from database settings
|
|
9
|
-
* Falls back to default hardcoded templates if not found
|
|
10
|
-
*/
|
|
11
|
-
async getEmailTemplates() {
|
|
12
|
-
try {
|
|
13
|
-
// Try to load templates from database
|
|
14
|
-
const pluginStore = strapi.store({
|
|
15
|
-
type: 'plugin',
|
|
16
|
-
name: 'magic-sessionmanager',
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const settings = await pluginStore.get({ key: 'settings' });
|
|
20
|
-
|
|
21
|
-
if (settings?.emailTemplates && Object.keys(settings.emailTemplates).length > 0) {
|
|
22
|
-
// Check if templates have content
|
|
23
|
-
const hasContent = Object.values(settings.emailTemplates).some(
|
|
24
|
-
template => template.html || template.text
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
if (hasContent) {
|
|
28
|
-
strapi.log.debug('[magic-sessionmanager/notifications] Using templates from database');
|
|
29
|
-
return settings.emailTemplates;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
} catch (err) {
|
|
33
|
-
strapi.log.warn('[magic-sessionmanager/notifications] Could not load templates from DB, using defaults:', err.message);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Default fallback templates
|
|
37
|
-
strapi.log.debug('[magic-sessionmanager/notifications] Using default fallback templates');
|
|
38
|
-
return {
|
|
39
|
-
suspiciousLogin: {
|
|
40
|
-
subject: '[ALERT] Suspicious Login Alert - Session Manager',
|
|
41
|
-
html: `
|
|
42
|
-
<html>
|
|
43
|
-
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
|
44
|
-
<div style="max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f9fafb; border-radius: 10px;">
|
|
45
|
-
<h2 style="color: #dc2626;">[ALERT] Suspicious Login Detected</h2>
|
|
46
|
-
<p>A potentially suspicious login was detected for your account.</p>
|
|
47
|
-
|
|
48
|
-
<div style="background: white; padding: 15px; border-radius: 8px; margin: 20px 0;">
|
|
49
|
-
<h3 style="margin-top: 0;">Account Information:</h3>
|
|
50
|
-
<ul>
|
|
51
|
-
<li><strong>Email:</strong> {{user.email}}</li>
|
|
52
|
-
<li><strong>Username:</strong> {{user.username}}</li>
|
|
53
|
-
</ul>
|
|
54
|
-
|
|
55
|
-
<h3>Login Details:</h3>
|
|
56
|
-
<ul>
|
|
57
|
-
<li><strong>Time:</strong> {{session.loginTime}}</li>
|
|
58
|
-
<li><strong>IP Address:</strong> {{session.ipAddress}}</li>
|
|
59
|
-
<li><strong>Location:</strong> {{geo.city}}, {{geo.country}}</li>
|
|
60
|
-
<li><strong>Timezone:</strong> {{geo.timezone}}</li>
|
|
61
|
-
<li><strong>Device:</strong> {{session.userAgent}}</li>
|
|
62
|
-
</ul>
|
|
63
|
-
|
|
64
|
-
<h3 style="color: #dc2626;">Security Alert:</h3>
|
|
65
|
-
<ul>
|
|
66
|
-
<li>VPN Detected: {{reason.isVpn}}</li>
|
|
67
|
-
<li>Proxy Detected: {{reason.isProxy}}</li>
|
|
68
|
-
<li>Threat Detected: {{reason.isThreat}}</li>
|
|
69
|
-
<li>Security Score: {{reason.securityScore}}/100</li>
|
|
70
|
-
</ul>
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
<p>If this was you, you can safely ignore this email. If you don't recognize this activity, please secure your account immediately.</p>
|
|
74
|
-
|
|
75
|
-
<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 20px 0;"/>
|
|
76
|
-
<p style="color: #666; font-size: 12px;">This is an automated security notification from Magic Session Manager.</p>
|
|
77
|
-
</div>
|
|
78
|
-
</body>
|
|
79
|
-
</html>`,
|
|
80
|
-
text: `[ALERT] Suspicious Login Detected\n\nA potentially suspicious login was detected for your account.\n\nAccount: {{user.email}}\nUsername: {{user.username}}\n\nLogin Details:\n- Time: {{session.loginTime}}\n- IP: {{session.ipAddress}}\n- Location: {{geo.city}}, {{geo.country}}\n\nSecurity: VPN={{reason.isVpn}}, Proxy={{reason.isProxy}}, Threat={{reason.isThreat}}, Score={{reason.securityScore}}/100`,
|
|
81
|
-
},
|
|
82
|
-
newLocation: {
|
|
83
|
-
subject: '[LOCATION] New Location Login Detected',
|
|
84
|
-
html: `<h2>[LOCATION] New Location Login</h2><p>Account: {{user.email}}</p><p>Time: {{session.loginTime}}</p><p>Location: {{geo.city}}, {{geo.country}}</p><p>IP: {{session.ipAddress}}</p>`,
|
|
85
|
-
text: `[LOCATION] New Location Login\n\nAccount: {{user.email}}\nTime: {{session.loginTime}}\nLocation: {{geo.city}}, {{geo.country}}\nIP: {{session.ipAddress}}`,
|
|
86
|
-
},
|
|
87
|
-
vpnProxy: {
|
|
88
|
-
subject: '[WARNING] VPN/Proxy Login Detected',
|
|
89
|
-
html: `<h2>[WARNING] VPN/Proxy Detected</h2><p>Account: {{user.email}}</p><p>Time: {{session.loginTime}}</p><p>IP: {{session.ipAddress}}</p><p>VPN: {{reason.isVpn}}, Proxy: {{reason.isProxy}}</p>`,
|
|
90
|
-
text: `[WARNING] VPN/Proxy Detected\n\nAccount: {{user.email}}\nTime: {{session.loginTime}}\nIP: {{session.ipAddress}}\nVPN: {{reason.isVpn}}, Proxy: {{reason.isProxy}}`,
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Replace template variables with actual values
|
|
97
|
-
*/
|
|
98
|
-
replaceVariables(template, data) {
|
|
99
|
-
let result = template;
|
|
100
|
-
|
|
101
|
-
// User variables
|
|
102
|
-
result = result.replace(/\{\{user\.email\}\}/g, data.user?.email || 'N/A');
|
|
103
|
-
result = result.replace(/\{\{user\.username\}\}/g, data.user?.username || 'N/A');
|
|
104
|
-
|
|
105
|
-
// Session variables
|
|
106
|
-
result = result.replace(/\{\{session\.loginTime\}\}/g,
|
|
107
|
-
data.session?.loginTime ? new Date(data.session.loginTime).toLocaleString() : 'N/A');
|
|
108
|
-
result = result.replace(/\{\{session\.ipAddress\}\}/g, data.session?.ipAddress || 'N/A');
|
|
109
|
-
result = result.replace(/\{\{session\.userAgent\}\}/g, data.session?.userAgent || 'N/A');
|
|
110
|
-
|
|
111
|
-
// Geo variables
|
|
112
|
-
result = result.replace(/\{\{geo\.city\}\}/g, data.geoData?.city || 'Unknown');
|
|
113
|
-
result = result.replace(/\{\{geo\.country\}\}/g, data.geoData?.country || 'Unknown');
|
|
114
|
-
result = result.replace(/\{\{geo\.timezone\}\}/g, data.geoData?.timezone || 'Unknown');
|
|
115
|
-
|
|
116
|
-
// Reason variables
|
|
117
|
-
result = result.replace(/\{\{reason\.isVpn\}\}/g, data.reason?.isVpn ? 'Yes' : 'No');
|
|
118
|
-
result = result.replace(/\{\{reason\.isProxy\}\}/g, data.reason?.isProxy ? 'Yes' : 'No');
|
|
119
|
-
result = result.replace(/\{\{reason\.isThreat\}\}/g, data.reason?.isThreat ? 'Yes' : 'No');
|
|
120
|
-
result = result.replace(/\{\{reason\.securityScore\}\}/g, data.reason?.securityScore || '0');
|
|
121
|
-
|
|
122
|
-
return result;
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Send suspicious login alert
|
|
127
|
-
* @param {Object} params - { user, session, reason, geoData }
|
|
128
|
-
*/
|
|
129
|
-
async sendSuspiciousLoginAlert({ user, session, reason, geoData }) {
|
|
130
|
-
try {
|
|
131
|
-
// Get templates from database (or defaults)
|
|
132
|
-
const templates = await this.getEmailTemplates();
|
|
133
|
-
const template = templates.suspiciousLogin;
|
|
134
|
-
|
|
135
|
-
// Prepare data for variable replacement
|
|
136
|
-
const data = { user, session, reason, geoData };
|
|
137
|
-
|
|
138
|
-
// Replace variables in template
|
|
139
|
-
const htmlContent = this.replaceVariables(template.html, data);
|
|
140
|
-
const textContent = this.replaceVariables(template.text, data);
|
|
141
|
-
|
|
142
|
-
await strapi.plugins['email'].services.email.send({
|
|
143
|
-
to: user.email,
|
|
144
|
-
subject: template.subject,
|
|
145
|
-
html: htmlContent,
|
|
146
|
-
text: textContent,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
strapi.log.info(`[magic-sessionmanager/notifications] Suspicious login alert sent to ${user.email}`);
|
|
150
|
-
return true;
|
|
151
|
-
} catch (err) {
|
|
152
|
-
strapi.log.error('[magic-sessionmanager/notifications] Error sending email:', err);
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Send new location login alert
|
|
159
|
-
* @param {Object} params - { user, session, geoData }
|
|
160
|
-
*/
|
|
161
|
-
async sendNewLocationAlert({ user, session, geoData }) {
|
|
162
|
-
try {
|
|
163
|
-
// Get templates from database (or defaults)
|
|
164
|
-
const templates = await this.getEmailTemplates();
|
|
165
|
-
const template = templates.newLocation;
|
|
166
|
-
|
|
167
|
-
// Prepare data for variable replacement
|
|
168
|
-
const data = { user, session, geoData, reason: {} };
|
|
169
|
-
|
|
170
|
-
// Replace variables in template
|
|
171
|
-
const htmlContent = this.replaceVariables(template.html, data);
|
|
172
|
-
const textContent = this.replaceVariables(template.text, data);
|
|
173
|
-
|
|
174
|
-
await strapi.plugins['email'].services.email.send({
|
|
175
|
-
to: user.email,
|
|
176
|
-
subject: template.subject,
|
|
177
|
-
html: htmlContent,
|
|
178
|
-
text: textContent,
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
strapi.log.info(`[magic-sessionmanager/notifications] New location alert sent to ${user.email}`);
|
|
182
|
-
return true;
|
|
183
|
-
} catch (err) {
|
|
184
|
-
strapi.log.error('[magic-sessionmanager/notifications] Error sending new location email:', err);
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Send VPN/Proxy login alert
|
|
191
|
-
* @param {Object} params - { user, session, reason, geoData }
|
|
192
|
-
*/
|
|
193
|
-
async sendVpnProxyAlert({ user, session, reason, geoData }) {
|
|
194
|
-
try {
|
|
195
|
-
// Get templates from database (or defaults)
|
|
196
|
-
const templates = await this.getEmailTemplates();
|
|
197
|
-
const template = templates.vpnProxy;
|
|
198
|
-
|
|
199
|
-
// Prepare data for variable replacement
|
|
200
|
-
const data = { user, session, reason, geoData };
|
|
201
|
-
|
|
202
|
-
// Replace variables in template
|
|
203
|
-
const htmlContent = this.replaceVariables(template.html, data);
|
|
204
|
-
const textContent = this.replaceVariables(template.text, data);
|
|
205
|
-
|
|
206
|
-
await strapi.plugins['email'].services.email.send({
|
|
207
|
-
to: user.email,
|
|
208
|
-
subject: template.subject,
|
|
209
|
-
html: htmlContent,
|
|
210
|
-
text: textContent,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
strapi.log.info(`[magic-sessionmanager/notifications] VPN/Proxy alert sent to ${user.email}`);
|
|
214
|
-
return true;
|
|
215
|
-
} catch (err) {
|
|
216
|
-
strapi.log.error('[magic-sessionmanager/notifications] Error sending VPN/Proxy email:', err);
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Send webhook notification
|
|
223
|
-
* @param {Object} params - { event, data, webhookUrl }
|
|
224
|
-
*/
|
|
225
|
-
async sendWebhook({ event, data, webhookUrl }) {
|
|
226
|
-
try {
|
|
227
|
-
const payload = {
|
|
228
|
-
event,
|
|
229
|
-
timestamp: new Date().toISOString(),
|
|
230
|
-
data,
|
|
231
|
-
source: 'magic-sessionmanager',
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
const response = await fetch(webhookUrl, {
|
|
235
|
-
method: 'POST',
|
|
236
|
-
headers: {
|
|
237
|
-
'Content-Type': 'application/json',
|
|
238
|
-
'User-Agent': 'Strapi-Magic-SessionManager-Webhook/1.0',
|
|
239
|
-
},
|
|
240
|
-
body: JSON.stringify(payload),
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
if (response.ok) {
|
|
244
|
-
strapi.log.info(`[magic-sessionmanager/notifications] Webhook sent: ${event}`);
|
|
245
|
-
return true;
|
|
246
|
-
} else {
|
|
247
|
-
strapi.log.warn(`[magic-sessionmanager/notifications] Webhook failed: ${response.status}`);
|
|
248
|
-
return false;
|
|
249
|
-
}
|
|
250
|
-
} catch (err) {
|
|
251
|
-
strapi.log.error('[magic-sessionmanager/notifications] Webhook error:', err);
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
},
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Format webhook for Discord
|
|
258
|
-
* @param {Object} params - { event, session, user, geoData }
|
|
259
|
-
*/
|
|
260
|
-
formatDiscordWebhook({ event, session, user, geoData }) {
|
|
261
|
-
const embed = {
|
|
262
|
-
title: this.getEventTitle(event),
|
|
263
|
-
color: this.getEventColor(event),
|
|
264
|
-
fields: [
|
|
265
|
-
{ name: 'User', value: `${user.email}\n${user.username || 'N/A'}`, inline: true },
|
|
266
|
-
{ name: 'IP', value: session.ipAddress, inline: true },
|
|
267
|
-
{ name: 'Time', value: new Date(session.loginTime).toLocaleString(), inline: false },
|
|
268
|
-
],
|
|
269
|
-
timestamp: new Date().toISOString(),
|
|
270
|
-
footer: { text: 'Magic Session Manager' },
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
if (geoData) {
|
|
274
|
-
embed.fields.push({
|
|
275
|
-
name: '[LOCATION] Location',
|
|
276
|
-
value: `${geoData.country_flag} ${geoData.city}, ${geoData.country}`,
|
|
277
|
-
inline: true,
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
if (geoData.isVpn || geoData.isProxy || geoData.isThreat) {
|
|
281
|
-
const warnings = [];
|
|
282
|
-
if (geoData.isVpn) warnings.push('VPN');
|
|
283
|
-
if (geoData.isProxy) warnings.push('Proxy');
|
|
284
|
-
if (geoData.isThreat) warnings.push('Threat');
|
|
285
|
-
|
|
286
|
-
embed.fields.push({
|
|
287
|
-
name: '[WARNING] Security',
|
|
288
|
-
value: `${warnings.join(', ')} detected\nScore: ${geoData.securityScore}/100`,
|
|
289
|
-
inline: true,
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return { embeds: [embed] };
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
getEventTitle(event) {
|
|
298
|
-
const titles = {
|
|
299
|
-
'login.suspicious': '[ALERT] Suspicious Login',
|
|
300
|
-
'login.new_location': '[LOCATION] New Location Login',
|
|
301
|
-
'login.vpn': '[WARNING] VPN Login Detected',
|
|
302
|
-
'login.threat': '[THREAT] Threat IP Login',
|
|
303
|
-
'session.terminated': '[INFO] Session Terminated',
|
|
304
|
-
};
|
|
305
|
-
return titles[event] || '[STATS] Session Event';
|
|
306
|
-
},
|
|
307
|
-
|
|
308
|
-
getEventColor(event) {
|
|
309
|
-
const colors = {
|
|
310
|
-
'login.suspicious': 0xFF0000, // Red
|
|
311
|
-
'login.new_location': 0xFFA500, // Orange
|
|
312
|
-
'login.vpn': 0xFF6B6B, // Light Red
|
|
313
|
-
'login.threat': 0x8B0000, // Dark Red
|
|
314
|
-
'session.terminated': 0x808080, // Gray
|
|
315
|
-
};
|
|
316
|
-
return colors[event] || 0x5865F2; // Discord Blue
|
|
317
|
-
},
|
|
318
|
-
});
|
|
319
|
-
|