strapi-plugin-magic-sessionmanager 4.2.11 → 4.2.13

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.
@@ -219,6 +219,18 @@ var encryption = {
219
219
  const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
220
220
  const { hashToken: hashToken$3 } = encryption;
221
221
  const lastTouchCache = /* @__PURE__ */ new Map();
222
+ const CACHE_MAX_SIZE = 1e4;
223
+ const CACHE_CLEANUP_AGE = 60 * 60 * 1e3;
224
+ function cleanupOldCacheEntries() {
225
+ if (lastTouchCache.size < CACHE_MAX_SIZE) return;
226
+ const now = Date.now();
227
+ const cutoff = now - CACHE_CLEANUP_AGE;
228
+ for (const [key, timestamp] of lastTouchCache.entries()) {
229
+ if (timestamp < cutoff) {
230
+ lastTouchCache.delete(key);
231
+ }
232
+ }
233
+ }
222
234
  var lastSeen = ({ strapi: strapi2 }) => {
223
235
  return async (ctx, next) => {
224
236
  const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
@@ -226,7 +238,30 @@ var lastSeen = ({ strapi: strapi2 }) => {
226
238
  await next();
227
239
  return;
228
240
  }
229
- const skipPaths = ["/admin", "/_health", "/favicon.ico"];
241
+ const skipPaths = [
242
+ "/admin",
243
+ // Admin panel routes (have their own auth)
244
+ "/_health",
245
+ // Health check
246
+ "/favicon.ico",
247
+ // Static assets
248
+ "/api/auth/local",
249
+ // Login endpoint
250
+ "/api/auth/register",
251
+ // Registration endpoint
252
+ "/api/auth/forgot-password",
253
+ // Password reset
254
+ "/api/auth/reset-password",
255
+ // Password reset
256
+ "/api/auth/logout",
257
+ // Logout endpoint (handled separately)
258
+ "/api/auth/refresh",
259
+ // Refresh token (has own validation in bootstrap.js)
260
+ "/api/connect",
261
+ // OAuth providers
262
+ "/api/magic-link"
263
+ // Magic link auth (if using magic-link plugin)
264
+ ];
230
265
  if (skipPaths.some((p) => ctx.path.startsWith(p))) {
231
266
  await next();
232
267
  return;
@@ -248,10 +283,8 @@ var lastSeen = ({ strapi: strapi2 }) => {
248
283
  ctx.state.sessionUserId = matchingSession.user.documentId;
249
284
  }
250
285
  } else {
251
- if (ctx.state.user && ctx.state.user.documentId) {
252
- strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Session terminated for user ${ctx.state.user.documentId}`);
253
- return ctx.unauthorized("This session has been terminated. Please login again.");
254
- }
286
+ strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Request blocked - session terminated or invalid (token hash: ${currentTokenHash.substring(0, 8)}...)`);
287
+ return ctx.unauthorized("This session has been terminated. Please login again.");
255
288
  }
256
289
  } catch (err) {
257
290
  strapi2.log.debug("[magic-sessionmanager] Error checking session:", err.message);
@@ -265,6 +298,7 @@ var lastSeen = ({ strapi: strapi2 }) => {
265
298
  const lastTouch = lastTouchCache.get(matchingSession.documentId) || 0;
266
299
  if (now - lastTouch > rateLimit) {
267
300
  lastTouchCache.set(matchingSession.documentId, now);
301
+ cleanupOldCacheEntries();
268
302
  await strapi2.documents(SESSION_UID$3).update({
269
303
  documentId: matchingSession.documentId,
270
304
  data: { lastActive: /* @__PURE__ */ new Date() }
@@ -351,20 +385,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
351
385
  ctx.body = { message: "Logged out successfully" };
352
386
  return;
353
387
  }
354
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
388
+ const tokenHashValue = hashToken$2(token);
389
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
355
390
  filters: {
391
+ tokenHash: tokenHashValue,
356
392
  isActive: true
357
393
  }
358
394
  });
359
- const matchingSession = allSessions.find((session2) => {
360
- if (!session2.token) return false;
361
- try {
362
- const decrypted = decryptToken$2(session2.token);
363
- return decrypted === token;
364
- } catch (err) {
365
- return false;
366
- }
367
- });
368
395
  if (matchingSession) {
369
396
  await sessionService.terminateSession({ sessionId: matchingSession.documentId });
370
397
  log.info(`[LOGOUT] Logout via /api/auth/logout - Session ${matchingSession.documentId} terminated`);
@@ -455,8 +482,10 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
455
482
  userAgent,
456
483
  token: ctx.body.jwt,
457
484
  // Store Access Token (encrypted)
458
- refreshToken: ctx.body.refreshToken
485
+ refreshToken: ctx.body.refreshToken,
459
486
  // Store Refresh Token (encrypted) if exists
487
+ geoData
488
+ // Store geolocation data if available
460
489
  });
461
490
  log.info(`[SUCCESS] Session created for user ${userDocId} (IP: ${ip})`);
462
491
  if (geoData && (config2.enableEmailAlerts || config2.enableWebhooks)) {
@@ -507,20 +536,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
507
536
  try {
508
537
  const refreshToken = ctx.request.body?.refreshToken;
509
538
  if (refreshToken) {
510
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
539
+ const refreshTokenHashValue = hashToken$2(refreshToken);
540
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
511
541
  filters: {
542
+ refreshTokenHash: refreshTokenHashValue,
512
543
  isActive: true
513
544
  }
514
545
  });
515
- const matchingSession = allSessions.find((session2) => {
516
- if (!session2.refreshToken) return false;
517
- try {
518
- const decrypted = decryptToken$2(session2.refreshToken);
519
- return decrypted === refreshToken;
520
- } catch (err) {
521
- return false;
522
- }
523
- });
524
546
  if (!matchingSession) {
525
547
  log.warn("[BLOCKED] Blocked refresh token request - no active session");
526
548
  ctx.status = 401;
@@ -546,20 +568,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
546
568
  const newAccessToken = ctx.body.jwt;
547
569
  const newRefreshToken = ctx.body.refreshToken;
548
570
  if (oldRefreshToken) {
549
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
571
+ const oldRefreshTokenHash = hashToken$2(oldRefreshToken);
572
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
550
573
  filters: {
574
+ refreshTokenHash: oldRefreshTokenHash,
551
575
  isActive: true
552
576
  }
553
577
  });
554
- const matchingSession = allSessions.find((session2) => {
555
- if (!session2.refreshToken) return false;
556
- try {
557
- const decrypted = decryptToken$2(session2.refreshToken);
558
- return decrypted === oldRefreshToken;
559
- } catch (err) {
560
- return false;
561
- }
562
- });
563
578
  if (matchingSession) {
564
579
  const encryptedToken = newAccessToken ? encryptToken$1(newAccessToken) : matchingSession.token;
565
580
  const encryptedRefreshToken = newRefreshToken ? encryptToken$1(newRefreshToken) : matchingSession.refreshToken;
@@ -688,11 +703,11 @@ var destroy$1 = async ({ strapi: strapi2 }) => {
688
703
  const log = createLogger$2(strapi2);
689
704
  if (strapi2.licenseGuard && strapi2.licenseGuard.pingInterval) {
690
705
  clearInterval(strapi2.licenseGuard.pingInterval);
691
- log.info("🛑 License pinging stopped");
706
+ log.info("[STOP] License pinging stopped");
692
707
  }
693
708
  if (strapi2.sessionManagerIntervals && strapi2.sessionManagerIntervals.cleanup) {
694
709
  clearInterval(strapi2.sessionManagerIntervals.cleanup);
695
- log.info("🛑 Session cleanup interval stopped");
710
+ log.info("[STOP] Session cleanup interval stopped");
696
711
  }
697
712
  log.info("[SUCCESS] Plugin cleanup completed");
698
713
  };
@@ -1173,6 +1188,7 @@ var session$3 = {
1173
1188
  * GET /api/magic-sessionmanager/my-sessions
1174
1189
  * Automatically uses the authenticated user's documentId
1175
1190
  * Marks which session is the current one (based on JWT token hash)
1191
+ * Fetches geolocation data on-demand if not already stored
1176
1192
  */
1177
1193
  async getOwnSessions(ctx) {
1178
1194
  try {
@@ -1189,7 +1205,8 @@ var session$3 = {
1189
1205
  const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
1190
1206
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
1191
1207
  const now = /* @__PURE__ */ new Date();
1192
- const sessionsWithCurrent = allSessions.map((session2) => {
1208
+ const geolocationService = strapi.plugin("magic-sessionmanager").service("geolocation");
1209
+ const sessionsWithCurrent = await Promise.all(allSessions.map(async (session2) => {
1193
1210
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1194
1211
  const timeSinceActive = now - lastActiveTime;
1195
1212
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -1198,6 +1215,39 @@ var session$3 = {
1198
1215
  const deviceType = session2.deviceType || parsedUA.deviceType;
1199
1216
  const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
1200
1217
  const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
1218
+ let geoLocation = session2.geoLocation;
1219
+ if (typeof geoLocation === "string") {
1220
+ try {
1221
+ geoLocation = JSON.parse(geoLocation);
1222
+ } catch (e) {
1223
+ geoLocation = null;
1224
+ }
1225
+ }
1226
+ if (!geoLocation && session2.ipAddress) {
1227
+ try {
1228
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
1229
+ if (geoData && geoData.country !== "Unknown") {
1230
+ geoLocation = {
1231
+ country: geoData.country,
1232
+ country_code: geoData.country_code,
1233
+ country_flag: geoData.country_flag,
1234
+ city: geoData.city,
1235
+ region: geoData.region,
1236
+ timezone: geoData.timezone
1237
+ };
1238
+ strapi.documents(SESSION_UID$1).update({
1239
+ documentId: session2.documentId,
1240
+ data: {
1241
+ geoLocation: JSON.stringify(geoLocation),
1242
+ securityScore: geoData.securityScore || null
1243
+ }
1244
+ }).catch(() => {
1245
+ });
1246
+ }
1247
+ } catch (geoErr) {
1248
+ strapi.log.debug("[magic-sessionmanager] Geolocation lookup failed:", geoErr.message);
1249
+ }
1250
+ }
1201
1251
  const {
1202
1252
  token,
1203
1253
  tokenHash,
@@ -1206,6 +1256,8 @@ var session$3 = {
1206
1256
  locale,
1207
1257
  publishedAt,
1208
1258
  // Remove Strapi internal fields
1259
+ geoLocation: _geo,
1260
+ // Remove raw geoLocation
1209
1261
  ...sessionWithoutTokens
1210
1262
  } = session2;
1211
1263
  return {
@@ -1213,11 +1265,13 @@ var session$3 = {
1213
1265
  deviceType,
1214
1266
  browserName,
1215
1267
  osName,
1268
+ geoLocation,
1269
+ // Parsed object or null
1216
1270
  isCurrentSession,
1217
1271
  isTrulyActive,
1218
1272
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1219
1273
  };
1220
- });
1274
+ }));
1221
1275
  sessionsWithCurrent.sort((a, b) => {
1222
1276
  if (a.isCurrentSession) return -1;
1223
1277
  if (b.isCurrentSession) return 1;
@@ -1319,6 +1373,7 @@ var session$3 = {
1319
1373
  * Get current session info based on JWT token hash
1320
1374
  * GET /api/magic-sessionmanager/current-session
1321
1375
  * Returns the session associated with the current JWT token
1376
+ * Fetches geolocation on-demand if not already stored
1322
1377
  */
1323
1378
  async getCurrentSession(ctx) {
1324
1379
  try {
@@ -1347,6 +1402,40 @@ var session$3 = {
1347
1402
  const deviceType = currentSession.deviceType || parsedUA.deviceType;
1348
1403
  const browserName = currentSession.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
1349
1404
  const osName = currentSession.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
1405
+ let geoLocation = currentSession.geoLocation;
1406
+ if (typeof geoLocation === "string") {
1407
+ try {
1408
+ geoLocation = JSON.parse(geoLocation);
1409
+ } catch (e) {
1410
+ geoLocation = null;
1411
+ }
1412
+ }
1413
+ if (!geoLocation && currentSession.ipAddress) {
1414
+ try {
1415
+ const geolocationService = strapi.plugin("magic-sessionmanager").service("geolocation");
1416
+ const geoData = await geolocationService.getIpInfo(currentSession.ipAddress);
1417
+ if (geoData && geoData.country !== "Unknown") {
1418
+ geoLocation = {
1419
+ country: geoData.country,
1420
+ country_code: geoData.country_code,
1421
+ country_flag: geoData.country_flag,
1422
+ city: geoData.city,
1423
+ region: geoData.region,
1424
+ timezone: geoData.timezone
1425
+ };
1426
+ strapi.documents(SESSION_UID$1).update({
1427
+ documentId: currentSession.documentId,
1428
+ data: {
1429
+ geoLocation: JSON.stringify(geoLocation),
1430
+ securityScore: geoData.securityScore || null
1431
+ }
1432
+ }).catch(() => {
1433
+ });
1434
+ }
1435
+ } catch (geoErr) {
1436
+ strapi.log.debug("[magic-sessionmanager] Geolocation lookup failed:", geoErr.message);
1437
+ }
1438
+ }
1350
1439
  const {
1351
1440
  token: _,
1352
1441
  tokenHash: _th,
@@ -1354,6 +1443,7 @@ var session$3 = {
1354
1443
  refreshTokenHash: _rth,
1355
1444
  locale: _l,
1356
1445
  publishedAt: _p,
1446
+ geoLocation: _geo,
1357
1447
  ...sessionWithoutTokens
1358
1448
  } = currentSession;
1359
1449
  ctx.body = {
@@ -1362,6 +1452,8 @@ var session$3 = {
1362
1452
  deviceType,
1363
1453
  browserName,
1364
1454
  osName,
1455
+ geoLocation,
1456
+ // Parsed object or null
1365
1457
  isCurrentSession: true,
1366
1458
  isTrulyActive: timeSinceActive < inactivityTimeout,
1367
1459
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
@@ -1905,10 +1997,10 @@ var session$1 = ({ strapi: strapi2 }) => {
1905
1997
  return {
1906
1998
  /**
1907
1999
  * Create a new session record
1908
- * @param {Object} params - { userId, ip, userAgent, token, refreshToken }
2000
+ * @param {Object} params - { userId, ip, userAgent, token, refreshToken, geoData }
1909
2001
  * @returns {Promise<Object>} Created session
1910
2002
  */
1911
- async createSession({ userId, ip = "unknown", userAgent = "unknown", token, refreshToken }) {
2003
+ async createSession({ userId, ip = "unknown", userAgent = "unknown", token, refreshToken, geoData }) {
1912
2004
  try {
1913
2005
  const now = /* @__PURE__ */ new Date();
1914
2006
  const sessionId = generateSessionId(userId);
@@ -1916,6 +2008,7 @@ var session$1 = ({ strapi: strapi2 }) => {
1916
2008
  const encryptedRefreshToken = refreshToken ? encryptToken(refreshToken) : null;
1917
2009
  const tokenHashValue = token ? hashToken(token) : null;
1918
2010
  const refreshTokenHashValue = refreshToken ? hashToken(refreshToken) : null;
2011
+ const parsedUA = parseUserAgent(userAgent);
1919
2012
  const session2 = await strapi2.documents(SESSION_UID).create({
1920
2013
  data: {
1921
2014
  user: userId,
@@ -1933,8 +2026,22 @@ var session$1 = ({ strapi: strapi2 }) => {
1933
2026
  // Encrypted Refresh Token
1934
2027
  refreshTokenHash: refreshTokenHashValue,
1935
2028
  // SHA-256 hash for fast lookup
1936
- sessionId
2029
+ sessionId,
1937
2030
  // Unique identifier
2031
+ // Device info from User-Agent
2032
+ deviceType: parsedUA.deviceType,
2033
+ browserName: parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName,
2034
+ osName: parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName,
2035
+ // Geolocation data (if available from Premium features)
2036
+ geoLocation: geoData ? JSON.stringify({
2037
+ country: geoData.country,
2038
+ country_code: geoData.country_code,
2039
+ country_flag: geoData.country_flag,
2040
+ city: geoData.city,
2041
+ region: geoData.region,
2042
+ timezone: geoData.timezone
2043
+ }) : null,
2044
+ securityScore: geoData?.securityScore || null
1938
2045
  }
1939
2046
  });
1940
2047
  log.info(`[SUCCESS] Session ${session2.documentId} (${sessionId}) created for user ${userId}`);
@@ -1995,6 +2102,7 @@ var session$1 = ({ strapi: strapi2 }) => {
1995
2102
  },
1996
2103
  /**
1997
2104
  * Get ALL sessions (active + inactive) with accurate online status
2105
+ * Fetches geolocation on-demand for sessions without it (limited to prevent API abuse)
1998
2106
  * @returns {Promise<Array>} All sessions with enhanced data
1999
2107
  */
2000
2108
  async getAllSessions() {
@@ -2007,8 +2115,10 @@ var session$1 = ({ strapi: strapi2 }) => {
2007
2115
  });
2008
2116
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2009
2117
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2118
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2119
+ let geoLookupsRemaining = 20;
2010
2120
  const now = /* @__PURE__ */ new Date();
2011
- const enhancedSessions = sessions.map((session2) => {
2121
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2012
2122
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2013
2123
  const timeSinceActive = now - lastActiveTime;
2014
2124
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2016,6 +2126,40 @@ var session$1 = ({ strapi: strapi2 }) => {
2016
2126
  const deviceType = session2.deviceType || parsedUA.deviceType;
2017
2127
  const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2018
2128
  const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2129
+ let geoLocation = session2.geoLocation;
2130
+ if (typeof geoLocation === "string") {
2131
+ try {
2132
+ geoLocation = JSON.parse(geoLocation);
2133
+ } catch (e) {
2134
+ geoLocation = null;
2135
+ }
2136
+ }
2137
+ if (!geoLocation && session2.ipAddress && geoLookupsRemaining > 0) {
2138
+ geoLookupsRemaining--;
2139
+ try {
2140
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2141
+ if (geoData && geoData.country !== "Unknown") {
2142
+ geoLocation = {
2143
+ country: geoData.country,
2144
+ country_code: geoData.country_code,
2145
+ country_flag: geoData.country_flag,
2146
+ city: geoData.city,
2147
+ region: geoData.region,
2148
+ timezone: geoData.timezone
2149
+ };
2150
+ strapi2.documents(SESSION_UID).update({
2151
+ documentId: session2.documentId,
2152
+ data: {
2153
+ geoLocation: JSON.stringify(geoLocation),
2154
+ securityScore: geoData.securityScore || null
2155
+ }
2156
+ }).catch(() => {
2157
+ });
2158
+ }
2159
+ } catch (geoErr) {
2160
+ log.debug("Geolocation lookup failed:", geoErr.message);
2161
+ }
2162
+ }
2019
2163
  const {
2020
2164
  token,
2021
2165
  tokenHash,
@@ -2023,6 +2167,8 @@ var session$1 = ({ strapi: strapi2 }) => {
2023
2167
  refreshTokenHash,
2024
2168
  locale,
2025
2169
  publishedAt,
2170
+ geoLocation: _geo,
2171
+ // Remove raw geoLocation, we use parsed version
2026
2172
  ...safeSession
2027
2173
  } = session2;
2028
2174
  return {
@@ -2030,10 +2176,12 @@ var session$1 = ({ strapi: strapi2 }) => {
2030
2176
  deviceType,
2031
2177
  browserName,
2032
2178
  osName,
2179
+ geoLocation,
2180
+ // Parsed object or null
2033
2181
  isTrulyActive,
2034
2182
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2035
2183
  };
2036
- });
2184
+ }));
2037
2185
  return enhancedSessions;
2038
2186
  } catch (err) {
2039
2187
  log.error("Error getting all sessions:", err);
@@ -2042,6 +2190,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2042
2190
  },
2043
2191
  /**
2044
2192
  * Get all active sessions with accurate online status
2193
+ * Fetches geolocation on-demand for sessions without it
2045
2194
  * @returns {Promise<Array>} Active sessions with user data and online status
2046
2195
  */
2047
2196
  async getActiveSessions() {
@@ -2053,8 +2202,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2053
2202
  });
2054
2203
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2055
2204
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2205
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2056
2206
  const now = /* @__PURE__ */ new Date();
2057
- const enhancedSessions = sessions.map((session2) => {
2207
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2058
2208
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2059
2209
  const timeSinceActive = now - lastActiveTime;
2060
2210
  const isTrulyActive = timeSinceActive < inactivityTimeout;
@@ -2062,6 +2212,39 @@ var session$1 = ({ strapi: strapi2 }) => {
2062
2212
  const deviceType = session2.deviceType || parsedUA.deviceType;
2063
2213
  const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2064
2214
  const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2215
+ let geoLocation = session2.geoLocation;
2216
+ if (typeof geoLocation === "string") {
2217
+ try {
2218
+ geoLocation = JSON.parse(geoLocation);
2219
+ } catch (e) {
2220
+ geoLocation = null;
2221
+ }
2222
+ }
2223
+ if (!geoLocation && session2.ipAddress) {
2224
+ try {
2225
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2226
+ if (geoData && geoData.country !== "Unknown") {
2227
+ geoLocation = {
2228
+ country: geoData.country,
2229
+ country_code: geoData.country_code,
2230
+ country_flag: geoData.country_flag,
2231
+ city: geoData.city,
2232
+ region: geoData.region,
2233
+ timezone: geoData.timezone
2234
+ };
2235
+ strapi2.documents(SESSION_UID).update({
2236
+ documentId: session2.documentId,
2237
+ data: {
2238
+ geoLocation: JSON.stringify(geoLocation),
2239
+ securityScore: geoData.securityScore || null
2240
+ }
2241
+ }).catch(() => {
2242
+ });
2243
+ }
2244
+ } catch (geoErr) {
2245
+ log.debug("Geolocation lookup failed:", geoErr.message);
2246
+ }
2247
+ }
2065
2248
  const {
2066
2249
  token,
2067
2250
  tokenHash,
@@ -2069,6 +2252,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2069
2252
  refreshTokenHash,
2070
2253
  locale,
2071
2254
  publishedAt,
2255
+ geoLocation: _geo,
2072
2256
  ...safeSession
2073
2257
  } = session2;
2074
2258
  return {
@@ -2076,10 +2260,11 @@ var session$1 = ({ strapi: strapi2 }) => {
2076
2260
  deviceType,
2077
2261
  browserName,
2078
2262
  osName,
2263
+ geoLocation,
2079
2264
  isTrulyActive,
2080
2265
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2081
2266
  };
2082
- });
2267
+ }));
2083
2268
  return enhancedSessions.filter((s) => s.isTrulyActive);
2084
2269
  } catch (err) {
2085
2270
  log.error("Error getting active sessions:", err);
@@ -2089,6 +2274,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2089
2274
  /**
2090
2275
  * Get all sessions for a specific user
2091
2276
  * Supports both numeric id (legacy) and documentId (Strapi v5)
2277
+ * Fetches geolocation on-demand for sessions without it
2092
2278
  * @param {string|number} userId - User documentId or numeric id
2093
2279
  * @returns {Promise<Array>} User's sessions with accurate online status
2094
2280
  */
@@ -2107,8 +2293,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2107
2293
  });
2108
2294
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2109
2295
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2296
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2110
2297
  const now = /* @__PURE__ */ new Date();
2111
- const enhancedSessions = sessions.map((session2) => {
2298
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2112
2299
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2113
2300
  const timeSinceActive = now - lastActiveTime;
2114
2301
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2116,6 +2303,39 @@ var session$1 = ({ strapi: strapi2 }) => {
2116
2303
  const deviceType = session2.deviceType || parsedUA.deviceType;
2117
2304
  const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2118
2305
  const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2306
+ let geoLocation = session2.geoLocation;
2307
+ if (typeof geoLocation === "string") {
2308
+ try {
2309
+ geoLocation = JSON.parse(geoLocation);
2310
+ } catch (e) {
2311
+ geoLocation = null;
2312
+ }
2313
+ }
2314
+ if (!geoLocation && session2.ipAddress) {
2315
+ try {
2316
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2317
+ if (geoData && geoData.country !== "Unknown") {
2318
+ geoLocation = {
2319
+ country: geoData.country,
2320
+ country_code: geoData.country_code,
2321
+ country_flag: geoData.country_flag,
2322
+ city: geoData.city,
2323
+ region: geoData.region,
2324
+ timezone: geoData.timezone
2325
+ };
2326
+ strapi2.documents(SESSION_UID).update({
2327
+ documentId: session2.documentId,
2328
+ data: {
2329
+ geoLocation: JSON.stringify(geoLocation),
2330
+ securityScore: geoData.securityScore || null
2331
+ }
2332
+ }).catch(() => {
2333
+ });
2334
+ }
2335
+ } catch (geoErr) {
2336
+ log.debug("Geolocation lookup failed:", geoErr.message);
2337
+ }
2338
+ }
2119
2339
  const {
2120
2340
  token,
2121
2341
  tokenHash,
@@ -2123,6 +2343,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2123
2343
  refreshTokenHash,
2124
2344
  locale,
2125
2345
  publishedAt,
2346
+ geoLocation: _geo,
2126
2347
  ...safeSession
2127
2348
  } = session2;
2128
2349
  return {
@@ -2130,10 +2351,11 @@ var session$1 = ({ strapi: strapi2 }) => {
2130
2351
  deviceType,
2131
2352
  browserName,
2132
2353
  osName,
2354
+ geoLocation,
2133
2355
  isTrulyActive,
2134
2356
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2135
2357
  };
2136
- });
2358
+ }));
2137
2359
  return enhancedSessions;
2138
2360
  } catch (err) {
2139
2361
  log.error("Error getting user sessions:", err);
@@ -2246,7 +2468,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2246
2468
  }
2247
2469
  };
2248
2470
  };
2249
- const version = "4.2.10";
2471
+ const version = "4.2.13";
2250
2472
  const require$$2 = {
2251
2473
  version
2252
2474
  };
@@ -215,6 +215,18 @@ var encryption = {
215
215
  const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
216
216
  const { hashToken: hashToken$3 } = encryption;
217
217
  const lastTouchCache = /* @__PURE__ */ new Map();
218
+ const CACHE_MAX_SIZE = 1e4;
219
+ const CACHE_CLEANUP_AGE = 60 * 60 * 1e3;
220
+ function cleanupOldCacheEntries() {
221
+ if (lastTouchCache.size < CACHE_MAX_SIZE) return;
222
+ const now = Date.now();
223
+ const cutoff = now - CACHE_CLEANUP_AGE;
224
+ for (const [key, timestamp] of lastTouchCache.entries()) {
225
+ if (timestamp < cutoff) {
226
+ lastTouchCache.delete(key);
227
+ }
228
+ }
229
+ }
218
230
  var lastSeen = ({ strapi: strapi2 }) => {
219
231
  return async (ctx, next) => {
220
232
  const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
@@ -222,7 +234,30 @@ var lastSeen = ({ strapi: strapi2 }) => {
222
234
  await next();
223
235
  return;
224
236
  }
225
- const skipPaths = ["/admin", "/_health", "/favicon.ico"];
237
+ const skipPaths = [
238
+ "/admin",
239
+ // Admin panel routes (have their own auth)
240
+ "/_health",
241
+ // Health check
242
+ "/favicon.ico",
243
+ // Static assets
244
+ "/api/auth/local",
245
+ // Login endpoint
246
+ "/api/auth/register",
247
+ // Registration endpoint
248
+ "/api/auth/forgot-password",
249
+ // Password reset
250
+ "/api/auth/reset-password",
251
+ // Password reset
252
+ "/api/auth/logout",
253
+ // Logout endpoint (handled separately)
254
+ "/api/auth/refresh",
255
+ // Refresh token (has own validation in bootstrap.js)
256
+ "/api/connect",
257
+ // OAuth providers
258
+ "/api/magic-link"
259
+ // Magic link auth (if using magic-link plugin)
260
+ ];
226
261
  if (skipPaths.some((p) => ctx.path.startsWith(p))) {
227
262
  await next();
228
263
  return;
@@ -244,10 +279,8 @@ var lastSeen = ({ strapi: strapi2 }) => {
244
279
  ctx.state.sessionUserId = matchingSession.user.documentId;
245
280
  }
246
281
  } else {
247
- if (ctx.state.user && ctx.state.user.documentId) {
248
- strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Session terminated for user ${ctx.state.user.documentId}`);
249
- return ctx.unauthorized("This session has been terminated. Please login again.");
250
- }
282
+ strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Request blocked - session terminated or invalid (token hash: ${currentTokenHash.substring(0, 8)}...)`);
283
+ return ctx.unauthorized("This session has been terminated. Please login again.");
251
284
  }
252
285
  } catch (err) {
253
286
  strapi2.log.debug("[magic-sessionmanager] Error checking session:", err.message);
@@ -261,6 +294,7 @@ var lastSeen = ({ strapi: strapi2 }) => {
261
294
  const lastTouch = lastTouchCache.get(matchingSession.documentId) || 0;
262
295
  if (now - lastTouch > rateLimit) {
263
296
  lastTouchCache.set(matchingSession.documentId, now);
297
+ cleanupOldCacheEntries();
264
298
  await strapi2.documents(SESSION_UID$3).update({
265
299
  documentId: matchingSession.documentId,
266
300
  data: { lastActive: /* @__PURE__ */ new Date() }
@@ -347,20 +381,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
347
381
  ctx.body = { message: "Logged out successfully" };
348
382
  return;
349
383
  }
350
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
384
+ const tokenHashValue = hashToken$2(token);
385
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
351
386
  filters: {
387
+ tokenHash: tokenHashValue,
352
388
  isActive: true
353
389
  }
354
390
  });
355
- const matchingSession = allSessions.find((session2) => {
356
- if (!session2.token) return false;
357
- try {
358
- const decrypted = decryptToken$2(session2.token);
359
- return decrypted === token;
360
- } catch (err) {
361
- return false;
362
- }
363
- });
364
391
  if (matchingSession) {
365
392
  await sessionService.terminateSession({ sessionId: matchingSession.documentId });
366
393
  log.info(`[LOGOUT] Logout via /api/auth/logout - Session ${matchingSession.documentId} terminated`);
@@ -451,8 +478,10 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
451
478
  userAgent,
452
479
  token: ctx.body.jwt,
453
480
  // Store Access Token (encrypted)
454
- refreshToken: ctx.body.refreshToken
481
+ refreshToken: ctx.body.refreshToken,
455
482
  // Store Refresh Token (encrypted) if exists
483
+ geoData
484
+ // Store geolocation data if available
456
485
  });
457
486
  log.info(`[SUCCESS] Session created for user ${userDocId} (IP: ${ip})`);
458
487
  if (geoData && (config2.enableEmailAlerts || config2.enableWebhooks)) {
@@ -503,20 +532,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
503
532
  try {
504
533
  const refreshToken = ctx.request.body?.refreshToken;
505
534
  if (refreshToken) {
506
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
535
+ const refreshTokenHashValue = hashToken$2(refreshToken);
536
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
507
537
  filters: {
538
+ refreshTokenHash: refreshTokenHashValue,
508
539
  isActive: true
509
540
  }
510
541
  });
511
- const matchingSession = allSessions.find((session2) => {
512
- if (!session2.refreshToken) return false;
513
- try {
514
- const decrypted = decryptToken$2(session2.refreshToken);
515
- return decrypted === refreshToken;
516
- } catch (err) {
517
- return false;
518
- }
519
- });
520
542
  if (!matchingSession) {
521
543
  log.warn("[BLOCKED] Blocked refresh token request - no active session");
522
544
  ctx.status = 401;
@@ -542,20 +564,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
542
564
  const newAccessToken = ctx.body.jwt;
543
565
  const newRefreshToken = ctx.body.refreshToken;
544
566
  if (oldRefreshToken) {
545
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
567
+ const oldRefreshTokenHash = hashToken$2(oldRefreshToken);
568
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
546
569
  filters: {
570
+ refreshTokenHash: oldRefreshTokenHash,
547
571
  isActive: true
548
572
  }
549
573
  });
550
- const matchingSession = allSessions.find((session2) => {
551
- if (!session2.refreshToken) return false;
552
- try {
553
- const decrypted = decryptToken$2(session2.refreshToken);
554
- return decrypted === oldRefreshToken;
555
- } catch (err) {
556
- return false;
557
- }
558
- });
559
574
  if (matchingSession) {
560
575
  const encryptedToken = newAccessToken ? encryptToken$1(newAccessToken) : matchingSession.token;
561
576
  const encryptedRefreshToken = newRefreshToken ? encryptToken$1(newRefreshToken) : matchingSession.refreshToken;
@@ -684,11 +699,11 @@ var destroy$1 = async ({ strapi: strapi2 }) => {
684
699
  const log = createLogger$2(strapi2);
685
700
  if (strapi2.licenseGuard && strapi2.licenseGuard.pingInterval) {
686
701
  clearInterval(strapi2.licenseGuard.pingInterval);
687
- log.info("🛑 License pinging stopped");
702
+ log.info("[STOP] License pinging stopped");
688
703
  }
689
704
  if (strapi2.sessionManagerIntervals && strapi2.sessionManagerIntervals.cleanup) {
690
705
  clearInterval(strapi2.sessionManagerIntervals.cleanup);
691
- log.info("🛑 Session cleanup interval stopped");
706
+ log.info("[STOP] Session cleanup interval stopped");
692
707
  }
693
708
  log.info("[SUCCESS] Plugin cleanup completed");
694
709
  };
@@ -1169,6 +1184,7 @@ var session$3 = {
1169
1184
  * GET /api/magic-sessionmanager/my-sessions
1170
1185
  * Automatically uses the authenticated user's documentId
1171
1186
  * Marks which session is the current one (based on JWT token hash)
1187
+ * Fetches geolocation data on-demand if not already stored
1172
1188
  */
1173
1189
  async getOwnSessions(ctx) {
1174
1190
  try {
@@ -1185,7 +1201,8 @@ var session$3 = {
1185
1201
  const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
1186
1202
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
1187
1203
  const now = /* @__PURE__ */ new Date();
1188
- const sessionsWithCurrent = allSessions.map((session2) => {
1204
+ const geolocationService = strapi.plugin("magic-sessionmanager").service("geolocation");
1205
+ const sessionsWithCurrent = await Promise.all(allSessions.map(async (session2) => {
1189
1206
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1190
1207
  const timeSinceActive = now - lastActiveTime;
1191
1208
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -1194,6 +1211,39 @@ var session$3 = {
1194
1211
  const deviceType = session2.deviceType || parsedUA.deviceType;
1195
1212
  const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
1196
1213
  const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
1214
+ let geoLocation = session2.geoLocation;
1215
+ if (typeof geoLocation === "string") {
1216
+ try {
1217
+ geoLocation = JSON.parse(geoLocation);
1218
+ } catch (e) {
1219
+ geoLocation = null;
1220
+ }
1221
+ }
1222
+ if (!geoLocation && session2.ipAddress) {
1223
+ try {
1224
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
1225
+ if (geoData && geoData.country !== "Unknown") {
1226
+ geoLocation = {
1227
+ country: geoData.country,
1228
+ country_code: geoData.country_code,
1229
+ country_flag: geoData.country_flag,
1230
+ city: geoData.city,
1231
+ region: geoData.region,
1232
+ timezone: geoData.timezone
1233
+ };
1234
+ strapi.documents(SESSION_UID$1).update({
1235
+ documentId: session2.documentId,
1236
+ data: {
1237
+ geoLocation: JSON.stringify(geoLocation),
1238
+ securityScore: geoData.securityScore || null
1239
+ }
1240
+ }).catch(() => {
1241
+ });
1242
+ }
1243
+ } catch (geoErr) {
1244
+ strapi.log.debug("[magic-sessionmanager] Geolocation lookup failed:", geoErr.message);
1245
+ }
1246
+ }
1197
1247
  const {
1198
1248
  token,
1199
1249
  tokenHash,
@@ -1202,6 +1252,8 @@ var session$3 = {
1202
1252
  locale,
1203
1253
  publishedAt,
1204
1254
  // Remove Strapi internal fields
1255
+ geoLocation: _geo,
1256
+ // Remove raw geoLocation
1205
1257
  ...sessionWithoutTokens
1206
1258
  } = session2;
1207
1259
  return {
@@ -1209,11 +1261,13 @@ var session$3 = {
1209
1261
  deviceType,
1210
1262
  browserName,
1211
1263
  osName,
1264
+ geoLocation,
1265
+ // Parsed object or null
1212
1266
  isCurrentSession,
1213
1267
  isTrulyActive,
1214
1268
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1215
1269
  };
1216
- });
1270
+ }));
1217
1271
  sessionsWithCurrent.sort((a, b) => {
1218
1272
  if (a.isCurrentSession) return -1;
1219
1273
  if (b.isCurrentSession) return 1;
@@ -1315,6 +1369,7 @@ var session$3 = {
1315
1369
  * Get current session info based on JWT token hash
1316
1370
  * GET /api/magic-sessionmanager/current-session
1317
1371
  * Returns the session associated with the current JWT token
1372
+ * Fetches geolocation on-demand if not already stored
1318
1373
  */
1319
1374
  async getCurrentSession(ctx) {
1320
1375
  try {
@@ -1343,6 +1398,40 @@ var session$3 = {
1343
1398
  const deviceType = currentSession.deviceType || parsedUA.deviceType;
1344
1399
  const browserName = currentSession.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
1345
1400
  const osName = currentSession.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
1401
+ let geoLocation = currentSession.geoLocation;
1402
+ if (typeof geoLocation === "string") {
1403
+ try {
1404
+ geoLocation = JSON.parse(geoLocation);
1405
+ } catch (e) {
1406
+ geoLocation = null;
1407
+ }
1408
+ }
1409
+ if (!geoLocation && currentSession.ipAddress) {
1410
+ try {
1411
+ const geolocationService = strapi.plugin("magic-sessionmanager").service("geolocation");
1412
+ const geoData = await geolocationService.getIpInfo(currentSession.ipAddress);
1413
+ if (geoData && geoData.country !== "Unknown") {
1414
+ geoLocation = {
1415
+ country: geoData.country,
1416
+ country_code: geoData.country_code,
1417
+ country_flag: geoData.country_flag,
1418
+ city: geoData.city,
1419
+ region: geoData.region,
1420
+ timezone: geoData.timezone
1421
+ };
1422
+ strapi.documents(SESSION_UID$1).update({
1423
+ documentId: currentSession.documentId,
1424
+ data: {
1425
+ geoLocation: JSON.stringify(geoLocation),
1426
+ securityScore: geoData.securityScore || null
1427
+ }
1428
+ }).catch(() => {
1429
+ });
1430
+ }
1431
+ } catch (geoErr) {
1432
+ strapi.log.debug("[magic-sessionmanager] Geolocation lookup failed:", geoErr.message);
1433
+ }
1434
+ }
1346
1435
  const {
1347
1436
  token: _,
1348
1437
  tokenHash: _th,
@@ -1350,6 +1439,7 @@ var session$3 = {
1350
1439
  refreshTokenHash: _rth,
1351
1440
  locale: _l,
1352
1441
  publishedAt: _p,
1442
+ geoLocation: _geo,
1353
1443
  ...sessionWithoutTokens
1354
1444
  } = currentSession;
1355
1445
  ctx.body = {
@@ -1358,6 +1448,8 @@ var session$3 = {
1358
1448
  deviceType,
1359
1449
  browserName,
1360
1450
  osName,
1451
+ geoLocation,
1452
+ // Parsed object or null
1361
1453
  isCurrentSession: true,
1362
1454
  isTrulyActive: timeSinceActive < inactivityTimeout,
1363
1455
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
@@ -1901,10 +1993,10 @@ var session$1 = ({ strapi: strapi2 }) => {
1901
1993
  return {
1902
1994
  /**
1903
1995
  * Create a new session record
1904
- * @param {Object} params - { userId, ip, userAgent, token, refreshToken }
1996
+ * @param {Object} params - { userId, ip, userAgent, token, refreshToken, geoData }
1905
1997
  * @returns {Promise<Object>} Created session
1906
1998
  */
1907
- async createSession({ userId, ip = "unknown", userAgent = "unknown", token, refreshToken }) {
1999
+ async createSession({ userId, ip = "unknown", userAgent = "unknown", token, refreshToken, geoData }) {
1908
2000
  try {
1909
2001
  const now = /* @__PURE__ */ new Date();
1910
2002
  const sessionId = generateSessionId(userId);
@@ -1912,6 +2004,7 @@ var session$1 = ({ strapi: strapi2 }) => {
1912
2004
  const encryptedRefreshToken = refreshToken ? encryptToken(refreshToken) : null;
1913
2005
  const tokenHashValue = token ? hashToken(token) : null;
1914
2006
  const refreshTokenHashValue = refreshToken ? hashToken(refreshToken) : null;
2007
+ const parsedUA = parseUserAgent(userAgent);
1915
2008
  const session2 = await strapi2.documents(SESSION_UID).create({
1916
2009
  data: {
1917
2010
  user: userId,
@@ -1929,8 +2022,22 @@ var session$1 = ({ strapi: strapi2 }) => {
1929
2022
  // Encrypted Refresh Token
1930
2023
  refreshTokenHash: refreshTokenHashValue,
1931
2024
  // SHA-256 hash for fast lookup
1932
- sessionId
2025
+ sessionId,
1933
2026
  // Unique identifier
2027
+ // Device info from User-Agent
2028
+ deviceType: parsedUA.deviceType,
2029
+ browserName: parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName,
2030
+ osName: parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName,
2031
+ // Geolocation data (if available from Premium features)
2032
+ geoLocation: geoData ? JSON.stringify({
2033
+ country: geoData.country,
2034
+ country_code: geoData.country_code,
2035
+ country_flag: geoData.country_flag,
2036
+ city: geoData.city,
2037
+ region: geoData.region,
2038
+ timezone: geoData.timezone
2039
+ }) : null,
2040
+ securityScore: geoData?.securityScore || null
1934
2041
  }
1935
2042
  });
1936
2043
  log.info(`[SUCCESS] Session ${session2.documentId} (${sessionId}) created for user ${userId}`);
@@ -1991,6 +2098,7 @@ var session$1 = ({ strapi: strapi2 }) => {
1991
2098
  },
1992
2099
  /**
1993
2100
  * Get ALL sessions (active + inactive) with accurate online status
2101
+ * Fetches geolocation on-demand for sessions without it (limited to prevent API abuse)
1994
2102
  * @returns {Promise<Array>} All sessions with enhanced data
1995
2103
  */
1996
2104
  async getAllSessions() {
@@ -2003,8 +2111,10 @@ var session$1 = ({ strapi: strapi2 }) => {
2003
2111
  });
2004
2112
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2005
2113
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2114
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2115
+ let geoLookupsRemaining = 20;
2006
2116
  const now = /* @__PURE__ */ new Date();
2007
- const enhancedSessions = sessions.map((session2) => {
2117
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2008
2118
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2009
2119
  const timeSinceActive = now - lastActiveTime;
2010
2120
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2012,6 +2122,40 @@ var session$1 = ({ strapi: strapi2 }) => {
2012
2122
  const deviceType = session2.deviceType || parsedUA.deviceType;
2013
2123
  const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2014
2124
  const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2125
+ let geoLocation = session2.geoLocation;
2126
+ if (typeof geoLocation === "string") {
2127
+ try {
2128
+ geoLocation = JSON.parse(geoLocation);
2129
+ } catch (e) {
2130
+ geoLocation = null;
2131
+ }
2132
+ }
2133
+ if (!geoLocation && session2.ipAddress && geoLookupsRemaining > 0) {
2134
+ geoLookupsRemaining--;
2135
+ try {
2136
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2137
+ if (geoData && geoData.country !== "Unknown") {
2138
+ geoLocation = {
2139
+ country: geoData.country,
2140
+ country_code: geoData.country_code,
2141
+ country_flag: geoData.country_flag,
2142
+ city: geoData.city,
2143
+ region: geoData.region,
2144
+ timezone: geoData.timezone
2145
+ };
2146
+ strapi2.documents(SESSION_UID).update({
2147
+ documentId: session2.documentId,
2148
+ data: {
2149
+ geoLocation: JSON.stringify(geoLocation),
2150
+ securityScore: geoData.securityScore || null
2151
+ }
2152
+ }).catch(() => {
2153
+ });
2154
+ }
2155
+ } catch (geoErr) {
2156
+ log.debug("Geolocation lookup failed:", geoErr.message);
2157
+ }
2158
+ }
2015
2159
  const {
2016
2160
  token,
2017
2161
  tokenHash,
@@ -2019,6 +2163,8 @@ var session$1 = ({ strapi: strapi2 }) => {
2019
2163
  refreshTokenHash,
2020
2164
  locale,
2021
2165
  publishedAt,
2166
+ geoLocation: _geo,
2167
+ // Remove raw geoLocation, we use parsed version
2022
2168
  ...safeSession
2023
2169
  } = session2;
2024
2170
  return {
@@ -2026,10 +2172,12 @@ var session$1 = ({ strapi: strapi2 }) => {
2026
2172
  deviceType,
2027
2173
  browserName,
2028
2174
  osName,
2175
+ geoLocation,
2176
+ // Parsed object or null
2029
2177
  isTrulyActive,
2030
2178
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2031
2179
  };
2032
- });
2180
+ }));
2033
2181
  return enhancedSessions;
2034
2182
  } catch (err) {
2035
2183
  log.error("Error getting all sessions:", err);
@@ -2038,6 +2186,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2038
2186
  },
2039
2187
  /**
2040
2188
  * Get all active sessions with accurate online status
2189
+ * Fetches geolocation on-demand for sessions without it
2041
2190
  * @returns {Promise<Array>} Active sessions with user data and online status
2042
2191
  */
2043
2192
  async getActiveSessions() {
@@ -2049,8 +2198,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2049
2198
  });
2050
2199
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2051
2200
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2201
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2052
2202
  const now = /* @__PURE__ */ new Date();
2053
- const enhancedSessions = sessions.map((session2) => {
2203
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2054
2204
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2055
2205
  const timeSinceActive = now - lastActiveTime;
2056
2206
  const isTrulyActive = timeSinceActive < inactivityTimeout;
@@ -2058,6 +2208,39 @@ var session$1 = ({ strapi: strapi2 }) => {
2058
2208
  const deviceType = session2.deviceType || parsedUA.deviceType;
2059
2209
  const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2060
2210
  const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2211
+ let geoLocation = session2.geoLocation;
2212
+ if (typeof geoLocation === "string") {
2213
+ try {
2214
+ geoLocation = JSON.parse(geoLocation);
2215
+ } catch (e) {
2216
+ geoLocation = null;
2217
+ }
2218
+ }
2219
+ if (!geoLocation && session2.ipAddress) {
2220
+ try {
2221
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2222
+ if (geoData && geoData.country !== "Unknown") {
2223
+ geoLocation = {
2224
+ country: geoData.country,
2225
+ country_code: geoData.country_code,
2226
+ country_flag: geoData.country_flag,
2227
+ city: geoData.city,
2228
+ region: geoData.region,
2229
+ timezone: geoData.timezone
2230
+ };
2231
+ strapi2.documents(SESSION_UID).update({
2232
+ documentId: session2.documentId,
2233
+ data: {
2234
+ geoLocation: JSON.stringify(geoLocation),
2235
+ securityScore: geoData.securityScore || null
2236
+ }
2237
+ }).catch(() => {
2238
+ });
2239
+ }
2240
+ } catch (geoErr) {
2241
+ log.debug("Geolocation lookup failed:", geoErr.message);
2242
+ }
2243
+ }
2061
2244
  const {
2062
2245
  token,
2063
2246
  tokenHash,
@@ -2065,6 +2248,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2065
2248
  refreshTokenHash,
2066
2249
  locale,
2067
2250
  publishedAt,
2251
+ geoLocation: _geo,
2068
2252
  ...safeSession
2069
2253
  } = session2;
2070
2254
  return {
@@ -2072,10 +2256,11 @@ var session$1 = ({ strapi: strapi2 }) => {
2072
2256
  deviceType,
2073
2257
  browserName,
2074
2258
  osName,
2259
+ geoLocation,
2075
2260
  isTrulyActive,
2076
2261
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2077
2262
  };
2078
- });
2263
+ }));
2079
2264
  return enhancedSessions.filter((s) => s.isTrulyActive);
2080
2265
  } catch (err) {
2081
2266
  log.error("Error getting active sessions:", err);
@@ -2085,6 +2270,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2085
2270
  /**
2086
2271
  * Get all sessions for a specific user
2087
2272
  * Supports both numeric id (legacy) and documentId (Strapi v5)
2273
+ * Fetches geolocation on-demand for sessions without it
2088
2274
  * @param {string|number} userId - User documentId or numeric id
2089
2275
  * @returns {Promise<Array>} User's sessions with accurate online status
2090
2276
  */
@@ -2103,8 +2289,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2103
2289
  });
2104
2290
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2105
2291
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2292
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2106
2293
  const now = /* @__PURE__ */ new Date();
2107
- const enhancedSessions = sessions.map((session2) => {
2294
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2108
2295
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2109
2296
  const timeSinceActive = now - lastActiveTime;
2110
2297
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2112,6 +2299,39 @@ var session$1 = ({ strapi: strapi2 }) => {
2112
2299
  const deviceType = session2.deviceType || parsedUA.deviceType;
2113
2300
  const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2114
2301
  const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2302
+ let geoLocation = session2.geoLocation;
2303
+ if (typeof geoLocation === "string") {
2304
+ try {
2305
+ geoLocation = JSON.parse(geoLocation);
2306
+ } catch (e) {
2307
+ geoLocation = null;
2308
+ }
2309
+ }
2310
+ if (!geoLocation && session2.ipAddress) {
2311
+ try {
2312
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2313
+ if (geoData && geoData.country !== "Unknown") {
2314
+ geoLocation = {
2315
+ country: geoData.country,
2316
+ country_code: geoData.country_code,
2317
+ country_flag: geoData.country_flag,
2318
+ city: geoData.city,
2319
+ region: geoData.region,
2320
+ timezone: geoData.timezone
2321
+ };
2322
+ strapi2.documents(SESSION_UID).update({
2323
+ documentId: session2.documentId,
2324
+ data: {
2325
+ geoLocation: JSON.stringify(geoLocation),
2326
+ securityScore: geoData.securityScore || null
2327
+ }
2328
+ }).catch(() => {
2329
+ });
2330
+ }
2331
+ } catch (geoErr) {
2332
+ log.debug("Geolocation lookup failed:", geoErr.message);
2333
+ }
2334
+ }
2115
2335
  const {
2116
2336
  token,
2117
2337
  tokenHash,
@@ -2119,6 +2339,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2119
2339
  refreshTokenHash,
2120
2340
  locale,
2121
2341
  publishedAt,
2342
+ geoLocation: _geo,
2122
2343
  ...safeSession
2123
2344
  } = session2;
2124
2345
  return {
@@ -2126,10 +2347,11 @@ var session$1 = ({ strapi: strapi2 }) => {
2126
2347
  deviceType,
2127
2348
  browserName,
2128
2349
  osName,
2350
+ geoLocation,
2129
2351
  isTrulyActive,
2130
2352
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2131
2353
  };
2132
- });
2354
+ }));
2133
2355
  return enhancedSessions;
2134
2356
  } catch (err) {
2135
2357
  log.error("Error getting user sessions:", err);
@@ -2242,7 +2464,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2242
2464
  }
2243
2465
  };
2244
2466
  };
2245
- const version = "4.2.10";
2467
+ const version = "4.2.13";
2246
2468
  const require$$2 = {
2247
2469
  version
2248
2470
  };
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.2.11",
2
+ "version": "4.2.13",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",