strapi-plugin-magic-sessionmanager 2.0.0 → 2.0.2

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.
Files changed (52) hide show
  1. package/admin/jsconfig.json +10 -0
  2. package/admin/src/components/Initializer.jsx +11 -0
  3. package/admin/src/components/LicenseGuard.jsx +591 -0
  4. package/admin/src/components/OnlineUsersWidget.jsx +208 -0
  5. package/admin/src/components/PluginIcon.jsx +8 -0
  6. package/admin/src/components/SessionDetailModal.jsx +445 -0
  7. package/admin/src/components/SessionInfoCard.jsx +151 -0
  8. package/admin/src/components/SessionInfoPanel.jsx +375 -0
  9. package/admin/src/components/index.jsx +5 -0
  10. package/admin/src/hooks/useLicense.js +103 -0
  11. package/admin/src/index.js +137 -0
  12. package/admin/src/pages/ActiveSessions.jsx +12 -0
  13. package/admin/src/pages/Analytics.jsx +735 -0
  14. package/admin/src/pages/App.jsx +12 -0
  15. package/admin/src/pages/HomePage.jsx +1248 -0
  16. package/admin/src/pages/License.jsx +603 -0
  17. package/admin/src/pages/Settings.jsx +1497 -0
  18. package/admin/src/pages/SettingsNew.jsx +1204 -0
  19. package/admin/src/pages/index.jsx +3 -0
  20. package/admin/src/pluginId.js +3 -0
  21. package/admin/src/translations/de.json +20 -0
  22. package/admin/src/translations/en.json +20 -0
  23. package/admin/src/utils/getTranslation.js +5 -0
  24. package/admin/src/utils/index.js +2 -0
  25. package/admin/src/utils/parseUserAgent.js +79 -0
  26. package/package.json +3 -1
  27. package/server/jsconfig.json +10 -0
  28. package/server/src/bootstrap.js +297 -0
  29. package/server/src/config/index.js +20 -0
  30. package/server/src/content-types/index.js +9 -0
  31. package/server/src/content-types/session/schema.json +76 -0
  32. package/server/src/controllers/controller.js +11 -0
  33. package/server/src/controllers/index.js +11 -0
  34. package/server/src/controllers/license.js +266 -0
  35. package/server/src/controllers/session.js +362 -0
  36. package/server/src/controllers/settings.js +122 -0
  37. package/server/src/destroy.js +18 -0
  38. package/server/src/index.js +21 -0
  39. package/server/src/middlewares/index.js +5 -0
  40. package/server/src/middlewares/last-seen.js +56 -0
  41. package/server/src/policies/index.js +3 -0
  42. package/server/src/register.js +32 -0
  43. package/server/src/routes/admin.js +149 -0
  44. package/server/src/routes/content-api.js +51 -0
  45. package/server/src/routes/index.js +9 -0
  46. package/server/src/services/geolocation.js +180 -0
  47. package/server/src/services/index.js +13 -0
  48. package/server/src/services/license-guard.js +308 -0
  49. package/server/src/services/notifications.js +319 -0
  50. package/server/src/services/service.js +7 -0
  51. package/server/src/services/session.js +345 -0
  52. package/server/src/utils/getClientIp.js +118 -0
