strapi-plugin-magic-sessionmanager 4.2.12 → 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`);
@@ -509,20 +536,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
509
536
  try {
510
537
  const refreshToken = ctx.request.body?.refreshToken;
511
538
  if (refreshToken) {
512
- 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({
513
541
  filters: {
542
+ refreshTokenHash: refreshTokenHashValue,
514
543
  isActive: true
515
544
  }
516
545
  });
517
- const matchingSession = allSessions.find((session2) => {
518
- if (!session2.refreshToken) return false;
519
- try {
520
- const decrypted = decryptToken$2(session2.refreshToken);
521
- return decrypted === refreshToken;
522
- } catch (err) {
523
- return false;
524
- }
525
- });
526
546
  if (!matchingSession) {
527
547
  log.warn("[BLOCKED] Blocked refresh token request - no active session");
528
548
  ctx.status = 401;
@@ -548,20 +568,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
548
568
  const newAccessToken = ctx.body.jwt;
549
569
  const newRefreshToken = ctx.body.refreshToken;
550
570
  if (oldRefreshToken) {
551
- 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({
552
573
  filters: {
574
+ refreshTokenHash: oldRefreshTokenHash,
553
575
  isActive: true
554
576
  }
555
577
  });
556
- const matchingSession = allSessions.find((session2) => {
557
- if (!session2.refreshToken) return false;
558
- try {
559
- const decrypted = decryptToken$2(session2.refreshToken);
560
- return decrypted === oldRefreshToken;
561
- } catch (err) {
562
- return false;
563
- }
564
- });
565
578
  if (matchingSession) {
566
579
  const encryptedToken = newAccessToken ? encryptToken$1(newAccessToken) : matchingSession.token;
567
580
  const encryptedRefreshToken = newRefreshToken ? encryptToken$1(newRefreshToken) : matchingSession.refreshToken;
@@ -690,11 +703,11 @@ var destroy$1 = async ({ strapi: strapi2 }) => {
690
703
  const log = createLogger$2(strapi2);
691
704
  if (strapi2.licenseGuard && strapi2.licenseGuard.pingInterval) {
692
705
  clearInterval(strapi2.licenseGuard.pingInterval);
693
- log.info("🛑 License pinging stopped");
706
+ log.info("[STOP] License pinging stopped");
694
707
  }
695
708
  if (strapi2.sessionManagerIntervals && strapi2.sessionManagerIntervals.cleanup) {
696
709
  clearInterval(strapi2.sessionManagerIntervals.cleanup);
697
- log.info("🛑 Session cleanup interval stopped");
710
+ log.info("[STOP] Session cleanup interval stopped");
698
711
  }
699
712
  log.info("[SUCCESS] Plugin cleanup completed");
700
713
  };
@@ -1175,6 +1188,7 @@ var session$3 = {
1175
1188
  * GET /api/magic-sessionmanager/my-sessions
1176
1189
  * Automatically uses the authenticated user's documentId
1177
1190
  * Marks which session is the current one (based on JWT token hash)
1191
+ * Fetches geolocation data on-demand if not already stored
1178
1192
  */
1179
1193
  async getOwnSessions(ctx) {
1180
1194
  try {
@@ -1191,7 +1205,8 @@ var session$3 = {
1191
1205
  const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
1192
1206
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
1193
1207
  const now = /* @__PURE__ */ new Date();
1194
- const sessionsWithCurrent = allSessions.map((session2) => {
1208
+ const geolocationService = strapi.plugin("magic-sessionmanager").service("geolocation");
1209
+ const sessionsWithCurrent = await Promise.all(allSessions.map(async (session2) => {
1195
1210
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1196
1211
  const timeSinceActive = now - lastActiveTime;
1197
1212
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -1208,6 +1223,31 @@ var session$3 = {
1208
1223
  geoLocation = null;
1209
1224
  }
1210
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
+ }
1211
1251
  const {
1212
1252
  token,
1213
1253
  tokenHash,
@@ -1231,7 +1271,7 @@ var session$3 = {
1231
1271
  isTrulyActive,
1232
1272
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1233
1273
  };
1234
- });
1274
+ }));
1235
1275
  sessionsWithCurrent.sort((a, b) => {
1236
1276
  if (a.isCurrentSession) return -1;
1237
1277
  if (b.isCurrentSession) return 1;
@@ -1333,6 +1373,7 @@ var session$3 = {
1333
1373
  * Get current session info based on JWT token hash
1334
1374
  * GET /api/magic-sessionmanager/current-session
1335
1375
  * Returns the session associated with the current JWT token
1376
+ * Fetches geolocation on-demand if not already stored
1336
1377
  */
1337
1378
  async getCurrentSession(ctx) {
1338
1379
  try {
@@ -1369,6 +1410,32 @@ var session$3 = {
1369
1410
  geoLocation = null;
1370
1411
  }
1371
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
+ }
1372
1439
  const {
1373
1440
  token: _,
1374
1441
  tokenHash: _th,
@@ -2035,6 +2102,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2035
2102
  },
2036
2103
  /**
2037
2104
  * Get ALL sessions (active + inactive) with accurate online status
2105
+ * Fetches geolocation on-demand for sessions without it (limited to prevent API abuse)
2038
2106
  * @returns {Promise<Array>} All sessions with enhanced data
2039
2107
  */
2040
2108
  async getAllSessions() {
@@ -2047,8 +2115,10 @@ var session$1 = ({ strapi: strapi2 }) => {
2047
2115
  });
2048
2116
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2049
2117
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2118
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2119
+ let geoLookupsRemaining = 20;
2050
2120
  const now = /* @__PURE__ */ new Date();
2051
- const enhancedSessions = sessions.map((session2) => {
2121
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2052
2122
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2053
2123
  const timeSinceActive = now - lastActiveTime;
2054
2124
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2064,6 +2134,32 @@ var session$1 = ({ strapi: strapi2 }) => {
2064
2134
  geoLocation = null;
2065
2135
  }
2066
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
+ }
2067
2163
  const {
2068
2164
  token,
2069
2165
  tokenHash,
@@ -2085,7 +2181,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2085
2181
  isTrulyActive,
2086
2182
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2087
2183
  };
2088
- });
2184
+ }));
2089
2185
  return enhancedSessions;
2090
2186
  } catch (err) {
2091
2187
  log.error("Error getting all sessions:", err);
@@ -2094,6 +2190,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2094
2190
  },
2095
2191
  /**
2096
2192
  * Get all active sessions with accurate online status
2193
+ * Fetches geolocation on-demand for sessions without it
2097
2194
  * @returns {Promise<Array>} Active sessions with user data and online status
2098
2195
  */
2099
2196
  async getActiveSessions() {
@@ -2105,8 +2202,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2105
2202
  });
2106
2203
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2107
2204
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2205
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2108
2206
  const now = /* @__PURE__ */ new Date();
2109
- const enhancedSessions = sessions.map((session2) => {
2207
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2110
2208
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2111
2209
  const timeSinceActive = now - lastActiveTime;
2112
2210
  const isTrulyActive = timeSinceActive < inactivityTimeout;
@@ -2122,6 +2220,31 @@ var session$1 = ({ strapi: strapi2 }) => {
2122
2220
  geoLocation = null;
2123
2221
  }
2124
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
+ }
2125
2248
  const {
2126
2249
  token,
2127
2250
  tokenHash,
@@ -2141,7 +2264,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2141
2264
  isTrulyActive,
2142
2265
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2143
2266
  };
2144
- });
2267
+ }));
2145
2268
  return enhancedSessions.filter((s) => s.isTrulyActive);
2146
2269
  } catch (err) {
2147
2270
  log.error("Error getting active sessions:", err);
@@ -2151,6 +2274,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2151
2274
  /**
2152
2275
  * Get all sessions for a specific user
2153
2276
  * Supports both numeric id (legacy) and documentId (Strapi v5)
2277
+ * Fetches geolocation on-demand for sessions without it
2154
2278
  * @param {string|number} userId - User documentId or numeric id
2155
2279
  * @returns {Promise<Array>} User's sessions with accurate online status
2156
2280
  */
@@ -2169,8 +2293,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2169
2293
  });
2170
2294
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2171
2295
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2296
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2172
2297
  const now = /* @__PURE__ */ new Date();
2173
- const enhancedSessions = sessions.map((session2) => {
2298
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2174
2299
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2175
2300
  const timeSinceActive = now - lastActiveTime;
2176
2301
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2186,6 +2311,31 @@ var session$1 = ({ strapi: strapi2 }) => {
2186
2311
  geoLocation = null;
2187
2312
  }
2188
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
+ }
2189
2339
  const {
2190
2340
  token,
2191
2341
  tokenHash,
@@ -2205,7 +2355,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2205
2355
  isTrulyActive,
2206
2356
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2207
2357
  };
2208
- });
2358
+ }));
2209
2359
  return enhancedSessions;
2210
2360
  } catch (err) {
2211
2361
  log.error("Error getting user sessions:", err);
@@ -2318,7 +2468,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2318
2468
  }
2319
2469
  };
2320
2470
  };
2321
- const version = "4.2.11";
2471
+ const version = "4.2.13";
2322
2472
  const require$$2 = {
2323
2473
  version
2324
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`);
@@ -505,20 +532,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
505
532
  try {
506
533
  const refreshToken = ctx.request.body?.refreshToken;
507
534
  if (refreshToken) {
508
- 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({
509
537
  filters: {
538
+ refreshTokenHash: refreshTokenHashValue,
510
539
  isActive: true
511
540
  }
512
541
  });
513
- const matchingSession = allSessions.find((session2) => {
514
- if (!session2.refreshToken) return false;
515
- try {
516
- const decrypted = decryptToken$2(session2.refreshToken);
517
- return decrypted === refreshToken;
518
- } catch (err) {
519
- return false;
520
- }
521
- });
522
542
  if (!matchingSession) {
523
543
  log.warn("[BLOCKED] Blocked refresh token request - no active session");
524
544
  ctx.status = 401;
@@ -544,20 +564,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
544
564
  const newAccessToken = ctx.body.jwt;
545
565
  const newRefreshToken = ctx.body.refreshToken;
546
566
  if (oldRefreshToken) {
547
- 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({
548
569
  filters: {
570
+ refreshTokenHash: oldRefreshTokenHash,
549
571
  isActive: true
550
572
  }
551
573
  });
552
- const matchingSession = allSessions.find((session2) => {
553
- if (!session2.refreshToken) return false;
554
- try {
555
- const decrypted = decryptToken$2(session2.refreshToken);
556
- return decrypted === oldRefreshToken;
557
- } catch (err) {
558
- return false;
559
- }
560
- });
561
574
  if (matchingSession) {
562
575
  const encryptedToken = newAccessToken ? encryptToken$1(newAccessToken) : matchingSession.token;
563
576
  const encryptedRefreshToken = newRefreshToken ? encryptToken$1(newRefreshToken) : matchingSession.refreshToken;
@@ -686,11 +699,11 @@ var destroy$1 = async ({ strapi: strapi2 }) => {
686
699
  const log = createLogger$2(strapi2);
687
700
  if (strapi2.licenseGuard && strapi2.licenseGuard.pingInterval) {
688
701
  clearInterval(strapi2.licenseGuard.pingInterval);
689
- log.info("🛑 License pinging stopped");
702
+ log.info("[STOP] License pinging stopped");
690
703
  }
691
704
  if (strapi2.sessionManagerIntervals && strapi2.sessionManagerIntervals.cleanup) {
692
705
  clearInterval(strapi2.sessionManagerIntervals.cleanup);
693
- log.info("🛑 Session cleanup interval stopped");
706
+ log.info("[STOP] Session cleanup interval stopped");
694
707
  }
695
708
  log.info("[SUCCESS] Plugin cleanup completed");
696
709
  };
@@ -1171,6 +1184,7 @@ var session$3 = {
1171
1184
  * GET /api/magic-sessionmanager/my-sessions
1172
1185
  * Automatically uses the authenticated user's documentId
1173
1186
  * Marks which session is the current one (based on JWT token hash)
1187
+ * Fetches geolocation data on-demand if not already stored
1174
1188
  */
1175
1189
  async getOwnSessions(ctx) {
1176
1190
  try {
@@ -1187,7 +1201,8 @@ var session$3 = {
1187
1201
  const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
1188
1202
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
1189
1203
  const now = /* @__PURE__ */ new Date();
1190
- const sessionsWithCurrent = allSessions.map((session2) => {
1204
+ const geolocationService = strapi.plugin("magic-sessionmanager").service("geolocation");
1205
+ const sessionsWithCurrent = await Promise.all(allSessions.map(async (session2) => {
1191
1206
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1192
1207
  const timeSinceActive = now - lastActiveTime;
1193
1208
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -1204,6 +1219,31 @@ var session$3 = {
1204
1219
  geoLocation = null;
1205
1220
  }
1206
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
+ }
1207
1247
  const {
1208
1248
  token,
1209
1249
  tokenHash,
@@ -1227,7 +1267,7 @@ var session$3 = {
1227
1267
  isTrulyActive,
1228
1268
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1229
1269
  };
1230
- });
1270
+ }));
1231
1271
  sessionsWithCurrent.sort((a, b) => {
1232
1272
  if (a.isCurrentSession) return -1;
1233
1273
  if (b.isCurrentSession) return 1;
@@ -1329,6 +1369,7 @@ var session$3 = {
1329
1369
  * Get current session info based on JWT token hash
1330
1370
  * GET /api/magic-sessionmanager/current-session
1331
1371
  * Returns the session associated with the current JWT token
1372
+ * Fetches geolocation on-demand if not already stored
1332
1373
  */
1333
1374
  async getCurrentSession(ctx) {
1334
1375
  try {
@@ -1365,6 +1406,32 @@ var session$3 = {
1365
1406
  geoLocation = null;
1366
1407
  }
1367
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
+ }
1368
1435
  const {
1369
1436
  token: _,
1370
1437
  tokenHash: _th,
@@ -2031,6 +2098,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2031
2098
  },
2032
2099
  /**
2033
2100
  * Get ALL sessions (active + inactive) with accurate online status
2101
+ * Fetches geolocation on-demand for sessions without it (limited to prevent API abuse)
2034
2102
  * @returns {Promise<Array>} All sessions with enhanced data
2035
2103
  */
2036
2104
  async getAllSessions() {
@@ -2043,8 +2111,10 @@ var session$1 = ({ strapi: strapi2 }) => {
2043
2111
  });
2044
2112
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2045
2113
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2114
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2115
+ let geoLookupsRemaining = 20;
2046
2116
  const now = /* @__PURE__ */ new Date();
2047
- const enhancedSessions = sessions.map((session2) => {
2117
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2048
2118
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2049
2119
  const timeSinceActive = now - lastActiveTime;
2050
2120
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2060,6 +2130,32 @@ var session$1 = ({ strapi: strapi2 }) => {
2060
2130
  geoLocation = null;
2061
2131
  }
2062
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
+ }
2063
2159
  const {
2064
2160
  token,
2065
2161
  tokenHash,
@@ -2081,7 +2177,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2081
2177
  isTrulyActive,
2082
2178
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2083
2179
  };
2084
- });
2180
+ }));
2085
2181
  return enhancedSessions;
2086
2182
  } catch (err) {
2087
2183
  log.error("Error getting all sessions:", err);
@@ -2090,6 +2186,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2090
2186
  },
2091
2187
  /**
2092
2188
  * Get all active sessions with accurate online status
2189
+ * Fetches geolocation on-demand for sessions without it
2093
2190
  * @returns {Promise<Array>} Active sessions with user data and online status
2094
2191
  */
2095
2192
  async getActiveSessions() {
@@ -2101,8 +2198,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2101
2198
  });
2102
2199
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2103
2200
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2201
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2104
2202
  const now = /* @__PURE__ */ new Date();
2105
- const enhancedSessions = sessions.map((session2) => {
2203
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2106
2204
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2107
2205
  const timeSinceActive = now - lastActiveTime;
2108
2206
  const isTrulyActive = timeSinceActive < inactivityTimeout;
@@ -2118,6 +2216,31 @@ var session$1 = ({ strapi: strapi2 }) => {
2118
2216
  geoLocation = null;
2119
2217
  }
2120
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
+ }
2121
2244
  const {
2122
2245
  token,
2123
2246
  tokenHash,
@@ -2137,7 +2260,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2137
2260
  isTrulyActive,
2138
2261
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2139
2262
  };
2140
- });
2263
+ }));
2141
2264
  return enhancedSessions.filter((s) => s.isTrulyActive);
2142
2265
  } catch (err) {
2143
2266
  log.error("Error getting active sessions:", err);
@@ -2147,6 +2270,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2147
2270
  /**
2148
2271
  * Get all sessions for a specific user
2149
2272
  * Supports both numeric id (legacy) and documentId (Strapi v5)
2273
+ * Fetches geolocation on-demand for sessions without it
2150
2274
  * @param {string|number} userId - User documentId or numeric id
2151
2275
  * @returns {Promise<Array>} User's sessions with accurate online status
2152
2276
  */
@@ -2165,8 +2289,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2165
2289
  });
2166
2290
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2167
2291
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2292
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2168
2293
  const now = /* @__PURE__ */ new Date();
2169
- const enhancedSessions = sessions.map((session2) => {
2294
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2170
2295
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2171
2296
  const timeSinceActive = now - lastActiveTime;
2172
2297
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2182,6 +2307,31 @@ var session$1 = ({ strapi: strapi2 }) => {
2182
2307
  geoLocation = null;
2183
2308
  }
2184
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
+ }
2185
2335
  const {
2186
2336
  token,
2187
2337
  tokenHash,
@@ -2201,7 +2351,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2201
2351
  isTrulyActive,
2202
2352
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2203
2353
  };
2204
- });
2354
+ }));
2205
2355
  return enhancedSessions;
2206
2356
  } catch (err) {
2207
2357
  log.error("Error getting user sessions:", err);
@@ -2314,7 +2464,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2314
2464
  }
2315
2465
  };
2316
2466
  };
2317
- const version = "4.2.11";
2467
+ const version = "4.2.13";
2318
2468
  const require$$2 = {
2319
2469
  version
2320
2470
  };
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.2.12",
2
+ "version": "4.2.13",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",