strapi-plugin-magic-sessionmanager 4.4.5 → 4.4.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/dist/server/index.js +297 -109
- package/dist/server/index.mjs +297 -109
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -172,11 +172,19 @@ const getClientIp$1 = (ctx) => {
|
|
|
172
172
|
};
|
|
173
173
|
const cleanIp = (ip) => {
|
|
174
174
|
if (!ip) return "unknown";
|
|
175
|
-
ip = ip.
|
|
175
|
+
ip = ip.trim();
|
|
176
|
+
if (ip.startsWith("[")) {
|
|
177
|
+
const bracketEnd = ip.indexOf("]");
|
|
178
|
+
if (bracketEnd !== -1) {
|
|
179
|
+
ip = ip.substring(1, bracketEnd);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
176
182
|
if (ip.startsWith("::ffff:")) {
|
|
177
183
|
ip = ip.substring(7);
|
|
178
184
|
}
|
|
179
|
-
ip
|
|
185
|
+
if (ip.includes(".") && ip.includes(":") && ip.indexOf(":") === ip.lastIndexOf(":")) {
|
|
186
|
+
ip = ip.split(":")[0];
|
|
187
|
+
}
|
|
180
188
|
return ip || "unknown";
|
|
181
189
|
};
|
|
182
190
|
const isPrivateIp = (ip) => {
|
|
@@ -220,11 +228,12 @@ function encryptToken$2(token) {
|
|
|
220
228
|
const authTag = cipher.getAuthTag();
|
|
221
229
|
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
|
|
222
230
|
} catch (err) {
|
|
223
|
-
|
|
231
|
+
const errMsg = err instanceof Error ? err.message : "Unknown encryption error";
|
|
232
|
+
console.error("[magic-sessionmanager/encryption] Encryption failed:", errMsg);
|
|
224
233
|
throw new Error("Failed to encrypt token");
|
|
225
234
|
}
|
|
226
235
|
}
|
|
227
|
-
function decryptToken$
|
|
236
|
+
function decryptToken$2(encryptedToken) {
|
|
228
237
|
if (!encryptedToken) return null;
|
|
229
238
|
try {
|
|
230
239
|
const key = getEncryptionKey();
|
|
@@ -241,7 +250,8 @@ function decryptToken$3(encryptedToken) {
|
|
|
241
250
|
decrypted += decipher.final("utf8");
|
|
242
251
|
return decrypted;
|
|
243
252
|
} catch (err) {
|
|
244
|
-
|
|
253
|
+
const errMsg = err instanceof Error ? err.message : "Unknown decryption error";
|
|
254
|
+
console.error("[magic-sessionmanager/encryption] Decryption failed:", errMsg);
|
|
245
255
|
return null;
|
|
246
256
|
}
|
|
247
257
|
}
|
|
@@ -257,7 +267,7 @@ function hashToken$3(token) {
|
|
|
257
267
|
}
|
|
258
268
|
var encryption = {
|
|
259
269
|
encryptToken: encryptToken$2,
|
|
260
|
-
decryptToken: decryptToken$
|
|
270
|
+
decryptToken: decryptToken$2,
|
|
261
271
|
generateSessionId: generateSessionId$1,
|
|
262
272
|
hashToken: hashToken$3
|
|
263
273
|
};
|
|
@@ -288,12 +298,25 @@ function isAuthEndpoint(path2) {
|
|
|
288
298
|
}
|
|
289
299
|
const userIdCache = /* @__PURE__ */ new Map();
|
|
290
300
|
const CACHE_TTL = 5 * 60 * 1e3;
|
|
301
|
+
const CACHE_MAX_SIZE = 1e3;
|
|
291
302
|
async function getDocumentIdFromNumericId(strapi2, numericId) {
|
|
292
303
|
const cacheKey = `user_${numericId}`;
|
|
293
304
|
const cached2 = userIdCache.get(cacheKey);
|
|
294
305
|
if (cached2 && Date.now() - cached2.timestamp < CACHE_TTL) {
|
|
295
306
|
return cached2.documentId;
|
|
296
307
|
}
|
|
308
|
+
if (userIdCache.size >= CACHE_MAX_SIZE) {
|
|
309
|
+
const now = Date.now();
|
|
310
|
+
for (const [key, value] of userIdCache) {
|
|
311
|
+
if (now - value.timestamp >= CACHE_TTL) {
|
|
312
|
+
userIdCache.delete(key);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (userIdCache.size >= CACHE_MAX_SIZE) {
|
|
316
|
+
const keysToDelete = [...userIdCache.keys()].slice(0, Math.floor(CACHE_MAX_SIZE / 4));
|
|
317
|
+
keysToDelete.forEach((key) => userIdCache.delete(key));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
297
320
|
try {
|
|
298
321
|
const user = await strapi2.entityService.findOne(USER_UID$3, numericId, {
|
|
299
322
|
fields: ["documentId"]
|
|
@@ -336,7 +359,7 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
336
359
|
isActive: false
|
|
337
360
|
},
|
|
338
361
|
limit: 5,
|
|
339
|
-
fields: ["documentId", "terminatedManually", "lastActive"],
|
|
362
|
+
fields: ["documentId", "terminatedManually", "lastActive", "loginTime"],
|
|
340
363
|
sort: [{ lastActive: "desc" }]
|
|
341
364
|
});
|
|
342
365
|
if (inactiveSessions && inactiveSessions.length > 0) {
|
|
@@ -346,6 +369,14 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
346
369
|
return ctx.unauthorized("Session has been terminated. Please login again.");
|
|
347
370
|
}
|
|
348
371
|
const sessionToReactivate = inactiveSessions[0];
|
|
372
|
+
const maxAgeDays = config2.maxSessionAgeDays || 30;
|
|
373
|
+
const loginTime = sessionToReactivate.loginTime ? new Date(sessionToReactivate.loginTime).getTime() : sessionToReactivate.lastActive ? new Date(sessionToReactivate.lastActive).getTime() : 0;
|
|
374
|
+
const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
375
|
+
const isExpired = loginTime > 0 && Date.now() - loginTime > maxAgeMs;
|
|
376
|
+
if (isExpired) {
|
|
377
|
+
strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Session exceeded max age of ${maxAgeDays} days (user: ${userDocId2.substring(0, 8)}...)`);
|
|
378
|
+
return ctx.unauthorized("Session expired. Please login again.");
|
|
379
|
+
}
|
|
349
380
|
await strapi2.documents(SESSION_UID$4).update({
|
|
350
381
|
documentId: sessionToReactivate.documentId,
|
|
351
382
|
data: {
|
|
@@ -386,7 +417,7 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
386
417
|
};
|
|
387
418
|
};
|
|
388
419
|
const getClientIp = getClientIp_1;
|
|
389
|
-
const { encryptToken: encryptToken$1, decryptToken: decryptToken$
|
|
420
|
+
const { encryptToken: encryptToken$1, decryptToken: decryptToken$1, hashToken: hashToken$2 } = encryption;
|
|
390
421
|
const { createLogger: createLogger$3 } = logger;
|
|
391
422
|
const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
|
|
392
423
|
const USER_UID$2 = "plugin::users-permissions.user";
|
|
@@ -420,11 +451,11 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
420
451
|
log.info("║ [SUCCESS] SESSION MANAGER LICENSE ACTIVE ║");
|
|
421
452
|
log.info("║ ║");
|
|
422
453
|
if (licenseStatus.data) {
|
|
423
|
-
|
|
454
|
+
const maskedKey = licenseStatus.data.licenseKey ? `${licenseStatus.data.licenseKey.substring(0, 8)}...` : "N/A";
|
|
455
|
+
log.info(`║ License: ${maskedKey}`.padEnd(66) + "║");
|
|
424
456
|
log.info(`║ User: ${licenseStatus.data.firstName} ${licenseStatus.data.lastName}`.padEnd(66) + "║");
|
|
425
|
-
log.info(`║ Email: ${licenseStatus.data.email}`.padEnd(66) + "║");
|
|
426
457
|
} else if (storedKey) {
|
|
427
|
-
log.info(`║ License: ${storedKey} (Offline Mode)`.padEnd(66) + "║");
|
|
458
|
+
log.info(`║ License: ${storedKey.substring(0, 8)}... (Offline Mode)`.padEnd(66) + "║");
|
|
428
459
|
log.info(`║ Status: Grace Period Active`.padEnd(66) + "║");
|
|
429
460
|
}
|
|
430
461
|
log.info("║ ║");
|
|
@@ -456,10 +487,21 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
456
487
|
try {
|
|
457
488
|
const token = ctx.request.headers?.authorization?.replace("Bearer ", "");
|
|
458
489
|
if (!token) {
|
|
459
|
-
ctx.status =
|
|
460
|
-
ctx.body = { message: "
|
|
490
|
+
ctx.status = 401;
|
|
491
|
+
ctx.body = { error: { status: 401, message: "Authorization token required" } };
|
|
461
492
|
return;
|
|
462
493
|
}
|
|
494
|
+
try {
|
|
495
|
+
const jwtService = strapi2.plugin("users-permissions").service("jwt");
|
|
496
|
+
const decoded = await jwtService.verify(token);
|
|
497
|
+
if (!decoded || !decoded.id) {
|
|
498
|
+
ctx.status = 401;
|
|
499
|
+
ctx.body = { error: { status: 401, message: "Invalid token" } };
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
} catch (jwtErr) {
|
|
503
|
+
log.debug("JWT verify failed during logout (cleaning up anyway):", jwtErr.message);
|
|
504
|
+
}
|
|
463
505
|
const tokenHashValue = hashToken$2(token);
|
|
464
506
|
const matchingSession = await strapi2.documents(SESSION_UID$3).findFirst({
|
|
465
507
|
filters: {
|
|
@@ -481,6 +523,7 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
481
523
|
},
|
|
482
524
|
config: {
|
|
483
525
|
auth: false
|
|
526
|
+
// We handle auth manually above to support expired-but-valid tokens
|
|
484
527
|
}
|
|
485
528
|
}]);
|
|
486
529
|
log.info("[SUCCESS] /api/auth/logout route registered");
|
|
@@ -540,8 +583,7 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
540
583
|
ctx.body = {
|
|
541
584
|
error: {
|
|
542
585
|
status: 403,
|
|
543
|
-
message: "Login blocked for security reasons"
|
|
544
|
-
details: { reason: blockReason }
|
|
586
|
+
message: "Login blocked for security reasons. Please contact support."
|
|
545
587
|
}
|
|
546
588
|
};
|
|
547
589
|
return;
|
|
@@ -603,11 +645,22 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
603
645
|
geoData
|
|
604
646
|
});
|
|
605
647
|
if (config2.discordWebhookUrl) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
648
|
+
const webhookUrl = config2.discordWebhookUrl;
|
|
649
|
+
try {
|
|
650
|
+
const parsed = new URL(webhookUrl);
|
|
651
|
+
const isValidDomain = parsed.protocol === "https:" && (parsed.hostname === "discord.com" || parsed.hostname === "discordapp.com" || parsed.hostname.endsWith(".discord.com") || parsed.hostname === "hooks.slack.com");
|
|
652
|
+
if (isValidDomain) {
|
|
653
|
+
await notificationService.sendWebhook({
|
|
654
|
+
event: "session.login",
|
|
655
|
+
data: webhookData,
|
|
656
|
+
webhookUrl
|
|
657
|
+
});
|
|
658
|
+
} else {
|
|
659
|
+
log.warn(`[SECURITY] Blocked webhook to untrusted domain: ${parsed.hostname}`);
|
|
660
|
+
}
|
|
661
|
+
} catch {
|
|
662
|
+
log.warn("[SECURITY] Invalid webhook URL in plugin config");
|
|
663
|
+
}
|
|
611
664
|
}
|
|
612
665
|
}
|
|
613
666
|
} catch (notifErr) {
|
|
@@ -788,6 +841,30 @@ async function ensureTokenHashIndex(strapi2, log) {
|
|
|
788
841
|
log.debug("[INDEX] Could not create tokenHash index (will retry on next startup):", err.message);
|
|
789
842
|
}
|
|
790
843
|
}
|
|
844
|
+
const sessionCheckErrors = { count: 0, lastReset: Date.now() };
|
|
845
|
+
const MAX_CONSECUTIVE_ERRORS = 10;
|
|
846
|
+
const ERROR_RESET_INTERVAL = 60 * 1e3;
|
|
847
|
+
function shouldFailOpen() {
|
|
848
|
+
const now = Date.now();
|
|
849
|
+
if (now - sessionCheckErrors.lastReset > ERROR_RESET_INTERVAL) {
|
|
850
|
+
sessionCheckErrors.count = 0;
|
|
851
|
+
sessionCheckErrors.lastReset = now;
|
|
852
|
+
}
|
|
853
|
+
sessionCheckErrors.count++;
|
|
854
|
+
if (sessionCheckErrors.count > MAX_CONSECUTIVE_ERRORS) {
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
function resetErrorCounter() {
|
|
860
|
+
sessionCheckErrors.count = 0;
|
|
861
|
+
}
|
|
862
|
+
function isSessionExpired(session2, maxAgeDays = 30) {
|
|
863
|
+
if (!session2.loginTime) return false;
|
|
864
|
+
const loginTime = new Date(session2.loginTime).getTime();
|
|
865
|
+
const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
866
|
+
return Date.now() - loginTime > maxAgeMs;
|
|
867
|
+
}
|
|
791
868
|
async function registerSessionAwareAuthStrategy(strapi2, log) {
|
|
792
869
|
try {
|
|
793
870
|
const usersPermissionsPlugin = strapi2.plugin("users-permissions");
|
|
@@ -809,15 +886,15 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
|
|
|
809
886
|
}
|
|
810
887
|
const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
811
888
|
const strictMode = config2.strictSessionEnforcement === true;
|
|
889
|
+
const maxSessionAgeDays = config2.maxSessionAgeDays || 30;
|
|
812
890
|
try {
|
|
813
891
|
const tokenHashValue = hashToken$2(token);
|
|
814
|
-
let userDocId = null;
|
|
815
892
|
const user = await strapi2.entityService.findOne(
|
|
816
893
|
"plugin::users-permissions.user",
|
|
817
894
|
decoded.id,
|
|
818
895
|
{ fields: ["documentId"] }
|
|
819
896
|
);
|
|
820
|
-
userDocId = user?.documentId;
|
|
897
|
+
const userDocId = user?.documentId;
|
|
821
898
|
if (!userDocId) {
|
|
822
899
|
strapi2.log.debug("[magic-sessionmanager] [JWT] No documentId found, allowing through");
|
|
823
900
|
return decoded;
|
|
@@ -827,9 +904,19 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
|
|
|
827
904
|
user: { documentId: userDocId },
|
|
828
905
|
tokenHash: tokenHashValue
|
|
829
906
|
},
|
|
830
|
-
fields: ["documentId", "isActive", "terminatedManually", "lastActive"]
|
|
907
|
+
fields: ["documentId", "isActive", "terminatedManually", "lastActive", "loginTime"]
|
|
831
908
|
});
|
|
832
909
|
if (thisSession) {
|
|
910
|
+
if (isSessionExpired(thisSession, maxSessionAgeDays)) {
|
|
911
|
+
strapi2.log.info(
|
|
912
|
+
`[magic-sessionmanager] [JWT-EXPIRED] Session exceeded max age of ${maxSessionAgeDays} days (user: ${userDocId.substring(0, 8)}...)`
|
|
913
|
+
);
|
|
914
|
+
await strapi2.documents(SESSION_UID$3).update({
|
|
915
|
+
documentId: thisSession.documentId,
|
|
916
|
+
data: { isActive: false, terminatedManually: true, logoutTime: /* @__PURE__ */ new Date() }
|
|
917
|
+
});
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
833
920
|
if (thisSession.terminatedManually === true) {
|
|
834
921
|
strapi2.log.info(
|
|
835
922
|
`[magic-sessionmanager] [JWT-BLOCKED] Session was manually terminated (user: ${userDocId.substring(0, 8)}...)`
|
|
@@ -837,38 +924,32 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
|
|
|
837
924
|
return null;
|
|
838
925
|
}
|
|
839
926
|
if (thisSession.isActive) {
|
|
927
|
+
resetErrorCounter();
|
|
840
928
|
return decoded;
|
|
841
929
|
}
|
|
842
930
|
await strapi2.documents(SESSION_UID$3).update({
|
|
843
931
|
documentId: thisSession.documentId,
|
|
844
|
-
data: {
|
|
845
|
-
isActive: true,
|
|
846
|
-
lastActive: /* @__PURE__ */ new Date()
|
|
847
|
-
}
|
|
932
|
+
data: { isActive: true, lastActive: /* @__PURE__ */ new Date() }
|
|
848
933
|
});
|
|
849
934
|
strapi2.log.info(
|
|
850
935
|
`[magic-sessionmanager] [JWT-REACTIVATED] Session reactivated for user ${userDocId.substring(0, 8)}...`
|
|
851
936
|
);
|
|
937
|
+
resetErrorCounter();
|
|
852
938
|
return decoded;
|
|
853
939
|
}
|
|
854
940
|
const anyActiveSessions = await strapi2.documents(SESSION_UID$3).findMany({
|
|
855
|
-
filters: {
|
|
856
|
-
user: { documentId: userDocId },
|
|
857
|
-
isActive: true
|
|
858
|
-
},
|
|
941
|
+
filters: { user: { documentId: userDocId }, isActive: true },
|
|
859
942
|
limit: 1
|
|
860
943
|
});
|
|
861
944
|
if (anyActiveSessions && anyActiveSessions.length > 0) {
|
|
862
945
|
strapi2.log.debug(
|
|
863
946
|
`[magic-sessionmanager] [JWT] No session for token but user has other active sessions (allowing)`
|
|
864
947
|
);
|
|
948
|
+
resetErrorCounter();
|
|
865
949
|
return decoded;
|
|
866
950
|
}
|
|
867
951
|
const terminatedSessions = await strapi2.documents(SESSION_UID$3).findMany({
|
|
868
|
-
filters: {
|
|
869
|
-
user: { documentId: userDocId },
|
|
870
|
-
terminatedManually: true
|
|
871
|
-
},
|
|
952
|
+
filters: { user: { documentId: userDocId }, terminatedManually: true },
|
|
872
953
|
limit: 1
|
|
873
954
|
});
|
|
874
955
|
if (terminatedSessions && terminatedSessions.length > 0) {
|
|
@@ -886,10 +967,15 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
|
|
|
886
967
|
strapi2.log.warn(
|
|
887
968
|
`[magic-sessionmanager] [JWT-WARN] No session for user ${userDocId.substring(0, 8)}... (allowing)`
|
|
888
969
|
);
|
|
970
|
+
resetErrorCounter();
|
|
889
971
|
return decoded;
|
|
890
972
|
} catch (err) {
|
|
891
|
-
|
|
892
|
-
|
|
973
|
+
if (shouldFailOpen()) {
|
|
974
|
+
strapi2.log.warn("[magic-sessionmanager] [JWT] Session check error (allowing):", err.message);
|
|
975
|
+
return decoded;
|
|
976
|
+
}
|
|
977
|
+
strapi2.log.error("[magic-sessionmanager] [JWT] Too many consecutive errors, blocking request:", err.message);
|
|
978
|
+
return null;
|
|
893
979
|
}
|
|
894
980
|
};
|
|
895
981
|
strapi2.log.info("[magic-sessionmanager] [AUTH] [SUCCESS] JWT verify wrapped with session validation");
|
|
@@ -1354,7 +1440,7 @@ function extractVersion(userAgent, regex) {
|
|
|
1354
1440
|
var userAgentParser = {
|
|
1355
1441
|
parseUserAgent: parseUserAgent$2
|
|
1356
1442
|
};
|
|
1357
|
-
const {
|
|
1443
|
+
const { hashToken: hashToken$1 } = encryption;
|
|
1358
1444
|
const { parseUserAgent: parseUserAgent$1 } = userAgentParser;
|
|
1359
1445
|
const SESSION_UID$2 = "plugin::magic-sessionmanager.session";
|
|
1360
1446
|
const USER_UID$1 = "plugin::users-permissions.user";
|
|
@@ -1414,7 +1500,8 @@ var session$3 = {
|
|
|
1414
1500
|
}
|
|
1415
1501
|
const allSessions = await strapi.documents(SESSION_UID$2).findMany({
|
|
1416
1502
|
filters: { user: { documentId: userId } },
|
|
1417
|
-
sort: { loginTime: "desc" }
|
|
1503
|
+
sort: { loginTime: "desc" },
|
|
1504
|
+
limit: 200
|
|
1418
1505
|
});
|
|
1419
1506
|
const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
|
|
1420
1507
|
const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
|
|
@@ -1452,7 +1539,7 @@ var session$3 = {
|
|
|
1452
1539
|
strapi.documents(SESSION_UID$2).update({
|
|
1453
1540
|
documentId: session2.documentId,
|
|
1454
1541
|
data: {
|
|
1455
|
-
geoLocation
|
|
1542
|
+
geoLocation,
|
|
1456
1543
|
securityScore: geoData.securityScore || null
|
|
1457
1544
|
}
|
|
1458
1545
|
}).catch(() => {
|
|
@@ -1514,9 +1601,15 @@ var session$3 = {
|
|
|
1514
1601
|
const { userId } = ctx.params;
|
|
1515
1602
|
const isAdminRequest = ctx.state.userAbility || ctx.state.admin;
|
|
1516
1603
|
const requestingUserDocId = ctx.state.user?.documentId;
|
|
1517
|
-
if (!isAdminRequest
|
|
1518
|
-
|
|
1519
|
-
|
|
1604
|
+
if (!isAdminRequest) {
|
|
1605
|
+
if (!requestingUserDocId) {
|
|
1606
|
+
strapi.log.warn(`[magic-sessionmanager] Security: Request without documentId tried to access sessions of user ${userId}`);
|
|
1607
|
+
return ctx.forbidden("Cannot verify user identity");
|
|
1608
|
+
}
|
|
1609
|
+
if (String(requestingUserDocId) !== String(userId)) {
|
|
1610
|
+
strapi.log.warn(`[magic-sessionmanager] Security: User ${requestingUserDocId} tried to access sessions of user ${userId}`);
|
|
1611
|
+
return ctx.forbidden("You can only access your own sessions");
|
|
1612
|
+
}
|
|
1520
1613
|
}
|
|
1521
1614
|
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
1522
1615
|
const sessions = await sessionService.getUserSessions(userId);
|
|
@@ -1640,7 +1733,7 @@ var session$3 = {
|
|
|
1640
1733
|
strapi.documents(SESSION_UID$2).update({
|
|
1641
1734
|
documentId: currentSession.documentId,
|
|
1642
1735
|
data: {
|
|
1643
|
-
geoLocation
|
|
1736
|
+
geoLocation,
|
|
1644
1737
|
securityScore: geoData.securityScore || null
|
|
1645
1738
|
}
|
|
1646
1739
|
}).catch(() => {
|
|
@@ -1722,22 +1815,6 @@ var session$3 = {
|
|
|
1722
1815
|
ctx.throw(500, "Error terminating session");
|
|
1723
1816
|
}
|
|
1724
1817
|
},
|
|
1725
|
-
/**
|
|
1726
|
-
* Terminate specific session
|
|
1727
|
-
* DELETE /magic-sessionmanager/sessions/:sessionId
|
|
1728
|
-
*/
|
|
1729
|
-
async terminateSession(ctx) {
|
|
1730
|
-
try {
|
|
1731
|
-
const { sessionId } = ctx.params;
|
|
1732
|
-
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
1733
|
-
await sessionService.terminateSession({ sessionId });
|
|
1734
|
-
ctx.body = {
|
|
1735
|
-
message: `Session ${sessionId} terminated`
|
|
1736
|
-
};
|
|
1737
|
-
} catch (err) {
|
|
1738
|
-
ctx.throw(500, "Error terminating session");
|
|
1739
|
-
}
|
|
1740
|
-
},
|
|
1741
1818
|
/**
|
|
1742
1819
|
* Simulate session timeout for testing (Admin action)
|
|
1743
1820
|
* POST /magic-sessionmanager/sessions/:sessionId/simulate-timeout
|
|
@@ -1746,6 +1823,10 @@ var session$3 = {
|
|
|
1746
1823
|
*/
|
|
1747
1824
|
async simulateTimeout(ctx) {
|
|
1748
1825
|
try {
|
|
1826
|
+
const nodeEnv = process.env.NODE_ENV || "development";
|
|
1827
|
+
if (nodeEnv === "production") {
|
|
1828
|
+
return ctx.forbidden("simulate-timeout is disabled in production");
|
|
1829
|
+
}
|
|
1749
1830
|
const { sessionId } = ctx.params;
|
|
1750
1831
|
const session2 = await strapi.documents(SESSION_UID$2).findOne({
|
|
1751
1832
|
documentId: sessionId
|
|
@@ -1818,9 +1899,15 @@ var session$3 = {
|
|
|
1818
1899
|
if (!ipAddress) {
|
|
1819
1900
|
return ctx.badRequest("IP address is required");
|
|
1820
1901
|
}
|
|
1821
|
-
const IPV4_REGEX = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
1822
|
-
const
|
|
1823
|
-
|
|
1902
|
+
const IPV4_REGEX = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
1903
|
+
const ipv4Match = ipAddress.match(IPV4_REGEX);
|
|
1904
|
+
const isValidIpv4 = ipv4Match && ipv4Match.slice(1).every((octet) => {
|
|
1905
|
+
const n = parseInt(octet, 10);
|
|
1906
|
+
return n >= 0 && n <= 255;
|
|
1907
|
+
});
|
|
1908
|
+
const IPV6_REGEX = /^[0-9a-fA-F:]{3,45}$/;
|
|
1909
|
+
const isValidIpv6 = !isValidIpv4 && IPV6_REGEX.test(ipAddress) && (ipAddress.match(/:/g) || []).length >= 2;
|
|
1910
|
+
if (!isValidIpv4 && !isValidIpv6) {
|
|
1824
1911
|
return ctx.badRequest("Invalid IP address format");
|
|
1825
1912
|
}
|
|
1826
1913
|
const licenseGuard2 = strapi.plugin("magic-sessionmanager").service("license-guard");
|
|
@@ -1982,12 +2069,11 @@ var license$1 = ({ strapi: strapi2 }) => ({
|
|
|
1982
2069
|
message: "No license found. Running in demo mode."
|
|
1983
2070
|
});
|
|
1984
2071
|
}
|
|
1985
|
-
strapi2.log.info(`[magic-sessionmanager/license-controller] Checking stored license: ${licenseKey}
|
|
2072
|
+
strapi2.log.info(`[magic-sessionmanager/license-controller] Checking stored license: ${licenseKey.substring(0, 8)}...`);
|
|
1986
2073
|
const verification = await licenseGuard2.verifyLicense(licenseKey);
|
|
1987
2074
|
const license2 = await licenseGuard2.getLicenseByKey(licenseKey);
|
|
1988
2075
|
strapi2.log.info("[magic-sessionmanager/license-controller] License data from MagicAPI:", {
|
|
1989
|
-
licenseKey: license2?.licenseKey,
|
|
1990
|
-
email: license2?.email,
|
|
2076
|
+
licenseKey: license2?.licenseKey ? `${license2.licenseKey.substring(0, 8)}...` : "N/A",
|
|
1991
2077
|
featurePremium: license2?.featurePremium,
|
|
1992
2078
|
isActive: license2?.isActive,
|
|
1993
2079
|
pluginName: license2?.pluginName
|
|
@@ -2131,6 +2217,55 @@ var license$1 = ({ strapi: strapi2 }) => ({
|
|
|
2131
2217
|
}
|
|
2132
2218
|
}
|
|
2133
2219
|
});
|
|
2220
|
+
const ALLOWED_WEBHOOK_DOMAINS = {
|
|
2221
|
+
discord: ["discord.com", "discordapp.com"],
|
|
2222
|
+
slack: ["hooks.slack.com"]
|
|
2223
|
+
};
|
|
2224
|
+
function sanitizeWebhookUrl(url, type2) {
|
|
2225
|
+
if (!url || typeof url !== "string") return "";
|
|
2226
|
+
const trimmed = url.trim();
|
|
2227
|
+
if (!trimmed) return "";
|
|
2228
|
+
try {
|
|
2229
|
+
const parsed = new URL(trimmed);
|
|
2230
|
+
if (parsed.protocol !== "https:") return "";
|
|
2231
|
+
const allowedDomains = ALLOWED_WEBHOOK_DOMAINS[type2] || [];
|
|
2232
|
+
const isAllowed = allowedDomains.some(
|
|
2233
|
+
(domain) => parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`)
|
|
2234
|
+
);
|
|
2235
|
+
if (!isAllowed) return "";
|
|
2236
|
+
return trimmed;
|
|
2237
|
+
} catch {
|
|
2238
|
+
return "";
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
function sanitizeCountryList(list) {
|
|
2242
|
+
if (!Array.isArray(list)) return [];
|
|
2243
|
+
return list.filter((code) => typeof code === "string" && /^[A-Z]{2}$/.test(code.trim().toUpperCase())).map((code) => code.trim().toUpperCase());
|
|
2244
|
+
}
|
|
2245
|
+
function sanitizeEmailTemplates(templates) {
|
|
2246
|
+
const defaults = {
|
|
2247
|
+
suspiciousLogin: { subject: "", html: "", text: "" },
|
|
2248
|
+
newLocation: { subject: "", html: "", text: "" },
|
|
2249
|
+
vpnProxy: { subject: "", html: "", text: "" }
|
|
2250
|
+
};
|
|
2251
|
+
if (!templates || typeof templates !== "object") return defaults;
|
|
2252
|
+
const dangerousTags = /<\s*\/?\s*(script|iframe|object|embed|form|input|button|link|meta|base)\b[^>]*>/gi;
|
|
2253
|
+
const dangerousAttrs = /\s(on\w+|javascript\s*:)[^=]*=/gi;
|
|
2254
|
+
const result = {};
|
|
2255
|
+
for (const [key, defaultVal] of Object.entries(defaults)) {
|
|
2256
|
+
const tpl = templates[key];
|
|
2257
|
+
if (!tpl || typeof tpl !== "object") {
|
|
2258
|
+
result[key] = defaultVal;
|
|
2259
|
+
continue;
|
|
2260
|
+
}
|
|
2261
|
+
result[key] = {
|
|
2262
|
+
subject: typeof tpl.subject === "string" ? tpl.subject.substring(0, 200) : "",
|
|
2263
|
+
html: typeof tpl.html === "string" ? tpl.html.replace(dangerousTags, "").replace(dangerousAttrs, " ").substring(0, 1e4) : "",
|
|
2264
|
+
text: typeof tpl.text === "string" ? tpl.text.substring(0, 5e3) : ""
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
return result;
|
|
2268
|
+
}
|
|
2134
2269
|
var settings$1 = {
|
|
2135
2270
|
/**
|
|
2136
2271
|
* Get plugin settings
|
|
@@ -2192,29 +2327,26 @@ var settings$1 = {
|
|
|
2192
2327
|
name: "magic-sessionmanager"
|
|
2193
2328
|
});
|
|
2194
2329
|
const sanitizedSettings = {
|
|
2195
|
-
inactivityTimeout: parseInt(body.inactivityTimeout) || 15,
|
|
2196
|
-
cleanupInterval: parseInt(body.cleanupInterval) || 30,
|
|
2197
|
-
lastSeenRateLimit: parseInt(body.lastSeenRateLimit) || 30,
|
|
2198
|
-
retentionDays: parseInt(body.retentionDays) || 90,
|
|
2330
|
+
inactivityTimeout: Math.max(1, Math.min(parseInt(body.inactivityTimeout) || 15, 1440)),
|
|
2331
|
+
cleanupInterval: Math.max(5, Math.min(parseInt(body.cleanupInterval) || 30, 1440)),
|
|
2332
|
+
lastSeenRateLimit: Math.max(5, Math.min(parseInt(body.lastSeenRateLimit) || 30, 300)),
|
|
2333
|
+
retentionDays: Math.max(1, Math.min(parseInt(body.retentionDays) || 90, 365)),
|
|
2334
|
+
maxSessionAgeDays: Math.max(1, Math.min(parseInt(body.maxSessionAgeDays) || 30, 365)),
|
|
2199
2335
|
enableGeolocation: !!body.enableGeolocation,
|
|
2200
2336
|
enableSecurityScoring: !!body.enableSecurityScoring,
|
|
2201
2337
|
blockSuspiciousSessions: !!body.blockSuspiciousSessions,
|
|
2202
|
-
maxFailedLogins: parseInt(body.maxFailedLogins) || 5,
|
|
2338
|
+
maxFailedLogins: Math.max(1, Math.min(parseInt(body.maxFailedLogins) || 5, 100)),
|
|
2203
2339
|
enableEmailAlerts: !!body.enableEmailAlerts,
|
|
2204
2340
|
alertOnSuspiciousLogin: !!body.alertOnSuspiciousLogin,
|
|
2205
2341
|
alertOnNewLocation: !!body.alertOnNewLocation,
|
|
2206
2342
|
alertOnVpnProxy: !!body.alertOnVpnProxy,
|
|
2207
2343
|
enableWebhooks: !!body.enableWebhooks,
|
|
2208
|
-
discordWebhookUrl:
|
|
2209
|
-
slackWebhookUrl:
|
|
2344
|
+
discordWebhookUrl: sanitizeWebhookUrl(body.discordWebhookUrl, "discord"),
|
|
2345
|
+
slackWebhookUrl: sanitizeWebhookUrl(body.slackWebhookUrl, "slack"),
|
|
2210
2346
|
enableGeofencing: !!body.enableGeofencing,
|
|
2211
|
-
allowedCountries:
|
|
2212
|
-
blockedCountries:
|
|
2213
|
-
emailTemplates: body.emailTemplates
|
|
2214
|
-
suspiciousLogin: { subject: "", html: "", text: "" },
|
|
2215
|
-
newLocation: { subject: "", html: "", text: "" },
|
|
2216
|
-
vpnProxy: { subject: "", html: "", text: "" }
|
|
2217
|
-
}
|
|
2347
|
+
allowedCountries: sanitizeCountryList(body.allowedCountries),
|
|
2348
|
+
blockedCountries: sanitizeCountryList(body.blockedCountries),
|
|
2349
|
+
emailTemplates: sanitizeEmailTemplates(body.emailTemplates)
|
|
2218
2350
|
};
|
|
2219
2351
|
await pluginStore.set({
|
|
2220
2352
|
key: "settings",
|
|
@@ -2285,15 +2417,15 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2285
2417
|
deviceType: parsedUA.deviceType,
|
|
2286
2418
|
browserName: parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName,
|
|
2287
2419
|
osName: parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName,
|
|
2288
|
-
// Geolocation data (
|
|
2289
|
-
geoLocation: geoData ?
|
|
2420
|
+
// Geolocation data (stored as JSON object, schema type: json)
|
|
2421
|
+
geoLocation: geoData ? {
|
|
2290
2422
|
country: geoData.country,
|
|
2291
2423
|
country_code: geoData.country_code,
|
|
2292
2424
|
country_flag: geoData.country_flag,
|
|
2293
2425
|
city: geoData.city,
|
|
2294
2426
|
region: geoData.region,
|
|
2295
2427
|
timezone: geoData.timezone
|
|
2296
|
-
}
|
|
2428
|
+
} : null,
|
|
2297
2429
|
securityScore: geoData?.securityScore || null
|
|
2298
2430
|
}
|
|
2299
2431
|
});
|
|
@@ -2314,6 +2446,14 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2314
2446
|
try {
|
|
2315
2447
|
const now = /* @__PURE__ */ new Date();
|
|
2316
2448
|
if (sessionId) {
|
|
2449
|
+
const existing = await strapi2.documents(SESSION_UID$1).findOne({
|
|
2450
|
+
documentId: sessionId,
|
|
2451
|
+
fields: ["documentId"]
|
|
2452
|
+
});
|
|
2453
|
+
if (!existing) {
|
|
2454
|
+
log.warn(`Session ${sessionId} not found for termination`);
|
|
2455
|
+
return;
|
|
2456
|
+
}
|
|
2317
2457
|
await strapi2.documents(SESSION_UID$1).update({
|
|
2318
2458
|
documentId: sessionId,
|
|
2319
2459
|
data: {
|
|
@@ -2405,7 +2545,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2405
2545
|
strapi2.documents(SESSION_UID$1).update({
|
|
2406
2546
|
documentId: session2.documentId,
|
|
2407
2547
|
data: {
|
|
2408
|
-
geoLocation
|
|
2548
|
+
geoLocation,
|
|
2409
2549
|
securityScore: geoData.securityScore || null
|
|
2410
2550
|
}
|
|
2411
2551
|
}).catch(() => {
|
|
@@ -2455,7 +2595,8 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2455
2595
|
const sessions = await strapi2.documents(SESSION_UID$1).findMany({
|
|
2456
2596
|
filters: { isActive: true },
|
|
2457
2597
|
populate: { user: { fields: ["documentId", "email", "username"] } },
|
|
2458
|
-
sort: { loginTime: "desc" }
|
|
2598
|
+
sort: { loginTime: "desc" },
|
|
2599
|
+
limit: 1e3
|
|
2459
2600
|
});
|
|
2460
2601
|
const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
2461
2602
|
const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
|
|
@@ -2492,7 +2633,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2492
2633
|
strapi2.documents(SESSION_UID$1).update({
|
|
2493
2634
|
documentId: session2.documentId,
|
|
2494
2635
|
data: {
|
|
2495
|
-
geoLocation
|
|
2636
|
+
geoLocation,
|
|
2496
2637
|
securityScore: geoData.securityScore || null
|
|
2497
2638
|
}
|
|
2498
2639
|
}).catch(() => {
|
|
@@ -2585,7 +2726,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2585
2726
|
strapi2.documents(SESSION_UID$1).update({
|
|
2586
2727
|
documentId: session2.documentId,
|
|
2587
2728
|
data: {
|
|
2588
|
-
geoLocation
|
|
2729
|
+
geoLocation,
|
|
2589
2730
|
securityScore: geoData.securityScore || null
|
|
2590
2731
|
}
|
|
2591
2732
|
}).catch(() => {
|
|
@@ -2722,20 +2863,36 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2722
2863
|
}
|
|
2723
2864
|
},
|
|
2724
2865
|
/**
|
|
2725
|
-
* Delete all inactive sessions from database
|
|
2866
|
+
* Delete all inactive sessions from database in batches
|
|
2726
2867
|
* WARNING: This permanently deletes records!
|
|
2727
2868
|
* @returns {Promise<number>} Number of deleted sessions
|
|
2728
2869
|
*/
|
|
2729
2870
|
async deleteInactiveSessions() {
|
|
2730
2871
|
try {
|
|
2731
2872
|
log.info("[DELETE] Deleting all inactive sessions...");
|
|
2732
|
-
const inactiveSessions = await strapi2.documents(SESSION_UID$1).findMany({
|
|
2733
|
-
filters: { isActive: false }
|
|
2734
|
-
});
|
|
2735
2873
|
let deletedCount = 0;
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2874
|
+
const BATCH_SIZE = 100;
|
|
2875
|
+
let hasMore = true;
|
|
2876
|
+
while (hasMore) {
|
|
2877
|
+
const batch = await strapi2.documents(SESSION_UID$1).findMany({
|
|
2878
|
+
filters: { isActive: false },
|
|
2879
|
+
fields: ["documentId"],
|
|
2880
|
+
limit: BATCH_SIZE
|
|
2881
|
+
});
|
|
2882
|
+
if (!batch || batch.length === 0) {
|
|
2883
|
+
hasMore = false;
|
|
2884
|
+
break;
|
|
2885
|
+
}
|
|
2886
|
+
const deleteResults = await Promise.allSettled(
|
|
2887
|
+
batch.map(
|
|
2888
|
+
(session2) => strapi2.documents(SESSION_UID$1).delete({ documentId: session2.documentId })
|
|
2889
|
+
)
|
|
2890
|
+
);
|
|
2891
|
+
const batchDeleted = deleteResults.filter((r) => r.status === "fulfilled").length;
|
|
2892
|
+
deletedCount += batchDeleted;
|
|
2893
|
+
if (batch.length < BATCH_SIZE) {
|
|
2894
|
+
hasMore = false;
|
|
2895
|
+
}
|
|
2739
2896
|
}
|
|
2740
2897
|
log.info(`[SUCCESS] Deleted ${deletedCount} inactive sessions`);
|
|
2741
2898
|
return deletedCount;
|
|
@@ -2746,7 +2903,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2746
2903
|
}
|
|
2747
2904
|
};
|
|
2748
2905
|
};
|
|
2749
|
-
const version$1 = "4.4.
|
|
2906
|
+
const version$1 = "4.4.5";
|
|
2750
2907
|
const require$$2 = {
|
|
2751
2908
|
version: version$1
|
|
2752
2909
|
};
|
|
@@ -3020,7 +3177,7 @@ var geolocation$1 = ({ strapi: strapi2 }) => ({
|
|
|
3020
3177
|
*/
|
|
3021
3178
|
async getIpInfo(ipAddress) {
|
|
3022
3179
|
try {
|
|
3023
|
-
if (!ipAddress ||
|
|
3180
|
+
if (!ipAddress || this.isPrivateIp(ipAddress)) {
|
|
3024
3181
|
return {
|
|
3025
3182
|
ip: ipAddress,
|
|
3026
3183
|
country: "Local Network",
|
|
@@ -3123,6 +3280,26 @@ var geolocation$1 = ({ strapi: strapi2 }) => ({
|
|
|
3123
3280
|
const codePoints = countryCode.toUpperCase().split("").map((char) => 127397 + char.charCodeAt());
|
|
3124
3281
|
return String.fromCodePoint(...codePoints);
|
|
3125
3282
|
},
|
|
3283
|
+
/**
|
|
3284
|
+
* Checks if an IP address is private/local (RFC 1918, RFC 4193, loopback, link-local)
|
|
3285
|
+
* @param {string} ip - IP address to check
|
|
3286
|
+
* @returns {boolean} True if IP is private/local
|
|
3287
|
+
*/
|
|
3288
|
+
isPrivateIp(ip) {
|
|
3289
|
+
if (!ip || ip === "unknown") return true;
|
|
3290
|
+
if (ip === "127.0.0.1" || ip === "localhost" || ip === "::1") return true;
|
|
3291
|
+
if (ip.startsWith("192.168.")) return true;
|
|
3292
|
+
if (ip.startsWith("10.")) return true;
|
|
3293
|
+
if (ip.startsWith("172.")) {
|
|
3294
|
+
const second = parseInt(ip.split(".")[1], 10);
|
|
3295
|
+
if (second >= 16 && second <= 31) return true;
|
|
3296
|
+
}
|
|
3297
|
+
if (ip.startsWith("169.254.")) return true;
|
|
3298
|
+
if (ip.startsWith("fc00:") || ip.startsWith("fd00:")) return true;
|
|
3299
|
+
if (ip.startsWith("fe80:")) return true;
|
|
3300
|
+
if (ip === "::1") return true;
|
|
3301
|
+
return false;
|
|
3302
|
+
},
|
|
3126
3303
|
/**
|
|
3127
3304
|
* Fallback data when API fails
|
|
3128
3305
|
*/
|
|
@@ -3258,25 +3435,36 @@ VPN: {{reason.isVpn}}, Proxy: {{reason.isProxy}}`
|
|
|
3258
3435
|
};
|
|
3259
3436
|
},
|
|
3260
3437
|
/**
|
|
3261
|
-
*
|
|
3438
|
+
* Escapes HTML special characters to prevent XSS in email templates
|
|
3439
|
+
* @param {string} str - String to escape
|
|
3440
|
+
* @returns {string} HTML-safe string
|
|
3441
|
+
*/
|
|
3442
|
+
escapeHtml(str2) {
|
|
3443
|
+
if (!str2 || typeof str2 !== "string") return str2 || "";
|
|
3444
|
+
return str2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3445
|
+
},
|
|
3446
|
+
/**
|
|
3447
|
+
* Replace template variables with HTML-escaped actual values
|
|
3448
|
+
* SECURITY: All dynamic values are HTML-escaped to prevent XSS via email
|
|
3262
3449
|
*/
|
|
3263
3450
|
replaceVariables(template2, data) {
|
|
3264
3451
|
let result = template2;
|
|
3265
|
-
|
|
3266
|
-
result = result.replace(/\{\{user\.
|
|
3452
|
+
const esc2 = this.escapeHtml.bind(this);
|
|
3453
|
+
result = result.replace(/\{\{user\.email\}\}/g, esc2(data.user?.email || "N/A"));
|
|
3454
|
+
result = result.replace(/\{\{user\.username\}\}/g, esc2(data.user?.username || "N/A"));
|
|
3267
3455
|
result = result.replace(
|
|
3268
3456
|
/\{\{session\.loginTime\}\}/g,
|
|
3269
|
-
data.session?.loginTime ? new Date(data.session.loginTime).toLocaleString() : "N/A"
|
|
3457
|
+
esc2(data.session?.loginTime ? new Date(data.session.loginTime).toLocaleString() : "N/A")
|
|
3270
3458
|
);
|
|
3271
|
-
result = result.replace(/\{\{session\.ipAddress\}\}/g, data.session?.ipAddress || "N/A");
|
|
3272
|
-
result = result.replace(/\{\{session\.userAgent\}\}/g, data.session?.userAgent || "N/A");
|
|
3273
|
-
result = result.replace(/\{\{geo\.city\}\}/g, data.geoData?.city || "Unknown");
|
|
3274
|
-
result = result.replace(/\{\{geo\.country\}\}/g, data.geoData?.country || "Unknown");
|
|
3275
|
-
result = result.replace(/\{\{geo\.timezone\}\}/g, data.geoData?.timezone || "Unknown");
|
|
3459
|
+
result = result.replace(/\{\{session\.ipAddress\}\}/g, esc2(data.session?.ipAddress || "N/A"));
|
|
3460
|
+
result = result.replace(/\{\{session\.userAgent\}\}/g, esc2(data.session?.userAgent || "N/A"));
|
|
3461
|
+
result = result.replace(/\{\{geo\.city\}\}/g, esc2(data.geoData?.city || "Unknown"));
|
|
3462
|
+
result = result.replace(/\{\{geo\.country\}\}/g, esc2(data.geoData?.country || "Unknown"));
|
|
3463
|
+
result = result.replace(/\{\{geo\.timezone\}\}/g, esc2(data.geoData?.timezone || "Unknown"));
|
|
3276
3464
|
result = result.replace(/\{\{reason\.isVpn\}\}/g, data.reason?.isVpn ? "Yes" : "No");
|
|
3277
3465
|
result = result.replace(/\{\{reason\.isProxy\}\}/g, data.reason?.isProxy ? "Yes" : "No");
|
|
3278
3466
|
result = result.replace(/\{\{reason\.isThreat\}\}/g, data.reason?.isThreat ? "Yes" : "No");
|
|
3279
|
-
result = result.replace(/\{\{reason\.securityScore\}\}/g, data.reason?.securityScore || "0");
|
|
3467
|
+
result = result.replace(/\{\{reason\.securityScore\}\}/g, esc2(String(data.reason?.securityScore || "0")));
|
|
3280
3468
|
return result;
|
|
3281
3469
|
},
|
|
3282
3470
|
/**
|
|
@@ -39052,7 +39240,7 @@ const dist = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty
|
|
|
39052
39240
|
const require$$0 = /* @__PURE__ */ getAugmentedNamespace(dist);
|
|
39053
39241
|
const SESSION_UID = "plugin::magic-sessionmanager.session";
|
|
39054
39242
|
const { errors } = require$$0;
|
|
39055
|
-
var sessionRequired$1 = async (policyContext,
|
|
39243
|
+
var sessionRequired$1 = async (policyContext, _policyConfig, { strapi: strapi2 }) => {
|
|
39056
39244
|
if (!policyContext.state.user) {
|
|
39057
39245
|
return true;
|
|
39058
39246
|
}
|
|
@@ -39070,8 +39258,8 @@ var sessionRequired$1 = async (policyContext, config2, { strapi: strapi2 }) => {
|
|
|
39070
39258
|
if (!userDocId) {
|
|
39071
39259
|
return true;
|
|
39072
39260
|
}
|
|
39073
|
-
const
|
|
39074
|
-
const strictMode =
|
|
39261
|
+
const pluginConfig = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
39262
|
+
const strictMode = pluginConfig.strictSessionEnforcement === true;
|
|
39075
39263
|
const activeSessions = await strapi2.documents(SESSION_UID).findMany({
|
|
39076
39264
|
filters: {
|
|
39077
39265
|
user: { documentId: userDocId },
|