strapi-plugin-magic-sessionmanager 3.7.0 → 4.0.0
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/README.md +90 -0
- package/admin/src/hooks/useLicense.js +1 -1
- package/admin/src/index.js +2 -2
- package/admin/src/pages/Settings.jsx +0 -1
- package/admin/src/utils/parseUserAgent.js +1 -1
- package/dist/_chunks/{Analytics-Bi-vcT63.js → Analytics-ioaeEh-E.js} +2 -2
- package/dist/_chunks/{Analytics-BM9i88xu.mjs → Analytics-mYu_uGwU.mjs} +2 -2
- package/dist/_chunks/{App-DcnJOCL9.mjs → App-BXpIS12l.mjs} +2 -2
- package/dist/_chunks/{App-BbiNy_cT.js → App-DdnUYWbC.js} +2 -2
- package/dist/_chunks/{License-DsxP-MAL.mjs → License-C03C2j9P.mjs} +1 -1
- package/dist/_chunks/{License-kYo8j2yl.js → License-DZYrOgcx.js} +1 -1
- package/dist/_chunks/{Settings-C3sW9eBD.mjs → Settings-0ocB3qHk.mjs} +2 -2
- package/dist/_chunks/{Settings-jW0TOE_d.js → Settings-C6_CqpCC.js} +2 -2
- package/dist/_chunks/{index-DG9XeVSg.mjs → index-DBRS3kt5.mjs} +7 -7
- package/dist/_chunks/{index-Dr2HT-Dd.js → index-DC8Y0qxx.js} +7 -7
- package/dist/_chunks/{useLicense-DOkJX-tk.mjs → useLicense-DSLL9n3Y.mjs} +2 -2
- package/dist/_chunks/{useLicense-BL_3bX9O.js → useLicense-qgGfMvse.js} +2 -2
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +117 -95
- package/dist/server/index.mjs +117 -95
- package/package.json +1 -1
- package/server/src/bootstrap.js +32 -26
- package/server/src/controllers/license.js +4 -4
- package/server/src/controllers/session.js +10 -6
- package/server/src/destroy.js +1 -1
- package/server/src/middlewares/last-seen.js +8 -3
- package/server/src/register.js +4 -4
- package/server/src/services/geolocation.js +4 -2
- package/server/src/services/license-guard.js +13 -10
- package/server/src/services/notifications.js +10 -10
- package/server/src/services/service.js +1 -1
- package/server/src/services/session.js +41 -31
- package/server/src/utils/encryption.js +1 -1
package/package.json
CHANGED
package/server/src/bootstrap.js
CHANGED
|
@@ -4,14 +4,19 @@
|
|
|
4
4
|
* Bootstrap: Mount middleware for session tracking
|
|
5
5
|
* Sessions are managed via plugin::magic-sessionmanager.session content type
|
|
6
6
|
*
|
|
7
|
+
* [SUCCESS] Migrated to strapi.documents() API (Strapi v5 Best Practice)
|
|
8
|
+
*
|
|
7
9
|
* NOTE: For multi-instance deployments, consider Redis locks or session store
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
const getClientIp = require('./utils/getClientIp');
|
|
11
13
|
const { encryptToken, decryptToken } = require('./utils/encryption');
|
|
12
14
|
|
|
15
|
+
const SESSION_UID = 'plugin::magic-sessionmanager.session';
|
|
16
|
+
const USER_UID = 'plugin::users-permissions.user';
|
|
17
|
+
|
|
13
18
|
module.exports = async ({ strapi }) => {
|
|
14
|
-
strapi.log.info('[magic-sessionmanager]
|
|
19
|
+
strapi.log.info('[magic-sessionmanager] [START] Bootstrap starting...');
|
|
15
20
|
|
|
16
21
|
try {
|
|
17
22
|
// Initialize License Guard
|
|
@@ -23,7 +28,7 @@ module.exports = async ({ strapi }) => {
|
|
|
23
28
|
|
|
24
29
|
if (!licenseStatus.valid) {
|
|
25
30
|
strapi.log.error('╔════════════════════════════════════════════════════════════════╗');
|
|
26
|
-
strapi.log.error('║
|
|
31
|
+
strapi.log.error('║ [ERROR] SESSION MANAGER - NO VALID LICENSE ║');
|
|
27
32
|
strapi.log.error('║ ║');
|
|
28
33
|
strapi.log.error('║ This plugin requires a valid license to operate. ║');
|
|
29
34
|
strapi.log.error('║ Please activate your license via Admin UI: ║');
|
|
@@ -40,7 +45,7 @@ module.exports = async ({ strapi }) => {
|
|
|
40
45
|
const storedKey = await pluginStore.get({ key: 'licenseKey' });
|
|
41
46
|
|
|
42
47
|
strapi.log.info('╔════════════════════════════════════════════════════════════════╗');
|
|
43
|
-
strapi.log.info('║
|
|
48
|
+
strapi.log.info('║ [SUCCESS] SESSION MANAGER LICENSE ACTIVE ║');
|
|
44
49
|
strapi.log.info('║ ║');
|
|
45
50
|
|
|
46
51
|
if (licenseStatus.data) {
|
|
@@ -53,7 +58,7 @@ module.exports = async ({ strapi }) => {
|
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
strapi.log.info('║ ║');
|
|
56
|
-
strapi.log.info('║
|
|
61
|
+
strapi.log.info('║ [RELOAD] Auto-pinging every 15 minutes ║');
|
|
57
62
|
strapi.log.info('╚════════════════════════════════════════════════════════════════╝');
|
|
58
63
|
}
|
|
59
64
|
}, 3000); // Wait 3 seconds for API to be ready
|
|
@@ -80,7 +85,7 @@ module.exports = async ({ strapi }) => {
|
|
|
80
85
|
}
|
|
81
86
|
}, cleanupInterval);
|
|
82
87
|
|
|
83
|
-
strapi.log.info('[magic-sessionmanager]
|
|
88
|
+
strapi.log.info('[magic-sessionmanager] [TIME] Periodic cleanup scheduled (every 30 minutes)');
|
|
84
89
|
|
|
85
90
|
// Store interval handle for cleanup on shutdown
|
|
86
91
|
if (!strapi.sessionManagerIntervals) {
|
|
@@ -104,7 +109,7 @@ module.exports = async ({ strapi }) => {
|
|
|
104
109
|
|
|
105
110
|
// Find session by decrypting tokens and matching
|
|
106
111
|
// Since tokens are encrypted, we need to get all active sessions and check each one
|
|
107
|
-
const allSessions = await strapi.
|
|
112
|
+
const allSessions = await strapi.documents(SESSION_UID).findMany( {
|
|
108
113
|
filters: {
|
|
109
114
|
isActive: true,
|
|
110
115
|
},
|
|
@@ -122,8 +127,8 @@ module.exports = async ({ strapi }) => {
|
|
|
122
127
|
});
|
|
123
128
|
|
|
124
129
|
if (matchingSession) {
|
|
125
|
-
await sessionService.terminateSession({ sessionId: matchingSession.
|
|
126
|
-
strapi.log.info(`[magic-sessionmanager]
|
|
130
|
+
await sessionService.terminateSession({ sessionId: matchingSession.documentId });
|
|
131
|
+
strapi.log.info(`[magic-sessionmanager] [LOGOUT] Logout via /api/auth/logout - Session ${matchingSession.documentId} terminated`);
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
ctx.status = 200;
|
|
@@ -139,7 +144,7 @@ module.exports = async ({ strapi }) => {
|
|
|
139
144
|
},
|
|
140
145
|
}]);
|
|
141
146
|
|
|
142
|
-
strapi.log.info('[magic-sessionmanager]
|
|
147
|
+
strapi.log.info('[magic-sessionmanager] [SUCCESS] /api/auth/logout route registered');
|
|
143
148
|
|
|
144
149
|
// Middleware to intercept logins
|
|
145
150
|
strapi.server.use(async (ctx, next) => {
|
|
@@ -158,7 +163,7 @@ module.exports = async ({ strapi }) => {
|
|
|
158
163
|
const ip = getClientIp(ctx);
|
|
159
164
|
const userAgent = ctx.request.headers?.['user-agent'] || ctx.request.header?.['user-agent'] || 'unknown';
|
|
160
165
|
|
|
161
|
-
strapi.log.info(`[magic-sessionmanager]
|
|
166
|
+
strapi.log.info(`[magic-sessionmanager] [CHECK] Login detected! User: ${user.id} (${user.email || user.username}) from IP: ${ip}`);
|
|
162
167
|
|
|
163
168
|
// Get config
|
|
164
169
|
const config = strapi.config.get('plugin::magic-sessionmanager') || {};
|
|
@@ -216,7 +221,7 @@ module.exports = async ({ strapi }) => {
|
|
|
216
221
|
|
|
217
222
|
// Block if needed
|
|
218
223
|
if (shouldBlock) {
|
|
219
|
-
strapi.log.warn(`[magic-sessionmanager]
|
|
224
|
+
strapi.log.warn(`[magic-sessionmanager] [BLOCKED] Blocking login: ${blockReason}`);
|
|
220
225
|
|
|
221
226
|
// Don't create session, return error
|
|
222
227
|
ctx.status = 403;
|
|
@@ -239,7 +244,7 @@ module.exports = async ({ strapi }) => {
|
|
|
239
244
|
refreshToken: ctx.body.refreshToken, // Store Refresh Token (encrypted) if exists
|
|
240
245
|
});
|
|
241
246
|
|
|
242
|
-
strapi.log.info(`[magic-sessionmanager]
|
|
247
|
+
strapi.log.info(`[magic-sessionmanager] [SUCCESS] Session created for user ${user.id} (IP: ${ip})`);
|
|
243
248
|
|
|
244
249
|
// Advanced: Send notifications
|
|
245
250
|
if (geoData && (config.enableEmailAlerts || config.enableWebhooks)) {
|
|
@@ -286,13 +291,13 @@ module.exports = async ({ strapi }) => {
|
|
|
286
291
|
}
|
|
287
292
|
}
|
|
288
293
|
} catch (err) {
|
|
289
|
-
strapi.log.error('[magic-sessionmanager]
|
|
294
|
+
strapi.log.error('[magic-sessionmanager] [ERROR] Error creating session:', err);
|
|
290
295
|
// Don't throw - login should still succeed even if session creation fails
|
|
291
296
|
}
|
|
292
297
|
}
|
|
293
298
|
});
|
|
294
299
|
|
|
295
|
-
strapi.log.info('[magic-sessionmanager]
|
|
300
|
+
strapi.log.info('[magic-sessionmanager] [SUCCESS] Login/Logout interceptor middleware mounted');
|
|
296
301
|
|
|
297
302
|
// Middleware to block refresh token requests for terminated sessions
|
|
298
303
|
strapi.server.use(async (ctx, next) => {
|
|
@@ -305,7 +310,7 @@ module.exports = async ({ strapi }) => {
|
|
|
305
310
|
|
|
306
311
|
if (refreshToken) {
|
|
307
312
|
// Find session with this refresh token
|
|
308
|
-
const allSessions = await strapi.
|
|
313
|
+
const allSessions = await strapi.documents(SESSION_UID).findMany( {
|
|
309
314
|
filters: {
|
|
310
315
|
isActive: true,
|
|
311
316
|
},
|
|
@@ -323,8 +328,8 @@ module.exports = async ({ strapi }) => {
|
|
|
323
328
|
});
|
|
324
329
|
|
|
325
330
|
if (!matchingSession) {
|
|
326
|
-
// No active session with this refresh token
|
|
327
|
-
strapi.log.warn('[magic-sessionmanager]
|
|
331
|
+
// No active session with this refresh token - Block!
|
|
332
|
+
strapi.log.warn('[magic-sessionmanager] [BLOCKED] Blocked refresh token request - no active session');
|
|
328
333
|
ctx.status = 401;
|
|
329
334
|
ctx.body = {
|
|
330
335
|
error: {
|
|
@@ -336,7 +341,7 @@ module.exports = async ({ strapi }) => {
|
|
|
336
341
|
return; // Don't continue
|
|
337
342
|
}
|
|
338
343
|
|
|
339
|
-
strapi.log.info(`[magic-sessionmanager]
|
|
344
|
+
strapi.log.info(`[magic-sessionmanager] [SUCCESS] Refresh token allowed for session ${matchingSession.documentId}`);
|
|
340
345
|
}
|
|
341
346
|
} catch (err) {
|
|
342
347
|
strapi.log.error('[magic-sessionmanager] Error checking refresh token:', err);
|
|
@@ -356,7 +361,7 @@ module.exports = async ({ strapi }) => {
|
|
|
356
361
|
|
|
357
362
|
if (oldRefreshToken) {
|
|
358
363
|
// Find session and update with new tokens
|
|
359
|
-
const allSessions = await strapi.
|
|
364
|
+
const allSessions = await strapi.documents(SESSION_UID).findMany( {
|
|
360
365
|
filters: {
|
|
361
366
|
isActive: true,
|
|
362
367
|
},
|
|
@@ -376,7 +381,8 @@ module.exports = async ({ strapi }) => {
|
|
|
376
381
|
const encryptedToken = newAccessToken ? encryptToken(newAccessToken) : matchingSession.token;
|
|
377
382
|
const encryptedRefreshToken = newRefreshToken ? encryptToken(newRefreshToken) : matchingSession.refreshToken;
|
|
378
383
|
|
|
379
|
-
await strapi.
|
|
384
|
+
await strapi.documents(SESSION_UID).update({
|
|
385
|
+
documentId: matchingSession.documentId,
|
|
380
386
|
data: {
|
|
381
387
|
token: encryptedToken,
|
|
382
388
|
refreshToken: encryptedRefreshToken,
|
|
@@ -384,7 +390,7 @@ module.exports = async ({ strapi }) => {
|
|
|
384
390
|
},
|
|
385
391
|
});
|
|
386
392
|
|
|
387
|
-
strapi.log.info(`[magic-sessionmanager]
|
|
393
|
+
strapi.log.info(`[magic-sessionmanager] [REFRESH] Tokens refreshed for session ${matchingSession.documentId}`);
|
|
388
394
|
}
|
|
389
395
|
}
|
|
390
396
|
} catch (err) {
|
|
@@ -393,18 +399,18 @@ module.exports = async ({ strapi }) => {
|
|
|
393
399
|
}
|
|
394
400
|
});
|
|
395
401
|
|
|
396
|
-
strapi.log.info('[magic-sessionmanager]
|
|
402
|
+
strapi.log.info('[magic-sessionmanager] [SUCCESS] Refresh Token interceptor middleware mounted');
|
|
397
403
|
|
|
398
404
|
// Mount lastSeen update middleware
|
|
399
405
|
strapi.server.use(
|
|
400
406
|
require('./middlewares/last-seen')({ strapi, sessionService })
|
|
401
407
|
);
|
|
402
408
|
|
|
403
|
-
strapi.log.info('[magic-sessionmanager]
|
|
404
|
-
strapi.log.info('[magic-sessionmanager]
|
|
405
|
-
strapi.log.info('[magic-sessionmanager]
|
|
409
|
+
strapi.log.info('[magic-sessionmanager] [SUCCESS] LastSeen middleware mounted');
|
|
410
|
+
strapi.log.info('[magic-sessionmanager] [SUCCESS] Bootstrap complete');
|
|
411
|
+
strapi.log.info('[magic-sessionmanager] [READY] Session Manager ready! Sessions stored in plugin::magic-sessionmanager.session');
|
|
406
412
|
|
|
407
413
|
} catch (err) {
|
|
408
|
-
strapi.log.error('[magic-sessionmanager]
|
|
414
|
+
strapi.log.error('[magic-sessionmanager] [ERROR] Bootstrap error:', err);
|
|
409
415
|
}
|
|
410
416
|
};
|
|
@@ -219,7 +219,7 @@ module.exports = ({ strapi }) => ({
|
|
|
219
219
|
const verification = await licenseGuard.verifyLicense(trimmedKey);
|
|
220
220
|
|
|
221
221
|
if (!verification.valid) {
|
|
222
|
-
strapi.log.warn(`[magic-sessionmanager]
|
|
222
|
+
strapi.log.warn(`[magic-sessionmanager] [WARNING] Invalid license key attempted: ${trimmedKey.substring(0, 8)}...`);
|
|
223
223
|
return ctx.badRequest('Invalid or expired license key');
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -227,13 +227,13 @@ module.exports = ({ strapi }) => ({
|
|
|
227
227
|
const license = await licenseGuard.getLicenseByKey(trimmedKey);
|
|
228
228
|
|
|
229
229
|
if (!license) {
|
|
230
|
-
strapi.log.warn(`[magic-sessionmanager]
|
|
230
|
+
strapi.log.warn(`[magic-sessionmanager] [WARNING] License not found in database: ${trimmedKey.substring(0, 8)}...`);
|
|
231
231
|
return ctx.badRequest('License not found');
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
// Verify email matches
|
|
235
235
|
if (license.email.toLowerCase() !== trimmedEmail) {
|
|
236
|
-
strapi.log.warn(`[magic-sessionmanager]
|
|
236
|
+
strapi.log.warn(`[magic-sessionmanager] [WARNING] Email mismatch for license key: ${trimmedKey.substring(0, 8)}... (Attempted: ${trimmedEmail})`);
|
|
237
237
|
return ctx.badRequest('Email address does not match this license key');
|
|
238
238
|
}
|
|
239
239
|
|
|
@@ -250,7 +250,7 @@ module.exports = ({ strapi }) => ({
|
|
|
250
250
|
data: verification.data,
|
|
251
251
|
};
|
|
252
252
|
|
|
253
|
-
strapi.log.info(`[magic-sessionmanager]
|
|
253
|
+
strapi.log.info(`[magic-sessionmanager] [SUCCESS] Existing license key validated and stored: ${trimmedKey.substring(0, 8)}... (Email: ${trimmedEmail})`);
|
|
254
254
|
|
|
255
255
|
return ctx.send({
|
|
256
256
|
success: true,
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const { decryptToken } = require('../utils/encryption');
|
|
4
4
|
|
|
5
|
+
const SESSION_UID = 'plugin::magic-sessionmanager.session';
|
|
6
|
+
const USER_UID = 'plugin::users-permissions.user';
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Session Controller
|
|
7
10
|
* Handles HTTP requests for session management
|
|
@@ -111,9 +114,9 @@ module.exports = {
|
|
|
111
114
|
.service('session');
|
|
112
115
|
|
|
113
116
|
// Find current session by decrypting and comparing tokens
|
|
114
|
-
const sessions = await strapi.
|
|
117
|
+
const sessions = await strapi.documents(SESSION_UID).findMany({
|
|
115
118
|
filters: {
|
|
116
|
-
user: {
|
|
119
|
+
user: { documentId: userId },
|
|
117
120
|
isActive: true,
|
|
118
121
|
},
|
|
119
122
|
});
|
|
@@ -131,8 +134,8 @@ module.exports = {
|
|
|
131
134
|
|
|
132
135
|
if (matchingSession) {
|
|
133
136
|
// Terminate only the current session
|
|
134
|
-
await sessionService.terminateSession({ sessionId: matchingSession.
|
|
135
|
-
strapi.log.info(`[magic-sessionmanager] User ${userId} logged out (session ${matchingSession.
|
|
137
|
+
await sessionService.terminateSession({ sessionId: matchingSession.documentId });
|
|
138
|
+
strapi.log.info(`[magic-sessionmanager] User ${userId} logged out (session ${matchingSession.documentId})`);
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
ctx.body = {
|
|
@@ -343,7 +346,7 @@ module.exports = {
|
|
|
343
346
|
const { userId } = ctx.params;
|
|
344
347
|
|
|
345
348
|
// Get current user status
|
|
346
|
-
const user = await strapi.
|
|
349
|
+
const user = await strapi.documents(USER_UID).findOne({ documentId: userId });
|
|
347
350
|
|
|
348
351
|
if (!user) {
|
|
349
352
|
return ctx.throw(404, 'User not found');
|
|
@@ -352,7 +355,8 @@ module.exports = {
|
|
|
352
355
|
// Toggle blocked status
|
|
353
356
|
const newBlockedStatus = !user.blocked;
|
|
354
357
|
|
|
355
|
-
await strapi.
|
|
358
|
+
await strapi.documents(USER_UID).update({
|
|
359
|
+
documentId: userId,
|
|
356
360
|
data: {
|
|
357
361
|
blocked: newBlockedStatus,
|
|
358
362
|
},
|
package/server/src/destroy.js
CHANGED
|
@@ -13,6 +13,6 @@ module.exports = async ({ strapi }) => {
|
|
|
13
13
|
strapi.log.info('[magic-sessionmanager] 🛑 Session cleanup interval stopped');
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
strapi.log.info('[magic-sessionmanager]
|
|
16
|
+
strapi.log.info('[magic-sessionmanager] [SUCCESS] Plugin cleanup completed');
|
|
17
17
|
};
|
|
18
18
|
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
* lastSeen Middleware
|
|
5
5
|
* Updates user lastSeen and session lastActive on each authenticated request
|
|
6
6
|
* Rate-limited to prevent DB write noise (default: 30 seconds)
|
|
7
|
+
*
|
|
8
|
+
* [SUCCESS] Migrated to strapi.documents() API (Strapi v5 Best Practice)
|
|
7
9
|
*/
|
|
10
|
+
|
|
11
|
+
const SESSION_UID = 'plugin::magic-sessionmanager.session';
|
|
12
|
+
|
|
8
13
|
module.exports = ({ strapi, sessionService }) => {
|
|
9
14
|
return async (ctx, next) => {
|
|
10
15
|
// BEFORE processing request: Check if user's sessions are active
|
|
@@ -13,9 +18,9 @@ module.exports = ({ strapi, sessionService }) => {
|
|
|
13
18
|
const userId = ctx.state.user.id;
|
|
14
19
|
|
|
15
20
|
// Check if user has ANY active sessions
|
|
16
|
-
const activeSessions = await strapi.
|
|
21
|
+
const activeSessions = await strapi.documents(SESSION_UID).findMany( {
|
|
17
22
|
filters: {
|
|
18
|
-
user: {
|
|
23
|
+
user: { documentId: userId },
|
|
19
24
|
isActive: true,
|
|
20
25
|
},
|
|
21
26
|
limit: 1,
|
|
@@ -23,7 +28,7 @@ module.exports = ({ strapi, sessionService }) => {
|
|
|
23
28
|
|
|
24
29
|
// If user has NO active sessions, reject the request
|
|
25
30
|
if (!activeSessions || activeSessions.length === 0) {
|
|
26
|
-
strapi.log.info(`[magic-sessionmanager]
|
|
31
|
+
strapi.log.info(`[magic-sessionmanager] [BLOCKED] Blocked request - User ${userId} has no active sessions`);
|
|
27
32
|
return ctx.unauthorized('All sessions have been terminated. Please login again.');
|
|
28
33
|
}
|
|
29
34
|
} catch (err) {
|
package/server/src/register.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Sessions are accessed via the Session Manager plugin UI components
|
|
7
7
|
*/
|
|
8
8
|
module.exports = async ({ strapi }) => {
|
|
9
|
-
strapi.log.info('[magic-sessionmanager]
|
|
9
|
+
strapi.log.info('[magic-sessionmanager] [START] Plugin registration starting...');
|
|
10
10
|
|
|
11
11
|
try {
|
|
12
12
|
// Get the user content type
|
|
@@ -21,12 +21,12 @@ module.exports = async ({ strapi }) => {
|
|
|
21
21
|
// Sessions are managed through SessionInfoPanel sidebar instead
|
|
22
22
|
if (userCT.attributes && userCT.attributes.sessions) {
|
|
23
23
|
delete userCT.attributes.sessions;
|
|
24
|
-
strapi.log.info('[magic-sessionmanager]
|
|
24
|
+
strapi.log.info('[magic-sessionmanager] [SUCCESS] Removed sessions field from User content type');
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
strapi.log.info('[magic-sessionmanager]
|
|
27
|
+
strapi.log.info('[magic-sessionmanager] [SUCCESS] Plugin registered successfully');
|
|
28
28
|
|
|
29
29
|
} catch (err) {
|
|
30
|
-
strapi.log.error('[magic-sessionmanager]
|
|
30
|
+
strapi.log.error('[magic-sessionmanager] [ERROR] Registration error:', err);
|
|
31
31
|
}
|
|
32
32
|
};
|
|
@@ -132,9 +132,11 @@ module.exports = ({ strapi }) => ({
|
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
134
|
* Get country flag emoji
|
|
135
|
+
* @param {string} countryCode - ISO 2-letter country code
|
|
136
|
+
* @returns {string} Flag emoji or empty string
|
|
135
137
|
*/
|
|
136
138
|
getCountryFlag(countryCode) {
|
|
137
|
-
if (!countryCode) return '
|
|
139
|
+
if (!countryCode) return '';
|
|
138
140
|
|
|
139
141
|
// Convert country code to flag emoji
|
|
140
142
|
const codePoints = countryCode
|
|
@@ -153,7 +155,7 @@ module.exports = ({ strapi }) => ({
|
|
|
153
155
|
ip: ipAddress,
|
|
154
156
|
country: 'Unknown',
|
|
155
157
|
country_code: 'XX',
|
|
156
|
-
country_flag: '
|
|
158
|
+
country_flag: '[GEO]',
|
|
157
159
|
city: 'Unknown',
|
|
158
160
|
region: 'Unknown',
|
|
159
161
|
timezone: 'Unknown',
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const crypto = require('crypto');
|
|
7
7
|
const os = require('os');
|
|
8
|
+
const pluginPkg = require('../../../package.json');
|
|
8
9
|
|
|
9
10
|
// FIXED LICENSE SERVER URL
|
|
10
11
|
const LICENSE_SERVER_URL = 'https://magicapi.fitlex.me';
|
|
@@ -68,7 +69,9 @@ module.exports = ({ strapi }) => ({
|
|
|
68
69
|
},
|
|
69
70
|
|
|
70
71
|
getUserAgent() {
|
|
71
|
-
|
|
72
|
+
const pluginVersion = pluginPkg.version || '1.0.0';
|
|
73
|
+
const strapiVersion = strapi.config.get('info.strapi') || '5.0.0';
|
|
74
|
+
return `MagicSessionManager/${pluginVersion} Strapi/${strapiVersion} Node/${process.version} ${os.platform()}/${os.release()}`;
|
|
72
75
|
},
|
|
73
76
|
|
|
74
77
|
async createLicense({ email, firstName, lastName }) {
|
|
@@ -98,14 +101,14 @@ module.exports = ({ strapi }) => ({
|
|
|
98
101
|
const data = await response.json();
|
|
99
102
|
|
|
100
103
|
if (data.success) {
|
|
101
|
-
strapi.log.info('[magic-sessionmanager]
|
|
104
|
+
strapi.log.info('[magic-sessionmanager] [SUCCESS] License created:', data.data.licenseKey);
|
|
102
105
|
return data.data;
|
|
103
106
|
} else {
|
|
104
|
-
strapi.log.error('[magic-sessionmanager]
|
|
107
|
+
strapi.log.error('[magic-sessionmanager] [ERROR] License creation failed:', data);
|
|
105
108
|
return null;
|
|
106
109
|
}
|
|
107
110
|
} catch (error) {
|
|
108
|
-
strapi.log.error('[magic-sessionmanager]
|
|
111
|
+
strapi.log.error('[magic-sessionmanager] [ERROR] Error creating license:', error);
|
|
109
112
|
return null;
|
|
110
113
|
}
|
|
111
114
|
},
|
|
@@ -204,11 +207,11 @@ module.exports = ({ strapi }) => ({
|
|
|
204
207
|
name: 'magic-sessionmanager'
|
|
205
208
|
});
|
|
206
209
|
await pluginStore.set({ key: 'licenseKey', value: licenseKey });
|
|
207
|
-
strapi.log.info(`[magic-sessionmanager]
|
|
210
|
+
strapi.log.info(`[magic-sessionmanager] [SUCCESS] License key stored: ${licenseKey.substring(0, 8)}...`);
|
|
208
211
|
},
|
|
209
212
|
|
|
210
213
|
startPinging(licenseKey, intervalMinutes = 15) {
|
|
211
|
-
strapi.log.info(`[magic-sessionmanager]
|
|
214
|
+
strapi.log.info(`[magic-sessionmanager] [TIME] Starting license pings every ${intervalMinutes} minutes`);
|
|
212
215
|
|
|
213
216
|
// Immediate ping
|
|
214
217
|
this.pingLicense(licenseKey);
|
|
@@ -230,7 +233,7 @@ module.exports = ({ strapi }) => ({
|
|
|
230
233
|
*/
|
|
231
234
|
async initialize() {
|
|
232
235
|
try {
|
|
233
|
-
strapi.log.info('[magic-sessionmanager]
|
|
236
|
+
strapi.log.info('[magic-sessionmanager] [SECURE] Initializing License Guard...');
|
|
234
237
|
|
|
235
238
|
// Check if license key exists in plugin store
|
|
236
239
|
const pluginStore = strapi.store({
|
|
@@ -240,7 +243,7 @@ module.exports = ({ strapi }) => ({
|
|
|
240
243
|
const licenseKey = await pluginStore.get({ key: 'licenseKey' });
|
|
241
244
|
|
|
242
245
|
if (!licenseKey) {
|
|
243
|
-
strapi.log.info('[magic-sessionmanager]
|
|
246
|
+
strapi.log.info('[magic-sessionmanager] [INFO] No license found - Running in demo mode');
|
|
244
247
|
return {
|
|
245
248
|
valid: false,
|
|
246
249
|
demo: true,
|
|
@@ -287,7 +290,7 @@ module.exports = ({ strapi }) => ({
|
|
|
287
290
|
gracePeriod: verification.gracePeriod || false,
|
|
288
291
|
};
|
|
289
292
|
} else {
|
|
290
|
-
strapi.log.error('[magic-sessionmanager]
|
|
293
|
+
strapi.log.error('[magic-sessionmanager] [ERROR] License validation failed');
|
|
291
294
|
return {
|
|
292
295
|
valid: false,
|
|
293
296
|
demo: true,
|
|
@@ -296,7 +299,7 @@ module.exports = ({ strapi }) => ({
|
|
|
296
299
|
};
|
|
297
300
|
}
|
|
298
301
|
} catch (error) {
|
|
299
|
-
strapi.log.error('[magic-sessionmanager]
|
|
302
|
+
strapi.log.error('[magic-sessionmanager] [ERROR] Error initializing License Guard:', error);
|
|
300
303
|
return {
|
|
301
304
|
valid: false,
|
|
302
305
|
demo: true,
|
|
@@ -80,14 +80,14 @@ module.exports = ({ strapi }) => ({
|
|
|
80
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
81
|
},
|
|
82
82
|
newLocation: {
|
|
83
|
-
subject: '
|
|
84
|
-
html: `<h2
|
|
85
|
-
text:
|
|
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
86
|
},
|
|
87
87
|
vpnProxy: {
|
|
88
|
-
subject: '
|
|
89
|
-
html: `<h2
|
|
90
|
-
text:
|
|
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
91
|
},
|
|
92
92
|
};
|
|
93
93
|
},
|
|
@@ -272,7 +272,7 @@ module.exports = ({ strapi }) => ({
|
|
|
272
272
|
|
|
273
273
|
if (geoData) {
|
|
274
274
|
embed.fields.push({
|
|
275
|
-
name: '
|
|
275
|
+
name: '[LOCATION] Location',
|
|
276
276
|
value: `${geoData.country_flag} ${geoData.city}, ${geoData.country}`,
|
|
277
277
|
inline: true,
|
|
278
278
|
});
|
|
@@ -284,7 +284,7 @@ module.exports = ({ strapi }) => ({
|
|
|
284
284
|
if (geoData.isThreat) warnings.push('Threat');
|
|
285
285
|
|
|
286
286
|
embed.fields.push({
|
|
287
|
-
name: '
|
|
287
|
+
name: '[WARNING] Security',
|
|
288
288
|
value: `${warnings.join(', ')} detected\nScore: ${geoData.securityScore}/100`,
|
|
289
289
|
inline: true,
|
|
290
290
|
});
|
|
@@ -297,12 +297,12 @@ module.exports = ({ strapi }) => ({
|
|
|
297
297
|
getEventTitle(event) {
|
|
298
298
|
const titles = {
|
|
299
299
|
'login.suspicious': '🚨 Suspicious Login',
|
|
300
|
-
'login.new_location': '
|
|
300
|
+
'login.new_location': '[LOCATION] New Location Login',
|
|
301
301
|
'login.vpn': '🔴 VPN Login Detected',
|
|
302
302
|
'login.threat': '⛔ Threat IP Login',
|
|
303
303
|
'session.terminated': '🔴 Session Terminated',
|
|
304
304
|
};
|
|
305
|
-
return titles[event] || '
|
|
305
|
+
return titles[event] || '[STATS] Session Event';
|
|
306
306
|
},
|
|
307
307
|
|
|
308
308
|
getEventColor(event) {
|