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.mjs
CHANGED
|
@@ -159,11 +159,19 @@ const getClientIp$1 = (ctx) => {
|
|
|
159
159
|
};
|
|
160
160
|
const cleanIp = (ip) => {
|
|
161
161
|
if (!ip) return "unknown";
|
|
162
|
-
ip = ip.
|
|
162
|
+
ip = ip.trim();
|
|
163
|
+
if (ip.startsWith("[")) {
|
|
164
|
+
const bracketEnd = ip.indexOf("]");
|
|
165
|
+
if (bracketEnd !== -1) {
|
|
166
|
+
ip = ip.substring(1, bracketEnd);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
163
169
|
if (ip.startsWith("::ffff:")) {
|
|
164
170
|
ip = ip.substring(7);
|
|
165
171
|
}
|
|
166
|
-
ip
|
|
172
|
+
if (ip.includes(".") && ip.includes(":") && ip.indexOf(":") === ip.lastIndexOf(":")) {
|
|
173
|
+
ip = ip.split(":")[0];
|
|
174
|
+
}
|
|
167
175
|
return ip || "unknown";
|
|
168
176
|
};
|
|
169
177
|
const isPrivateIp = (ip) => {
|
|
@@ -207,11 +215,12 @@ function encryptToken$2(token) {
|
|
|
207
215
|
const authTag = cipher.getAuthTag();
|
|
208
216
|
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
|
|
209
217
|
} catch (err) {
|
|
210
|
-
|
|
218
|
+
const errMsg = err instanceof Error ? err.message : "Unknown encryption error";
|
|
219
|
+
console.error("[magic-sessionmanager/encryption] Encryption failed:", errMsg);
|
|
211
220
|
throw new Error("Failed to encrypt token");
|
|
212
221
|
}
|
|
213
222
|
}
|
|
214
|
-
function decryptToken$
|
|
223
|
+
function decryptToken$2(encryptedToken) {
|
|
215
224
|
if (!encryptedToken) return null;
|
|
216
225
|
try {
|
|
217
226
|
const key = getEncryptionKey();
|
|
@@ -228,7 +237,8 @@ function decryptToken$3(encryptedToken) {
|
|
|
228
237
|
decrypted += decipher.final("utf8");
|
|
229
238
|
return decrypted;
|
|
230
239
|
} catch (err) {
|
|
231
|
-
|
|
240
|
+
const errMsg = err instanceof Error ? err.message : "Unknown decryption error";
|
|
241
|
+
console.error("[magic-sessionmanager/encryption] Decryption failed:", errMsg);
|
|
232
242
|
return null;
|
|
233
243
|
}
|
|
234
244
|
}
|
|
@@ -244,7 +254,7 @@ function hashToken$3(token) {
|
|
|
244
254
|
}
|
|
245
255
|
var encryption = {
|
|
246
256
|
encryptToken: encryptToken$2,
|
|
247
|
-
decryptToken: decryptToken$
|
|
257
|
+
decryptToken: decryptToken$2,
|
|
248
258
|
generateSessionId: generateSessionId$1,
|
|
249
259
|
hashToken: hashToken$3
|
|
250
260
|
};
|
|
@@ -275,12 +285,25 @@ function isAuthEndpoint(path2) {
|
|
|
275
285
|
}
|
|
276
286
|
const userIdCache = /* @__PURE__ */ new Map();
|
|
277
287
|
const CACHE_TTL = 5 * 60 * 1e3;
|
|
288
|
+
const CACHE_MAX_SIZE = 1e3;
|
|
278
289
|
async function getDocumentIdFromNumericId(strapi2, numericId) {
|
|
279
290
|
const cacheKey = `user_${numericId}`;
|
|
280
291
|
const cached2 = userIdCache.get(cacheKey);
|
|
281
292
|
if (cached2 && Date.now() - cached2.timestamp < CACHE_TTL) {
|
|
282
293
|
return cached2.documentId;
|
|
283
294
|
}
|
|
295
|
+
if (userIdCache.size >= CACHE_MAX_SIZE) {
|
|
296
|
+
const now = Date.now();
|
|
297
|
+
for (const [key, value] of userIdCache) {
|
|
298
|
+
if (now - value.timestamp >= CACHE_TTL) {
|
|
299
|
+
userIdCache.delete(key);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (userIdCache.size >= CACHE_MAX_SIZE) {
|
|
303
|
+
const keysToDelete = [...userIdCache.keys()].slice(0, Math.floor(CACHE_MAX_SIZE / 4));
|
|
304
|
+
keysToDelete.forEach((key) => userIdCache.delete(key));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
284
307
|
try {
|
|
285
308
|
const user = await strapi2.entityService.findOne(USER_UID$3, numericId, {
|
|
286
309
|
fields: ["documentId"]
|
|
@@ -323,7 +346,7 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
323
346
|
isActive: false
|
|
324
347
|
},
|
|
325
348
|
limit: 5,
|
|
326
|
-
fields: ["documentId", "terminatedManually", "lastActive"],
|
|
349
|
+
fields: ["documentId", "terminatedManually", "lastActive", "loginTime"],
|
|
327
350
|
sort: [{ lastActive: "desc" }]
|
|
328
351
|
});
|
|
329
352
|
if (inactiveSessions && inactiveSessions.length > 0) {
|
|
@@ -333,6 +356,14 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
333
356
|
return ctx.unauthorized("Session has been terminated. Please login again.");
|
|
334
357
|
}
|
|
335
358
|
const sessionToReactivate = inactiveSessions[0];
|
|
359
|
+
const maxAgeDays = config2.maxSessionAgeDays || 30;
|
|
360
|
+
const loginTime = sessionToReactivate.loginTime ? new Date(sessionToReactivate.loginTime).getTime() : sessionToReactivate.lastActive ? new Date(sessionToReactivate.lastActive).getTime() : 0;
|
|
361
|
+
const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
362
|
+
const isExpired = loginTime > 0 && Date.now() - loginTime > maxAgeMs;
|
|
363
|
+
if (isExpired) {
|
|
364
|
+
strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Session exceeded max age of ${maxAgeDays} days (user: ${userDocId2.substring(0, 8)}...)`);
|
|
365
|
+
return ctx.unauthorized("Session expired. Please login again.");
|
|
366
|
+
}
|
|
336
367
|
await strapi2.documents(SESSION_UID$4).update({
|
|
337
368
|
documentId: sessionToReactivate.documentId,
|
|
338
369
|
data: {
|
|
@@ -373,7 +404,7 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
373
404
|
};
|
|
374
405
|
};
|
|
375
406
|
const getClientIp = getClientIp_1;
|
|
376
|
-
const { encryptToken: encryptToken$1, decryptToken: decryptToken$
|
|
407
|
+
const { encryptToken: encryptToken$1, decryptToken: decryptToken$1, hashToken: hashToken$2 } = encryption;
|
|
377
408
|
const { createLogger: createLogger$3 } = logger;
|
|
378
409
|
const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
|
|
379
410
|
const USER_UID$2 = "plugin::users-permissions.user";
|
|
@@ -407,11 +438,11 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
407
438
|
log.info("║ [SUCCESS] SESSION MANAGER LICENSE ACTIVE ║");
|
|
408
439
|
log.info("║ ║");
|
|
409
440
|
if (licenseStatus.data) {
|
|
410
|
-
|
|
441
|
+
const maskedKey = licenseStatus.data.licenseKey ? `${licenseStatus.data.licenseKey.substring(0, 8)}...` : "N/A";
|
|
442
|
+
log.info(`║ License: ${maskedKey}`.padEnd(66) + "║");
|
|
411
443
|
log.info(`║ User: ${licenseStatus.data.firstName} ${licenseStatus.data.lastName}`.padEnd(66) + "║");
|
|
412
|
-
log.info(`║ Email: ${licenseStatus.data.email}`.padEnd(66) + "║");
|
|
413
444
|
} else if (storedKey) {
|
|
414
|
-
log.info(`║ License: ${storedKey} (Offline Mode)`.padEnd(66) + "║");
|
|
445
|
+
log.info(`║ License: ${storedKey.substring(0, 8)}... (Offline Mode)`.padEnd(66) + "║");
|
|
415
446
|
log.info(`║ Status: Grace Period Active`.padEnd(66) + "║");
|
|
416
447
|
}
|
|
417
448
|
log.info("║ ║");
|
|
@@ -443,10 +474,21 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
443
474
|
try {
|
|
444
475
|
const token = ctx.request.headers?.authorization?.replace("Bearer ", "");
|
|
445
476
|
if (!token) {
|
|
446
|
-
ctx.status =
|
|
447
|
-
ctx.body = { message: "
|
|
477
|
+
ctx.status = 401;
|
|
478
|
+
ctx.body = { error: { status: 401, message: "Authorization token required" } };
|
|
448
479
|
return;
|
|
449
480
|
}
|
|
481
|
+
try {
|
|
482
|
+
const jwtService = strapi2.plugin("users-permissions").service("jwt");
|
|
483
|
+
const decoded = await jwtService.verify(token);
|
|
484
|
+
if (!decoded || !decoded.id) {
|
|
485
|
+
ctx.status = 401;
|
|
486
|
+
ctx.body = { error: { status: 401, message: "Invalid token" } };
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
} catch (jwtErr) {
|
|
490
|
+
log.debug("JWT verify failed during logout (cleaning up anyway):", jwtErr.message);
|
|
491
|
+
}
|
|
450
492
|
const tokenHashValue = hashToken$2(token);
|
|
451
493
|
const matchingSession = await strapi2.documents(SESSION_UID$3).findFirst({
|
|
452
494
|
filters: {
|
|
@@ -468,6 +510,7 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
468
510
|
},
|
|
469
511
|
config: {
|
|
470
512
|
auth: false
|
|
513
|
+
// We handle auth manually above to support expired-but-valid tokens
|
|
471
514
|
}
|
|
472
515
|
}]);
|
|
473
516
|
log.info("[SUCCESS] /api/auth/logout route registered");
|
|
@@ -527,8 +570,7 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
527
570
|
ctx.body = {
|
|
528
571
|
error: {
|
|
529
572
|
status: 403,
|
|
530
|
-
message: "Login blocked for security reasons"
|
|
531
|
-
details: { reason: blockReason }
|
|
573
|
+
message: "Login blocked for security reasons. Please contact support."
|
|
532
574
|
}
|
|
533
575
|
};
|
|
534
576
|
return;
|
|
@@ -590,11 +632,22 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
590
632
|
geoData
|
|
591
633
|
});
|
|
592
634
|
if (config2.discordWebhookUrl) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
635
|
+
const webhookUrl = config2.discordWebhookUrl;
|
|
636
|
+
try {
|
|
637
|
+
const parsed = new URL(webhookUrl);
|
|
638
|
+
const isValidDomain = parsed.protocol === "https:" && (parsed.hostname === "discord.com" || parsed.hostname === "discordapp.com" || parsed.hostname.endsWith(".discord.com") || parsed.hostname === "hooks.slack.com");
|
|
639
|
+
if (isValidDomain) {
|
|
640
|
+
await notificationService.sendWebhook({
|
|
641
|
+
event: "session.login",
|
|
642
|
+
data: webhookData,
|
|
643
|
+
webhookUrl
|
|
644
|
+
});
|
|
645
|
+
} else {
|
|
646
|
+
log.warn(`[SECURITY] Blocked webhook to untrusted domain: ${parsed.hostname}`);
|
|
647
|
+
}
|
|
648
|
+
} catch {
|
|
649
|
+
log.warn("[SECURITY] Invalid webhook URL in plugin config");
|
|
650
|
+
}
|
|
598
651
|
}
|
|
599
652
|
}
|
|
600
653
|
} catch (notifErr) {
|
|
@@ -775,6 +828,30 @@ async function ensureTokenHashIndex(strapi2, log) {
|
|
|
775
828
|
log.debug("[INDEX] Could not create tokenHash index (will retry on next startup):", err.message);
|
|
776
829
|
}
|
|
777
830
|
}
|
|
831
|
+
const sessionCheckErrors = { count: 0, lastReset: Date.now() };
|
|
832
|
+
const MAX_CONSECUTIVE_ERRORS = 10;
|
|
833
|
+
const ERROR_RESET_INTERVAL = 60 * 1e3;
|
|
834
|
+
function shouldFailOpen() {
|
|
835
|
+
const now = Date.now();
|
|
836
|
+
if (now - sessionCheckErrors.lastReset > ERROR_RESET_INTERVAL) {
|
|
837
|
+
sessionCheckErrors.count = 0;
|
|
838
|
+
sessionCheckErrors.lastReset = now;
|
|
839
|
+
}
|
|
840
|
+
sessionCheckErrors.count++;
|
|
841
|
+
if (sessionCheckErrors.count > MAX_CONSECUTIVE_ERRORS) {
|
|
842
|
+
return false;
|
|
843
|
+
}
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
function resetErrorCounter() {
|
|
847
|
+
sessionCheckErrors.count = 0;
|
|
848
|
+
}
|
|
849
|
+
function isSessionExpired(session2, maxAgeDays = 30) {
|
|
850
|
+
if (!session2.loginTime) return false;
|
|
851
|
+
const loginTime = new Date(session2.loginTime).getTime();
|
|
852
|
+
const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
853
|
+
return Date.now() - loginTime > maxAgeMs;
|
|
854
|
+
}
|
|
778
855
|
async function registerSessionAwareAuthStrategy(strapi2, log) {
|
|
779
856
|
try {
|
|
780
857
|
const usersPermissionsPlugin = strapi2.plugin("users-permissions");
|
|
@@ -796,15 +873,15 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
|
|
|
796
873
|
}
|
|
797
874
|
const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
798
875
|
const strictMode = config2.strictSessionEnforcement === true;
|
|
876
|
+
const maxSessionAgeDays = config2.maxSessionAgeDays || 30;
|
|
799
877
|
try {
|
|
800
878
|
const tokenHashValue = hashToken$2(token);
|
|
801
|
-
let userDocId = null;
|
|
802
879
|
const user = await strapi2.entityService.findOne(
|
|
803
880
|
"plugin::users-permissions.user",
|
|
804
881
|
decoded.id,
|
|
805
882
|
{ fields: ["documentId"] }
|
|
806
883
|
);
|
|
807
|
-
userDocId = user?.documentId;
|
|
884
|
+
const userDocId = user?.documentId;
|
|
808
885
|
if (!userDocId) {
|
|
809
886
|
strapi2.log.debug("[magic-sessionmanager] [JWT] No documentId found, allowing through");
|
|
810
887
|
return decoded;
|
|
@@ -814,9 +891,19 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
|
|
|
814
891
|
user: { documentId: userDocId },
|
|
815
892
|
tokenHash: tokenHashValue
|
|
816
893
|
},
|
|
817
|
-
fields: ["documentId", "isActive", "terminatedManually", "lastActive"]
|
|
894
|
+
fields: ["documentId", "isActive", "terminatedManually", "lastActive", "loginTime"]
|
|
818
895
|
});
|
|
819
896
|
if (thisSession) {
|
|
897
|
+
if (isSessionExpired(thisSession, maxSessionAgeDays)) {
|
|
898
|
+
strapi2.log.info(
|
|
899
|
+
`[magic-sessionmanager] [JWT-EXPIRED] Session exceeded max age of ${maxSessionAgeDays} days (user: ${userDocId.substring(0, 8)}...)`
|
|
900
|
+
);
|
|
901
|
+
await strapi2.documents(SESSION_UID$3).update({
|
|
902
|
+
documentId: thisSession.documentId,
|
|
903
|
+
data: { isActive: false, terminatedManually: true, logoutTime: /* @__PURE__ */ new Date() }
|
|
904
|
+
});
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
820
907
|
if (thisSession.terminatedManually === true) {
|
|
821
908
|
strapi2.log.info(
|
|
822
909
|
`[magic-sessionmanager] [JWT-BLOCKED] Session was manually terminated (user: ${userDocId.substring(0, 8)}...)`
|
|
@@ -824,38 +911,32 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
|
|
|
824
911
|
return null;
|
|
825
912
|
}
|
|
826
913
|
if (thisSession.isActive) {
|
|
914
|
+
resetErrorCounter();
|
|
827
915
|
return decoded;
|
|
828
916
|
}
|
|
829
917
|
await strapi2.documents(SESSION_UID$3).update({
|
|
830
918
|
documentId: thisSession.documentId,
|
|
831
|
-
data: {
|
|
832
|
-
isActive: true,
|
|
833
|
-
lastActive: /* @__PURE__ */ new Date()
|
|
834
|
-
}
|
|
919
|
+
data: { isActive: true, lastActive: /* @__PURE__ */ new Date() }
|
|
835
920
|
});
|
|
836
921
|
strapi2.log.info(
|
|
837
922
|
`[magic-sessionmanager] [JWT-REACTIVATED] Session reactivated for user ${userDocId.substring(0, 8)}...`
|
|
838
923
|
);
|
|
924
|
+
resetErrorCounter();
|
|
839
925
|
return decoded;
|
|
840
926
|
}
|
|
841
927
|
const anyActiveSessions = await strapi2.documents(SESSION_UID$3).findMany({
|
|
842
|
-
filters: {
|
|
843
|
-
user: { documentId: userDocId },
|
|
844
|
-
isActive: true
|
|
845
|
-
},
|
|
928
|
+
filters: { user: { documentId: userDocId }, isActive: true },
|
|
846
929
|
limit: 1
|
|
847
930
|
});
|
|
848
931
|
if (anyActiveSessions && anyActiveSessions.length > 0) {
|
|
849
932
|
strapi2.log.debug(
|
|
850
933
|
`[magic-sessionmanager] [JWT] No session for token but user has other active sessions (allowing)`
|
|
851
934
|
);
|
|
935
|
+
resetErrorCounter();
|
|
852
936
|
return decoded;
|
|
853
937
|
}
|
|
854
938
|
const terminatedSessions = await strapi2.documents(SESSION_UID$3).findMany({
|
|
855
|
-
filters: {
|
|
856
|
-
user: { documentId: userDocId },
|
|
857
|
-
terminatedManually: true
|
|
858
|
-
},
|
|
939
|
+
filters: { user: { documentId: userDocId }, terminatedManually: true },
|
|
859
940
|
limit: 1
|
|
860
941
|
});
|
|
861
942
|
if (terminatedSessions && terminatedSessions.length > 0) {
|
|
@@ -873,10 +954,15 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
|
|
|
873
954
|
strapi2.log.warn(
|
|
874
955
|
`[magic-sessionmanager] [JWT-WARN] No session for user ${userDocId.substring(0, 8)}... (allowing)`
|
|
875
956
|
);
|
|
957
|
+
resetErrorCounter();
|
|
876
958
|
return decoded;
|
|
877
959
|
} catch (err) {
|
|
878
|
-
|
|
879
|
-
|
|
960
|
+
if (shouldFailOpen()) {
|
|
961
|
+
strapi2.log.warn("[magic-sessionmanager] [JWT] Session check error (allowing):", err.message);
|
|
962
|
+
return decoded;
|
|
963
|
+
}
|
|
964
|
+
strapi2.log.error("[magic-sessionmanager] [JWT] Too many consecutive errors, blocking request:", err.message);
|
|
965
|
+
return null;
|
|
880
966
|
}
|
|
881
967
|
};
|
|
882
968
|
strapi2.log.info("[magic-sessionmanager] [AUTH] [SUCCESS] JWT verify wrapped with session validation");
|
|
@@ -1341,7 +1427,7 @@ function extractVersion(userAgent, regex) {
|
|
|
1341
1427
|
var userAgentParser = {
|
|
1342
1428
|
parseUserAgent: parseUserAgent$2
|
|
1343
1429
|
};
|
|
1344
|
-
const {
|
|
1430
|
+
const { hashToken: hashToken$1 } = encryption;
|
|
1345
1431
|
const { parseUserAgent: parseUserAgent$1 } = userAgentParser;
|
|
1346
1432
|
const SESSION_UID$2 = "plugin::magic-sessionmanager.session";
|
|
1347
1433
|
const USER_UID$1 = "plugin::users-permissions.user";
|
|
@@ -1401,7 +1487,8 @@ var session$3 = {
|
|
|
1401
1487
|
}
|
|
1402
1488
|
const allSessions = await strapi.documents(SESSION_UID$2).findMany({
|
|
1403
1489
|
filters: { user: { documentId: userId } },
|
|
1404
|
-
sort: { loginTime: "desc" }
|
|
1490
|
+
sort: { loginTime: "desc" },
|
|
1491
|
+
limit: 200
|
|
1405
1492
|
});
|
|
1406
1493
|
const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
|
|
1407
1494
|
const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
|
|
@@ -1439,7 +1526,7 @@ var session$3 = {
|
|
|
1439
1526
|
strapi.documents(SESSION_UID$2).update({
|
|
1440
1527
|
documentId: session2.documentId,
|
|
1441
1528
|
data: {
|
|
1442
|
-
geoLocation
|
|
1529
|
+
geoLocation,
|
|
1443
1530
|
securityScore: geoData.securityScore || null
|
|
1444
1531
|
}
|
|
1445
1532
|
}).catch(() => {
|
|
@@ -1501,9 +1588,15 @@ var session$3 = {
|
|
|
1501
1588
|
const { userId } = ctx.params;
|
|
1502
1589
|
const isAdminRequest = ctx.state.userAbility || ctx.state.admin;
|
|
1503
1590
|
const requestingUserDocId = ctx.state.user?.documentId;
|
|
1504
|
-
if (!isAdminRequest
|
|
1505
|
-
|
|
1506
|
-
|
|
1591
|
+
if (!isAdminRequest) {
|
|
1592
|
+
if (!requestingUserDocId) {
|
|
1593
|
+
strapi.log.warn(`[magic-sessionmanager] Security: Request without documentId tried to access sessions of user ${userId}`);
|
|
1594
|
+
return ctx.forbidden("Cannot verify user identity");
|
|
1595
|
+
}
|
|
1596
|
+
if (String(requestingUserDocId) !== String(userId)) {
|
|
1597
|
+
strapi.log.warn(`[magic-sessionmanager] Security: User ${requestingUserDocId} tried to access sessions of user ${userId}`);
|
|
1598
|
+
return ctx.forbidden("You can only access your own sessions");
|
|
1599
|
+
}
|
|
1507
1600
|
}
|
|
1508
1601
|
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
1509
1602
|
const sessions = await sessionService.getUserSessions(userId);
|
|
@@ -1627,7 +1720,7 @@ var session$3 = {
|
|
|
1627
1720
|
strapi.documents(SESSION_UID$2).update({
|
|
1628
1721
|
documentId: currentSession.documentId,
|
|
1629
1722
|
data: {
|
|
1630
|
-
geoLocation
|
|
1723
|
+
geoLocation,
|
|
1631
1724
|
securityScore: geoData.securityScore || null
|
|
1632
1725
|
}
|
|
1633
1726
|
}).catch(() => {
|
|
@@ -1709,22 +1802,6 @@ var session$3 = {
|
|
|
1709
1802
|
ctx.throw(500, "Error terminating session");
|
|
1710
1803
|
}
|
|
1711
1804
|
},
|
|
1712
|
-
/**
|
|
1713
|
-
* Terminate specific session
|
|
1714
|
-
* DELETE /magic-sessionmanager/sessions/:sessionId
|
|
1715
|
-
*/
|
|
1716
|
-
async terminateSession(ctx) {
|
|
1717
|
-
try {
|
|
1718
|
-
const { sessionId } = ctx.params;
|
|
1719
|
-
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
1720
|
-
await sessionService.terminateSession({ sessionId });
|
|
1721
|
-
ctx.body = {
|
|
1722
|
-
message: `Session ${sessionId} terminated`
|
|
1723
|
-
};
|
|
1724
|
-
} catch (err) {
|
|
1725
|
-
ctx.throw(500, "Error terminating session");
|
|
1726
|
-
}
|
|
1727
|
-
},
|
|
1728
1805
|
/**
|
|
1729
1806
|
* Simulate session timeout for testing (Admin action)
|
|
1730
1807
|
* POST /magic-sessionmanager/sessions/:sessionId/simulate-timeout
|
|
@@ -1733,6 +1810,10 @@ var session$3 = {
|
|
|
1733
1810
|
*/
|
|
1734
1811
|
async simulateTimeout(ctx) {
|
|
1735
1812
|
try {
|
|
1813
|
+
const nodeEnv = process.env.NODE_ENV || "development";
|
|
1814
|
+
if (nodeEnv === "production") {
|
|
1815
|
+
return ctx.forbidden("simulate-timeout is disabled in production");
|
|
1816
|
+
}
|
|
1736
1817
|
const { sessionId } = ctx.params;
|
|
1737
1818
|
const session2 = await strapi.documents(SESSION_UID$2).findOne({
|
|
1738
1819
|
documentId: sessionId
|
|
@@ -1805,9 +1886,15 @@ var session$3 = {
|
|
|
1805
1886
|
if (!ipAddress) {
|
|
1806
1887
|
return ctx.badRequest("IP address is required");
|
|
1807
1888
|
}
|
|
1808
|
-
const IPV4_REGEX = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
1809
|
-
const
|
|
1810
|
-
|
|
1889
|
+
const IPV4_REGEX = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
1890
|
+
const ipv4Match = ipAddress.match(IPV4_REGEX);
|
|
1891
|
+
const isValidIpv4 = ipv4Match && ipv4Match.slice(1).every((octet) => {
|
|
1892
|
+
const n = parseInt(octet, 10);
|
|
1893
|
+
return n >= 0 && n <= 255;
|
|
1894
|
+
});
|
|
1895
|
+
const IPV6_REGEX = /^[0-9a-fA-F:]{3,45}$/;
|
|
1896
|
+
const isValidIpv6 = !isValidIpv4 && IPV6_REGEX.test(ipAddress) && (ipAddress.match(/:/g) || []).length >= 2;
|
|
1897
|
+
if (!isValidIpv4 && !isValidIpv6) {
|
|
1811
1898
|
return ctx.badRequest("Invalid IP address format");
|
|
1812
1899
|
}
|
|
1813
1900
|
const licenseGuard2 = strapi.plugin("magic-sessionmanager").service("license-guard");
|
|
@@ -1969,12 +2056,11 @@ var license$1 = ({ strapi: strapi2 }) => ({
|
|
|
1969
2056
|
message: "No license found. Running in demo mode."
|
|
1970
2057
|
});
|
|
1971
2058
|
}
|
|
1972
|
-
strapi2.log.info(`[magic-sessionmanager/license-controller] Checking stored license: ${licenseKey}
|
|
2059
|
+
strapi2.log.info(`[magic-sessionmanager/license-controller] Checking stored license: ${licenseKey.substring(0, 8)}...`);
|
|
1973
2060
|
const verification = await licenseGuard2.verifyLicense(licenseKey);
|
|
1974
2061
|
const license2 = await licenseGuard2.getLicenseByKey(licenseKey);
|
|
1975
2062
|
strapi2.log.info("[magic-sessionmanager/license-controller] License data from MagicAPI:", {
|
|
1976
|
-
licenseKey: license2?.licenseKey,
|
|
1977
|
-
email: license2?.email,
|
|
2063
|
+
licenseKey: license2?.licenseKey ? `${license2.licenseKey.substring(0, 8)}...` : "N/A",
|
|
1978
2064
|
featurePremium: license2?.featurePremium,
|
|
1979
2065
|
isActive: license2?.isActive,
|
|
1980
2066
|
pluginName: license2?.pluginName
|
|
@@ -2118,6 +2204,55 @@ var license$1 = ({ strapi: strapi2 }) => ({
|
|
|
2118
2204
|
}
|
|
2119
2205
|
}
|
|
2120
2206
|
});
|
|
2207
|
+
const ALLOWED_WEBHOOK_DOMAINS = {
|
|
2208
|
+
discord: ["discord.com", "discordapp.com"],
|
|
2209
|
+
slack: ["hooks.slack.com"]
|
|
2210
|
+
};
|
|
2211
|
+
function sanitizeWebhookUrl(url, type2) {
|
|
2212
|
+
if (!url || typeof url !== "string") return "";
|
|
2213
|
+
const trimmed = url.trim();
|
|
2214
|
+
if (!trimmed) return "";
|
|
2215
|
+
try {
|
|
2216
|
+
const parsed = new URL(trimmed);
|
|
2217
|
+
if (parsed.protocol !== "https:") return "";
|
|
2218
|
+
const allowedDomains = ALLOWED_WEBHOOK_DOMAINS[type2] || [];
|
|
2219
|
+
const isAllowed = allowedDomains.some(
|
|
2220
|
+
(domain) => parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`)
|
|
2221
|
+
);
|
|
2222
|
+
if (!isAllowed) return "";
|
|
2223
|
+
return trimmed;
|
|
2224
|
+
} catch {
|
|
2225
|
+
return "";
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
function sanitizeCountryList(list) {
|
|
2229
|
+
if (!Array.isArray(list)) return [];
|
|
2230
|
+
return list.filter((code) => typeof code === "string" && /^[A-Z]{2}$/.test(code.trim().toUpperCase())).map((code) => code.trim().toUpperCase());
|
|
2231
|
+
}
|
|
2232
|
+
function sanitizeEmailTemplates(templates) {
|
|
2233
|
+
const defaults = {
|
|
2234
|
+
suspiciousLogin: { subject: "", html: "", text: "" },
|
|
2235
|
+
newLocation: { subject: "", html: "", text: "" },
|
|
2236
|
+
vpnProxy: { subject: "", html: "", text: "" }
|
|
2237
|
+
};
|
|
2238
|
+
if (!templates || typeof templates !== "object") return defaults;
|
|
2239
|
+
const dangerousTags = /<\s*\/?\s*(script|iframe|object|embed|form|input|button|link|meta|base)\b[^>]*>/gi;
|
|
2240
|
+
const dangerousAttrs = /\s(on\w+|javascript\s*:)[^=]*=/gi;
|
|
2241
|
+
const result = {};
|
|
2242
|
+
for (const [key, defaultVal] of Object.entries(defaults)) {
|
|
2243
|
+
const tpl = templates[key];
|
|
2244
|
+
if (!tpl || typeof tpl !== "object") {
|
|
2245
|
+
result[key] = defaultVal;
|
|
2246
|
+
continue;
|
|
2247
|
+
}
|
|
2248
|
+
result[key] = {
|
|
2249
|
+
subject: typeof tpl.subject === "string" ? tpl.subject.substring(0, 200) : "",
|
|
2250
|
+
html: typeof tpl.html === "string" ? tpl.html.replace(dangerousTags, "").replace(dangerousAttrs, " ").substring(0, 1e4) : "",
|
|
2251
|
+
text: typeof tpl.text === "string" ? tpl.text.substring(0, 5e3) : ""
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
return result;
|
|
2255
|
+
}
|
|
2121
2256
|
var settings$1 = {
|
|
2122
2257
|
/**
|
|
2123
2258
|
* Get plugin settings
|
|
@@ -2179,29 +2314,26 @@ var settings$1 = {
|
|
|
2179
2314
|
name: "magic-sessionmanager"
|
|
2180
2315
|
});
|
|
2181
2316
|
const sanitizedSettings = {
|
|
2182
|
-
inactivityTimeout: parseInt(body.inactivityTimeout) || 15,
|
|
2183
|
-
cleanupInterval: parseInt(body.cleanupInterval) || 30,
|
|
2184
|
-
lastSeenRateLimit: parseInt(body.lastSeenRateLimit) || 30,
|
|
2185
|
-
retentionDays: parseInt(body.retentionDays) || 90,
|
|
2317
|
+
inactivityTimeout: Math.max(1, Math.min(parseInt(body.inactivityTimeout) || 15, 1440)),
|
|
2318
|
+
cleanupInterval: Math.max(5, Math.min(parseInt(body.cleanupInterval) || 30, 1440)),
|
|
2319
|
+
lastSeenRateLimit: Math.max(5, Math.min(parseInt(body.lastSeenRateLimit) || 30, 300)),
|
|
2320
|
+
retentionDays: Math.max(1, Math.min(parseInt(body.retentionDays) || 90, 365)),
|
|
2321
|
+
maxSessionAgeDays: Math.max(1, Math.min(parseInt(body.maxSessionAgeDays) || 30, 365)),
|
|
2186
2322
|
enableGeolocation: !!body.enableGeolocation,
|
|
2187
2323
|
enableSecurityScoring: !!body.enableSecurityScoring,
|
|
2188
2324
|
blockSuspiciousSessions: !!body.blockSuspiciousSessions,
|
|
2189
|
-
maxFailedLogins: parseInt(body.maxFailedLogins) || 5,
|
|
2325
|
+
maxFailedLogins: Math.max(1, Math.min(parseInt(body.maxFailedLogins) || 5, 100)),
|
|
2190
2326
|
enableEmailAlerts: !!body.enableEmailAlerts,
|
|
2191
2327
|
alertOnSuspiciousLogin: !!body.alertOnSuspiciousLogin,
|
|
2192
2328
|
alertOnNewLocation: !!body.alertOnNewLocation,
|
|
2193
2329
|
alertOnVpnProxy: !!body.alertOnVpnProxy,
|
|
2194
2330
|
enableWebhooks: !!body.enableWebhooks,
|
|
2195
|
-
discordWebhookUrl:
|
|
2196
|
-
slackWebhookUrl:
|
|
2331
|
+
discordWebhookUrl: sanitizeWebhookUrl(body.discordWebhookUrl, "discord"),
|
|
2332
|
+
slackWebhookUrl: sanitizeWebhookUrl(body.slackWebhookUrl, "slack"),
|
|
2197
2333
|
enableGeofencing: !!body.enableGeofencing,
|
|
2198
|
-
allowedCountries:
|
|
2199
|
-
blockedCountries:
|
|
2200
|
-
emailTemplates: body.emailTemplates
|
|
2201
|
-
suspiciousLogin: { subject: "", html: "", text: "" },
|
|
2202
|
-
newLocation: { subject: "", html: "", text: "" },
|
|
2203
|
-
vpnProxy: { subject: "", html: "", text: "" }
|
|
2204
|
-
}
|
|
2334
|
+
allowedCountries: sanitizeCountryList(body.allowedCountries),
|
|
2335
|
+
blockedCountries: sanitizeCountryList(body.blockedCountries),
|
|
2336
|
+
emailTemplates: sanitizeEmailTemplates(body.emailTemplates)
|
|
2205
2337
|
};
|
|
2206
2338
|
await pluginStore.set({
|
|
2207
2339
|
key: "settings",
|
|
@@ -2272,15 +2404,15 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2272
2404
|
deviceType: parsedUA.deviceType,
|
|
2273
2405
|
browserName: parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName,
|
|
2274
2406
|
osName: parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName,
|
|
2275
|
-
// Geolocation data (
|
|
2276
|
-
geoLocation: geoData ?
|
|
2407
|
+
// Geolocation data (stored as JSON object, schema type: json)
|
|
2408
|
+
geoLocation: geoData ? {
|
|
2277
2409
|
country: geoData.country,
|
|
2278
2410
|
country_code: geoData.country_code,
|
|
2279
2411
|
country_flag: geoData.country_flag,
|
|
2280
2412
|
city: geoData.city,
|
|
2281
2413
|
region: geoData.region,
|
|
2282
2414
|
timezone: geoData.timezone
|
|
2283
|
-
}
|
|
2415
|
+
} : null,
|
|
2284
2416
|
securityScore: geoData?.securityScore || null
|
|
2285
2417
|
}
|
|
2286
2418
|
});
|
|
@@ -2301,6 +2433,14 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2301
2433
|
try {
|
|
2302
2434
|
const now = /* @__PURE__ */ new Date();
|
|
2303
2435
|
if (sessionId) {
|
|
2436
|
+
const existing = await strapi2.documents(SESSION_UID$1).findOne({
|
|
2437
|
+
documentId: sessionId,
|
|
2438
|
+
fields: ["documentId"]
|
|
2439
|
+
});
|
|
2440
|
+
if (!existing) {
|
|
2441
|
+
log.warn(`Session ${sessionId} not found for termination`);
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2304
2444
|
await strapi2.documents(SESSION_UID$1).update({
|
|
2305
2445
|
documentId: sessionId,
|
|
2306
2446
|
data: {
|
|
@@ -2392,7 +2532,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2392
2532
|
strapi2.documents(SESSION_UID$1).update({
|
|
2393
2533
|
documentId: session2.documentId,
|
|
2394
2534
|
data: {
|
|
2395
|
-
geoLocation
|
|
2535
|
+
geoLocation,
|
|
2396
2536
|
securityScore: geoData.securityScore || null
|
|
2397
2537
|
}
|
|
2398
2538
|
}).catch(() => {
|
|
@@ -2442,7 +2582,8 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2442
2582
|
const sessions = await strapi2.documents(SESSION_UID$1).findMany({
|
|
2443
2583
|
filters: { isActive: true },
|
|
2444
2584
|
populate: { user: { fields: ["documentId", "email", "username"] } },
|
|
2445
|
-
sort: { loginTime: "desc" }
|
|
2585
|
+
sort: { loginTime: "desc" },
|
|
2586
|
+
limit: 1e3
|
|
2446
2587
|
});
|
|
2447
2588
|
const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
2448
2589
|
const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
|
|
@@ -2479,7 +2620,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2479
2620
|
strapi2.documents(SESSION_UID$1).update({
|
|
2480
2621
|
documentId: session2.documentId,
|
|
2481
2622
|
data: {
|
|
2482
|
-
geoLocation
|
|
2623
|
+
geoLocation,
|
|
2483
2624
|
securityScore: geoData.securityScore || null
|
|
2484
2625
|
}
|
|
2485
2626
|
}).catch(() => {
|
|
@@ -2572,7 +2713,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2572
2713
|
strapi2.documents(SESSION_UID$1).update({
|
|
2573
2714
|
documentId: session2.documentId,
|
|
2574
2715
|
data: {
|
|
2575
|
-
geoLocation
|
|
2716
|
+
geoLocation,
|
|
2576
2717
|
securityScore: geoData.securityScore || null
|
|
2577
2718
|
}
|
|
2578
2719
|
}).catch(() => {
|
|
@@ -2709,20 +2850,36 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2709
2850
|
}
|
|
2710
2851
|
},
|
|
2711
2852
|
/**
|
|
2712
|
-
* Delete all inactive sessions from database
|
|
2853
|
+
* Delete all inactive sessions from database in batches
|
|
2713
2854
|
* WARNING: This permanently deletes records!
|
|
2714
2855
|
* @returns {Promise<number>} Number of deleted sessions
|
|
2715
2856
|
*/
|
|
2716
2857
|
async deleteInactiveSessions() {
|
|
2717
2858
|
try {
|
|
2718
2859
|
log.info("[DELETE] Deleting all inactive sessions...");
|
|
2719
|
-
const inactiveSessions = await strapi2.documents(SESSION_UID$1).findMany({
|
|
2720
|
-
filters: { isActive: false }
|
|
2721
|
-
});
|
|
2722
2860
|
let deletedCount = 0;
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2861
|
+
const BATCH_SIZE = 100;
|
|
2862
|
+
let hasMore = true;
|
|
2863
|
+
while (hasMore) {
|
|
2864
|
+
const batch = await strapi2.documents(SESSION_UID$1).findMany({
|
|
2865
|
+
filters: { isActive: false },
|
|
2866
|
+
fields: ["documentId"],
|
|
2867
|
+
limit: BATCH_SIZE
|
|
2868
|
+
});
|
|
2869
|
+
if (!batch || batch.length === 0) {
|
|
2870
|
+
hasMore = false;
|
|
2871
|
+
break;
|
|
2872
|
+
}
|
|
2873
|
+
const deleteResults = await Promise.allSettled(
|
|
2874
|
+
batch.map(
|
|
2875
|
+
(session2) => strapi2.documents(SESSION_UID$1).delete({ documentId: session2.documentId })
|
|
2876
|
+
)
|
|
2877
|
+
);
|
|
2878
|
+
const batchDeleted = deleteResults.filter((r) => r.status === "fulfilled").length;
|
|
2879
|
+
deletedCount += batchDeleted;
|
|
2880
|
+
if (batch.length < BATCH_SIZE) {
|
|
2881
|
+
hasMore = false;
|
|
2882
|
+
}
|
|
2726
2883
|
}
|
|
2727
2884
|
log.info(`[SUCCESS] Deleted ${deletedCount} inactive sessions`);
|
|
2728
2885
|
return deletedCount;
|
|
@@ -2733,7 +2890,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2733
2890
|
}
|
|
2734
2891
|
};
|
|
2735
2892
|
};
|
|
2736
|
-
const version$1 = "4.4.
|
|
2893
|
+
const version$1 = "4.4.5";
|
|
2737
2894
|
const require$$2 = {
|
|
2738
2895
|
version: version$1
|
|
2739
2896
|
};
|
|
@@ -3007,7 +3164,7 @@ var geolocation$1 = ({ strapi: strapi2 }) => ({
|
|
|
3007
3164
|
*/
|
|
3008
3165
|
async getIpInfo(ipAddress) {
|
|
3009
3166
|
try {
|
|
3010
|
-
if (!ipAddress ||
|
|
3167
|
+
if (!ipAddress || this.isPrivateIp(ipAddress)) {
|
|
3011
3168
|
return {
|
|
3012
3169
|
ip: ipAddress,
|
|
3013
3170
|
country: "Local Network",
|
|
@@ -3110,6 +3267,26 @@ var geolocation$1 = ({ strapi: strapi2 }) => ({
|
|
|
3110
3267
|
const codePoints = countryCode.toUpperCase().split("").map((char) => 127397 + char.charCodeAt());
|
|
3111
3268
|
return String.fromCodePoint(...codePoints);
|
|
3112
3269
|
},
|
|
3270
|
+
/**
|
|
3271
|
+
* Checks if an IP address is private/local (RFC 1918, RFC 4193, loopback, link-local)
|
|
3272
|
+
* @param {string} ip - IP address to check
|
|
3273
|
+
* @returns {boolean} True if IP is private/local
|
|
3274
|
+
*/
|
|
3275
|
+
isPrivateIp(ip) {
|
|
3276
|
+
if (!ip || ip === "unknown") return true;
|
|
3277
|
+
if (ip === "127.0.0.1" || ip === "localhost" || ip === "::1") return true;
|
|
3278
|
+
if (ip.startsWith("192.168.")) return true;
|
|
3279
|
+
if (ip.startsWith("10.")) return true;
|
|
3280
|
+
if (ip.startsWith("172.")) {
|
|
3281
|
+
const second = parseInt(ip.split(".")[1], 10);
|
|
3282
|
+
if (second >= 16 && second <= 31) return true;
|
|
3283
|
+
}
|
|
3284
|
+
if (ip.startsWith("169.254.")) return true;
|
|
3285
|
+
if (ip.startsWith("fc00:") || ip.startsWith("fd00:")) return true;
|
|
3286
|
+
if (ip.startsWith("fe80:")) return true;
|
|
3287
|
+
if (ip === "::1") return true;
|
|
3288
|
+
return false;
|
|
3289
|
+
},
|
|
3113
3290
|
/**
|
|
3114
3291
|
* Fallback data when API fails
|
|
3115
3292
|
*/
|
|
@@ -3245,25 +3422,36 @@ VPN: {{reason.isVpn}}, Proxy: {{reason.isProxy}}`
|
|
|
3245
3422
|
};
|
|
3246
3423
|
},
|
|
3247
3424
|
/**
|
|
3248
|
-
*
|
|
3425
|
+
* Escapes HTML special characters to prevent XSS in email templates
|
|
3426
|
+
* @param {string} str - String to escape
|
|
3427
|
+
* @returns {string} HTML-safe string
|
|
3428
|
+
*/
|
|
3429
|
+
escapeHtml(str2) {
|
|
3430
|
+
if (!str2 || typeof str2 !== "string") return str2 || "";
|
|
3431
|
+
return str2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3432
|
+
},
|
|
3433
|
+
/**
|
|
3434
|
+
* Replace template variables with HTML-escaped actual values
|
|
3435
|
+
* SECURITY: All dynamic values are HTML-escaped to prevent XSS via email
|
|
3249
3436
|
*/
|
|
3250
3437
|
replaceVariables(template2, data) {
|
|
3251
3438
|
let result = template2;
|
|
3252
|
-
|
|
3253
|
-
result = result.replace(/\{\{user\.
|
|
3439
|
+
const esc2 = this.escapeHtml.bind(this);
|
|
3440
|
+
result = result.replace(/\{\{user\.email\}\}/g, esc2(data.user?.email || "N/A"));
|
|
3441
|
+
result = result.replace(/\{\{user\.username\}\}/g, esc2(data.user?.username || "N/A"));
|
|
3254
3442
|
result = result.replace(
|
|
3255
3443
|
/\{\{session\.loginTime\}\}/g,
|
|
3256
|
-
data.session?.loginTime ? new Date(data.session.loginTime).toLocaleString() : "N/A"
|
|
3444
|
+
esc2(data.session?.loginTime ? new Date(data.session.loginTime).toLocaleString() : "N/A")
|
|
3257
3445
|
);
|
|
3258
|
-
result = result.replace(/\{\{session\.ipAddress\}\}/g, data.session?.ipAddress || "N/A");
|
|
3259
|
-
result = result.replace(/\{\{session\.userAgent\}\}/g, data.session?.userAgent || "N/A");
|
|
3260
|
-
result = result.replace(/\{\{geo\.city\}\}/g, data.geoData?.city || "Unknown");
|
|
3261
|
-
result = result.replace(/\{\{geo\.country\}\}/g, data.geoData?.country || "Unknown");
|
|
3262
|
-
result = result.replace(/\{\{geo\.timezone\}\}/g, data.geoData?.timezone || "Unknown");
|
|
3446
|
+
result = result.replace(/\{\{session\.ipAddress\}\}/g, esc2(data.session?.ipAddress || "N/A"));
|
|
3447
|
+
result = result.replace(/\{\{session\.userAgent\}\}/g, esc2(data.session?.userAgent || "N/A"));
|
|
3448
|
+
result = result.replace(/\{\{geo\.city\}\}/g, esc2(data.geoData?.city || "Unknown"));
|
|
3449
|
+
result = result.replace(/\{\{geo\.country\}\}/g, esc2(data.geoData?.country || "Unknown"));
|
|
3450
|
+
result = result.replace(/\{\{geo\.timezone\}\}/g, esc2(data.geoData?.timezone || "Unknown"));
|
|
3263
3451
|
result = result.replace(/\{\{reason\.isVpn\}\}/g, data.reason?.isVpn ? "Yes" : "No");
|
|
3264
3452
|
result = result.replace(/\{\{reason\.isProxy\}\}/g, data.reason?.isProxy ? "Yes" : "No");
|
|
3265
3453
|
result = result.replace(/\{\{reason\.isThreat\}\}/g, data.reason?.isThreat ? "Yes" : "No");
|
|
3266
|
-
result = result.replace(/\{\{reason\.securityScore\}\}/g, data.reason?.securityScore || "0");
|
|
3454
|
+
result = result.replace(/\{\{reason\.securityScore\}\}/g, esc2(String(data.reason?.securityScore || "0")));
|
|
3267
3455
|
return result;
|
|
3268
3456
|
},
|
|
3269
3457
|
/**
|
|
@@ -39039,7 +39227,7 @@ const dist = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty
|
|
|
39039
39227
|
const require$$0 = /* @__PURE__ */ getAugmentedNamespace(dist);
|
|
39040
39228
|
const SESSION_UID = "plugin::magic-sessionmanager.session";
|
|
39041
39229
|
const { errors } = require$$0;
|
|
39042
|
-
var sessionRequired$1 = async (policyContext,
|
|
39230
|
+
var sessionRequired$1 = async (policyContext, _policyConfig, { strapi: strapi2 }) => {
|
|
39043
39231
|
if (!policyContext.state.user) {
|
|
39044
39232
|
return true;
|
|
39045
39233
|
}
|
|
@@ -39057,8 +39245,8 @@ var sessionRequired$1 = async (policyContext, config2, { strapi: strapi2 }) => {
|
|
|
39057
39245
|
if (!userDocId) {
|
|
39058
39246
|
return true;
|
|
39059
39247
|
}
|
|
39060
|
-
const
|
|
39061
|
-
const strictMode =
|
|
39248
|
+
const pluginConfig = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
39249
|
+
const strictMode = pluginConfig.strictSessionEnforcement === true;
|
|
39062
39250
|
const activeSessions = await strapi2.documents(SESSION_UID).findMany({
|
|
39063
39251
|
filters: {
|
|
39064
39252
|
user: { documentId: userDocId },
|