@@ -0,0 +1,308 @@
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
+
9
+ // FIXED LICENSE SERVER URL
10
+ const LICENSE_SERVER_URL = 'https://magicapi.fitlex.me';
11
+
12
+ module.exports = ({ strapi }) => ({
13
+ /**
14
+ * Get license server URL
15
+ */
16
+ getLicenseServerUrl() {
17
+ return LICENSE_SERVER_URL;
18
+ },
19
+
20
+ /**
21
+ * Generate device ID
22
+ */
23
+ generateDeviceId() {
24
+ try {
25
+ const networkInterfaces = os.networkInterfaces();
26
+ const macAddresses = [];
27
+
28
+ Object.values(networkInterfaces).forEach(interfaces => {
29
+ interfaces?.forEach(iface => {
30
+ if (iface.mac && iface.mac !== '00:00:00:00:00:00') {
31
+ macAddresses.push(iface.mac);
32
+ }
33
+ });
34
+ });
35
+
36
+ const identifier = `${macAddresses.join('-')}-${os.hostname()}`;
37
+ return crypto.createHash('sha256').update(identifier).digest('hex').substring(0, 32);
38
+ } catch (error) {
39
+ return crypto.randomBytes(16).toString('hex');
40
+ }
41
+ },
42
+
43
+ getDeviceName() {
44
+ try {
45
+ return os.hostname() || 'Unknown Device';
46
+ } catch (error) {
47
+ return 'Unknown Device';
48
+ }
49
+ },
50
+
51
+ getIpAddress() {
52
+ try {
53
+ const networkInterfaces = os.networkInterfaces();
54
+ for (const name of Object.keys(networkInterfaces)) {
55
+ const interfaces = networkInterfaces[name];
56
+ if (interfaces) {
57
+ for (const iface of interfaces) {
58
+ if (iface.family === 'IPv4' && !iface.internal) {
59
+ return iface.address;
60
+ }
61
+ }
62
+ }
63
+ }
64
+ return '127.0.0.1';
65
+ } catch (error) {
66
+ return '127.0.0.1';
67
+ }
68
+ },
69
+
70
+ getUserAgent() {
71
+ return `Strapi/${strapi.config.get('info.strapi') || '5.0.0'} Node/${process.version} ${os.platform()}/${os.release()}`;
72
+ },
73
+
74
+ async createLicense({ email, firstName, lastName }) {
75
+ try {
76
+ const deviceId = this.generateDeviceId();
77
+ const deviceName = this.getDeviceName();
78
+ const ipAddress = this.getIpAddress();
79
+ const userAgent = this.getUserAgent();
80
+
81
+ const licenseServerUrl = this.getLicenseServerUrl();
82
+ const response = await fetch(`${licenseServerUrl}/api/licenses/create`, {
83
+ method: 'POST',
84
+ headers: { 'Content-Type': 'application/json' },
85
+ body: JSON.stringify({
86
+ email,
87
+ firstName,
88
+ lastName,
89
+ deviceName,
90
+ deviceId,
91
+ ipAddress,
92
+ userAgent,
93
+ pluginName: 'magic-sessionmanager',
94
+ productName: 'Magic Session Manager - Premium Session Tracking',
95
+ }),
96
+ });
97
+
98
+ const data = await response.json();
99
+
100
+ if (data.success) {
101
+ strapi.log.info('[magic-sessionmanager] ✅ License created:', data.data.licenseKey);
102
+ return data.data;
103
+ } else {
104
+ strapi.log.error('[magic-sessionmanager] ❌ License creation failed:', data);
105
+ return null;
106
+ }
107
+ } catch (error) {
108
+ strapi.log.error('[magic-sessionmanager] ❌ Error creating license:', error);
109
+ return null;
110
+ }
111
+ },
112
+
113
+ async verifyLicense(licenseKey, allowGracePeriod = false) {
114
+ try {
115
+ const controller = new AbortController();
116
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
117
+
118
+ const licenseServerUrl = this.getLicenseServerUrl();
119
+ const response = await fetch(`${licenseServerUrl}/api/licenses/verify`, {
120
+ method: 'POST',
121
+ headers: { 'Content-Type': 'application/json' },
122
+ body: JSON.stringify({
123
+ licenseKey,
124
+ pluginName: 'magic-sessionmanager',
125
+ productName: 'Magic Session Manager - Premium Session Tracking',
126
+ }),
127
+ signal: controller.signal,
128
+ });
129
+
130
+ clearTimeout(timeoutId);
131
+ const data = await response.json();
132
+
133
+ if (data.success && data.data) {
134
+ return { valid: true, data: data.data, gracePeriod: false };
135
+ } else {
136
+ return { valid: false, data: null };
137
+ }
138
+ } catch (error) {
139
+ if (allowGracePeriod) {
140
+ return { valid: true, data: null, gracePeriod: true };
141
+ }
142
+ return { valid: false, data: null };
143
+ }
144
+ },
145
+
146
+ async getLicenseByKey(licenseKey) {
147
+ try {
148
+ const licenseServerUrl = this.getLicenseServerUrl();
149
+ const url = `${licenseServerUrl}/api/licenses/key/${licenseKey}`;
150
+
151
+ strapi.log.debug(`[magic-sessionmanager/license-guard] Fetching license from: ${url}`);
152
+
153
+ const response = await fetch(url, {
154
+ method: 'GET',
155
+ headers: { 'Content-Type': 'application/json' },
156
+ });
157
+
158
+ const data = await response.json();
159
+
160
+ if (data.success && data.data) {
161
+ strapi.log.debug(`[magic-sessionmanager/license-guard] License fetched: ${data.data.email}, featurePremium: ${data.data.featurePremium}`);
162
+ return data.data;
163
+ }
164
+
165
+ strapi.log.warn(`[magic-sessionmanager/license-guard] License API returned no data`);
166
+ return null;
167
+ } catch (error) {
168
+ strapi.log.error('[magic-sessionmanager/license-guard] Error fetching license by key:', error);
169
+ return null;
170
+ }
171
+ },
172
+
173
+ async pingLicense(licenseKey) {
174
+ try {
175
+ const deviceId = this.generateDeviceId();
176
+ const deviceName = this.getDeviceName();
177
+ const ipAddress = this.getIpAddress();
178
+ const userAgent = this.getUserAgent();
179
+
180
+ const licenseServerUrl = this.getLicenseServerUrl();
181
+ const response = await fetch(`${licenseServerUrl}/api/licenses/ping`, {
182
+ method: 'POST',
183
+ headers: { 'Content-Type': 'application/json' },
184
+ body: JSON.stringify({
185
+ licenseKey,
186
+ deviceId,
187
+ deviceName,
188
+ ipAddress,
189
+ userAgent,
190
+ pluginName: 'magic-sessionmanager',
191
+ }),
192
+ });
193
+
194
+ const data = await response.json();
195
+ return data.success ? data.data : null;
196
+ } catch (error) {
197
+ return null;
198
+ }
199
+ },
200
+
201
+ async storeLicenseKey(licenseKey) {
202
+ const pluginStore = strapi.store({
203
+ type: 'plugin',
204
+ name: 'magic-sessionmanager'
205
+ });
206
+ await pluginStore.set({ key: 'licenseKey', value: licenseKey });
207
+ strapi.log.info(`[magic-sessionmanager] ✅ License key stored: ${licenseKey.substring(0, 8)}...`);
208
+ },
209
+
210
+ startPinging(licenseKey, intervalMinutes = 15) {
211
+ strapi.log.info(`[magic-sessionmanager] ⏰ Starting license pings every ${intervalMinutes} minutes`);
212
+
213
+ // Immediate ping
214
+ this.pingLicense(licenseKey);
215
+
216
+ const interval = setInterval(async () => {
217
+ try {
218
+ await this.pingLicense(licenseKey);
219
+ } catch (error) {
220
+ strapi.log.error('[magic-sessionmanager] Ping error:', error);
221
+ }
222
+ }, intervalMinutes * 60 * 1000);
223
+
224
+ return interval;
225
+ },
226
+
227
+ /**
228
+ * Initialize license guard
229
+ * Checks for existing license and starts pinging
230
+ */
231
+ async initialize() {
232
+ try {
233
+ strapi.log.info('[magic-sessionmanager] 🔐 Initializing License Guard...');
234
+
235
+ // Check if license key exists in plugin store
236
+ const pluginStore = strapi.store({
237
+ type: 'plugin',
238
+ name: 'magic-sessionmanager'
239
+ });
240
+ const licenseKey = await pluginStore.get({ key: 'licenseKey' });
241
+
242
+ if (!licenseKey) {
243
+ strapi.log.info('[magic-sessionmanager] ℹ️ No license found - Running in demo mode');
244
+ return {
245
+ valid: false,
246
+ demo: true,
247
+ data: null,
248
+ };
249
+ }
250
+
251
+ // Check last validation timestamp for grace period
252
+ const lastValidated = await pluginStore.get({ key: 'lastValidated' });
253
+ const now = new Date();
254
+ const gracePeriodHours = 24;
255
+ let withinGracePeriod = false;
256
+
257
+ if (lastValidated) {
258
+ const lastValidatedDate = new Date(lastValidated);
259
+ const hoursSinceValidation = (now.getTime() - lastValidatedDate.getTime()) / (1000 * 60 * 60);
260
+ withinGracePeriod = hoursSinceValidation < gracePeriodHours;
261
+ }
262
+
263
+ // Verify license (allow grace period if we have a last validation)
264
+ const verification = await this.verifyLicense(licenseKey, withinGracePeriod);
265
+
266
+ if (verification.valid) {
267
+ // Update last validated timestamp
268
+ await pluginStore.set({
269
+ key: 'lastValidated',
270
+ value: now.toISOString()
271
+ });
272
+
273
+ // Start automatic pinging
274
+ const pingInterval = this.startPinging(licenseKey, 15);
275
+
276
+ // Store interval globally so we can clean it up
277
+ strapi.licenseGuard = {
278
+ licenseKey,
279
+ pingInterval,
280
+ data: verification.data,
281
+ };
282
+
283
+ return {
284
+ valid: true,
285
+ demo: false,
286
+ data: verification.data,
287
+ gracePeriod: verification.gracePeriod || false,
288
+ };
289
+ } else {
290
+ strapi.log.error('[magic-sessionmanager] ❌ License validation failed');
291
+ return {
292
+ valid: false,
293
+ demo: true,
294
+ error: 'Invalid or expired license',
295
+ data: null,
296
+ };
297
+ }
298
+ } catch (error) {
299
+ strapi.log.error('[magic-sessionmanager] ❌ Error initializing License Guard:', error);
300
+ return {
301
+ valid: false,
302
+ demo: true,
303
+ error: error.message,
304
+ data: null,
305
+ };
306
+ }
307
+ },
308
+ });
@@ -0,0 +1,319 @@
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: '🚨 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;">🚨 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: `🚨 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: '📍 New Location Login Detected',
84
+ html: `<h2>📍 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: `📍 New Location Login\n\nAccount: {{user.email}}\nTime: {{session.loginTime}}\nLocation: {{geo.city}}, {{geo.country}}\nIP: {{session.ipAddress}}`,
86
+ },
87
+ vpnProxy: {
88
+ subject: '⚠️ VPN/Proxy Login Detected',
89
+ html: `<h2>⚠️ 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: `⚠️ 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',
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: '⚠️ 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': '🚨 Suspicious Login',
300
+ 'login.new_location': '📍 New Location Login',
301
+ 'login.vpn': '🔴 VPN Login Detected',
302
+ 'login.threat': '⛔ Threat IP Login',
303
+ 'session.terminated': '🔴 Session Terminated',
304
+ };
305
+ return titles[event] || '📊 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
+
@@ -0,0 +1,7 @@
1
+ const service = ({ strapi }) => ({
2
+ getWelcomeMessage() {
3
+ return 'Welcome to Strapi 🚀';
4
+ },
5
+ });
6
+
7
+ export default service;