strapi-plugin-magic-sessionmanager 4.2.12 → 4.2.14

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,11 +238,50 @@ 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 UI
244
+ "/content-manager",
245
+ // Content Manager
246
+ "/content-type-builder",
247
+ // Content-Type Builder
248
+ "/upload",
249
+ // Media Library
250
+ "/i18n",
251
+ // Internationalization
252
+ "/users-permissions",
253
+ // Users & Permissions settings
254
+ "/email",
255
+ // Email plugin
256
+ "/_health",
257
+ // Health check
258
+ "/favicon.ico",
259
+ // Static assets
260
+ "/api/auth/local",
261
+ // Login endpoint
262
+ "/api/auth/register",
263
+ // Registration endpoint
264
+ "/api/auth/forgot-password",
265
+ // Password reset
266
+ "/api/auth/reset-password",
267
+ // Password reset
268
+ "/api/auth/logout",
269
+ // Logout endpoint (handled separately)
270
+ "/api/auth/refresh",
271
+ // Refresh token (has own validation in bootstrap.js)
272
+ "/api/connect",
273
+ // OAuth providers
274
+ "/api/magic-link"
275
+ // Magic link auth (if using magic-link plugin)
276
+ ];
230
277
  if (skipPaths.some((p) => ctx.path.startsWith(p))) {
231
278
  await next();
232
279
  return;
233
280
  }
281
+ if (!ctx.path.startsWith("/api/")) {
282
+ await next();
283
+ return;
284
+ }
234
285
  let matchingSession = null;
235
286
  try {
236
287
  const currentTokenHash = hashToken$3(currentToken);
@@ -248,10 +299,8 @@ var lastSeen = ({ strapi: strapi2 }) => {
248
299
  ctx.state.sessionUserId = matchingSession.user.documentId;
249
300
  }
250
301
  } 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
- }
302
+ strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Request blocked - session terminated or invalid (token hash: ${currentTokenHash.substring(0, 8)}...)`);
303
+ return ctx.unauthorized("This session has been terminated. Please login again.");
255
304
  }
256
305
  } catch (err) {
257
306
  strapi2.log.debug("[magic-sessionmanager] Error checking session:", err.message);
@@ -265,6 +314,7 @@ var lastSeen = ({ strapi: strapi2 }) => {
265
314
  const lastTouch = lastTouchCache.get(matchingSession.documentId) || 0;
266
315
  if (now - lastTouch > rateLimit) {
267
316
  lastTouchCache.set(matchingSession.documentId, now);
317
+ cleanupOldCacheEntries();
268
318
  await strapi2.documents(SESSION_UID$3).update({
269
319
  documentId: matchingSession.documentId,
270
320
  data: { lastActive: /* @__PURE__ */ new Date() }
@@ -351,20 +401,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
351
401
  ctx.body = { message: "Logged out successfully" };
352
402
  return;
353
403
  }
354
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
404
+ const tokenHashValue = hashToken$2(token);
405
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
355
406
  filters: {
407
+ tokenHash: tokenHashValue,
356
408
  isActive: true
357
409
  }
358
410
  });
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
411
  if (matchingSession) {
369
412
  await sessionService.terminateSession({ sessionId: matchingSession.documentId });
370
413
  log.info(`[LOGOUT] Logout via /api/auth/logout - Session ${matchingSession.documentId} terminated`);
@@ -509,20 +552,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
509
552
  try {
510
553
  const refreshToken = ctx.request.body?.refreshToken;
511
554
  if (refreshToken) {
512
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
555
+ const refreshTokenHashValue = hashToken$2(refreshToken);
556
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
513
557
  filters: {
558
+ refreshTokenHash: refreshTokenHashValue,
514
559
  isActive: true
515
560
  }
516
561
  });
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
562
  if (!matchingSession) {
527
563
  log.warn("[BLOCKED] Blocked refresh token request - no active session");
528
564
  ctx.status = 401;
@@ -548,20 +584,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
548
584
  const newAccessToken = ctx.body.jwt;
549
585
  const newRefreshToken = ctx.body.refreshToken;
550
586
  if (oldRefreshToken) {
551
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
587
+ const oldRefreshTokenHash = hashToken$2(oldRefreshToken);
588
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
552
589
  filters: {
590
+ refreshTokenHash: oldRefreshTokenHash,
553
591
  isActive: true
554
592
  }
555
593
  });
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
594
  if (matchingSession) {
566
595
  const encryptedToken = newAccessToken ? encryptToken$1(newAccessToken) : matchingSession.token;
567
596
  const encryptedRefreshToken = newRefreshToken ? encryptToken$1(newRefreshToken) : matchingSession.refreshToken;
@@ -690,11 +719,11 @@ var destroy$1 = async ({ strapi: strapi2 }) => {
690
719
  const log = createLogger$2(strapi2);
691
720
  if (strapi2.licenseGuard && strapi2.licenseGuard.pingInterval) {
692
721
  clearInterval(strapi2.licenseGuard.pingInterval);
693
- log.info("🛑 License pinging stopped");
722
+ log.info("[STOP] License pinging stopped");
694
723
  }
695
724
  if (strapi2.sessionManagerIntervals && strapi2.sessionManagerIntervals.cleanup) {
696
725
  clearInterval(strapi2.sessionManagerIntervals.cleanup);
697
- log.info("🛑 Session cleanup interval stopped");
726
+ log.info("[STOP] Session cleanup interval stopped");
698
727
  }
699
728
  log.info("[SUCCESS] Plugin cleanup completed");
700
729
  };
@@ -1175,6 +1204,7 @@ var session$3 = {
1175
1204
  * GET /api/magic-sessionmanager/my-sessions
1176
1205
  * Automatically uses the authenticated user's documentId
1177
1206
  * Marks which session is the current one (based on JWT token hash)
1207
+ * Fetches geolocation data on-demand if not already stored
1178
1208
  */
1179
1209
  async getOwnSessions(ctx) {
1180
1210
  try {
@@ -1191,7 +1221,8 @@ var session$3 = {
1191
1221
  const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
1192
1222
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
1193
1223
  const now = /* @__PURE__ */ new Date();
1194
- const sessionsWithCurrent = allSessions.map((session2) => {
1224
+ const geolocationService = strapi.plugin("magic-sessionmanager").service("geolocation");
1225
+ const sessionsWithCurrent = await Promise.all(allSessions.map(async (session2) => {
1195
1226
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1196
1227
  const timeSinceActive = now - lastActiveTime;
1197
1228
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -1208,6 +1239,31 @@ var session$3 = {
1208
1239
  geoLocation = null;
1209
1240
  }
1210
1241
  }
1242
+ if (!geoLocation && session2.ipAddress) {
1243
+ try {
1244
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
1245
+ if (geoData && geoData.country !== "Unknown") {
1246
+ geoLocation = {
1247
+ country: geoData.country,
1248
+ country_code: geoData.country_code,
1249
+ country_flag: geoData.country_flag,
1250
+ city: geoData.city,
1251
+ region: geoData.region,
1252
+ timezone: geoData.timezone
1253
+ };
1254
+ strapi.documents(SESSION_UID$1).update({
1255
+ documentId: session2.documentId,
1256
+ data: {
1257
+ geoLocation: JSON.stringify(geoLocation),
1258
+ securityScore: geoData.securityScore || null
1259
+ }
1260
+ }).catch(() => {
1261
+ });
1262
+ }
1263
+ } catch (geoErr) {
1264
+ strapi.log.debug("[magic-sessionmanager] Geolocation lookup failed:", geoErr.message);
1265
+ }
1266
+ }
1211
1267
  const {
1212
1268
  token,
1213
1269
  tokenHash,
@@ -1231,7 +1287,7 @@ var session$3 = {
1231
1287
  isTrulyActive,
1232
1288
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1233
1289
  };
1234
- });
1290
+ }));
1235
1291
  sessionsWithCurrent.sort((a, b) => {
1236
1292
  if (a.isCurrentSession) return -1;
1237
1293
  if (b.isCurrentSession) return 1;
@@ -1333,6 +1389,7 @@ var session$3 = {
1333
1389
  * Get current session info based on JWT token hash
1334
1390
  * GET /api/magic-sessionmanager/current-session
1335
1391
  * Returns the session associated with the current JWT token
1392
+ * Fetches geolocation on-demand if not already stored
1336
1393
  */
1337
1394
  async getCurrentSession(ctx) {
1338
1395
  try {
@@ -1369,6 +1426,32 @@ var session$3 = {
1369
1426
  geoLocation = null;
1370
1427
  }
1371
1428
  }
1429
+ if (!geoLocation && currentSession.ipAddress) {
1430
+ try {
1431
+ const geolocationService = strapi.plugin("magic-sessionmanager").service("geolocation");
1432
+ const geoData = await geolocationService.getIpInfo(currentSession.ipAddress);
1433
+ if (geoData && geoData.country !== "Unknown") {
1434
+ geoLocation = {
1435
+ country: geoData.country,
1436
+ country_code: geoData.country_code,
1437
+ country_flag: geoData.country_flag,
1438
+ city: geoData.city,
1439
+ region: geoData.region,
1440
+ timezone: geoData.timezone
1441
+ };
1442
+ strapi.documents(SESSION_UID$1).update({
1443
+ documentId: currentSession.documentId,
1444
+ data: {
1445
+ geoLocation: JSON.stringify(geoLocation),
1446
+ securityScore: geoData.securityScore || null
1447
+ }
1448
+ }).catch(() => {
1449
+ });
1450
+ }
1451
+ } catch (geoErr) {
1452
+ strapi.log.debug("[magic-sessionmanager] Geolocation lookup failed:", geoErr.message);
1453
+ }
1454
+ }
1372
1455
  const {
1373
1456
  token: _,
1374
1457
  tokenHash: _th,
@@ -2035,6 +2118,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2035
2118
  },
2036
2119
  /**
2037
2120
  * Get ALL sessions (active + inactive) with accurate online status
2121
+ * Fetches geolocation on-demand for sessions without it (limited to prevent API abuse)
2038
2122
  * @returns {Promise<Array>} All sessions with enhanced data
2039
2123
  */
2040
2124
  async getAllSessions() {
@@ -2047,8 +2131,10 @@ var session$1 = ({ strapi: strapi2 }) => {
2047
2131
  });
2048
2132
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2049
2133
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2134
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2135
+ let geoLookupsRemaining = 20;
2050
2136
  const now = /* @__PURE__ */ new Date();
2051
- const enhancedSessions = sessions.map((session2) => {
2137
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2052
2138
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2053
2139
  const timeSinceActive = now - lastActiveTime;
2054
2140
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2064,6 +2150,32 @@ var session$1 = ({ strapi: strapi2 }) => {
2064
2150
  geoLocation = null;
2065
2151
  }
2066
2152
  }
2153
+ if (!geoLocation && session2.ipAddress && geoLookupsRemaining > 0) {
2154
+ geoLookupsRemaining--;
2155
+ try {
2156
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2157
+ if (geoData && geoData.country !== "Unknown") {
2158
+ geoLocation = {
2159
+ country: geoData.country,
2160
+ country_code: geoData.country_code,
2161
+ country_flag: geoData.country_flag,
2162
+ city: geoData.city,
2163
+ region: geoData.region,
2164
+ timezone: geoData.timezone
2165
+ };
2166
+ strapi2.documents(SESSION_UID).update({
2167
+ documentId: session2.documentId,
2168
+ data: {
2169
+ geoLocation: JSON.stringify(geoLocation),
2170
+ securityScore: geoData.securityScore || null
2171
+ }
2172
+ }).catch(() => {
2173
+ });
2174
+ }
2175
+ } catch (geoErr) {
2176
+ log.debug("Geolocation lookup failed:", geoErr.message);
2177
+ }
2178
+ }
2067
2179
  const {
2068
2180
  token,
2069
2181
  tokenHash,
@@ -2085,7 +2197,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2085
2197
  isTrulyActive,
2086
2198
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2087
2199
  };
2088
- });
2200
+ }));
2089
2201
  return enhancedSessions;
2090
2202
  } catch (err) {
2091
2203
  log.error("Error getting all sessions:", err);
@@ -2094,6 +2206,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2094
2206
  },
2095
2207
  /**
2096
2208
  * Get all active sessions with accurate online status
2209
+ * Fetches geolocation on-demand for sessions without it
2097
2210
  * @returns {Promise<Array>} Active sessions with user data and online status
2098
2211
  */
2099
2212
  async getActiveSessions() {
@@ -2105,8 +2218,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2105
2218
  });
2106
2219
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2107
2220
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2221
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2108
2222
  const now = /* @__PURE__ */ new Date();
2109
- const enhancedSessions = sessions.map((session2) => {
2223
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2110
2224
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2111
2225
  const timeSinceActive = now - lastActiveTime;
2112
2226
  const isTrulyActive = timeSinceActive < inactivityTimeout;
@@ -2122,6 +2236,31 @@ var session$1 = ({ strapi: strapi2 }) => {
2122
2236
  geoLocation = null;
2123
2237
  }
2124
2238
  }
2239
+ if (!geoLocation && session2.ipAddress) {
2240
+ try {
2241
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2242
+ if (geoData && geoData.country !== "Unknown") {
2243
+ geoLocation = {
2244
+ country: geoData.country,
2245
+ country_code: geoData.country_code,
2246
+ country_flag: geoData.country_flag,
2247
+ city: geoData.city,
2248
+ region: geoData.region,
2249
+ timezone: geoData.timezone
2250
+ };
2251
+ strapi2.documents(SESSION_UID).update({
2252
+ documentId: session2.documentId,
2253
+ data: {
2254
+ geoLocation: JSON.stringify(geoLocation),
2255
+ securityScore: geoData.securityScore || null
2256
+ }
2257
+ }).catch(() => {
2258
+ });
2259
+ }
2260
+ } catch (geoErr) {
2261
+ log.debug("Geolocation lookup failed:", geoErr.message);
2262
+ }
2263
+ }
2125
2264
  const {
2126
2265
  token,
2127
2266
  tokenHash,
@@ -2141,7 +2280,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2141
2280
  isTrulyActive,
2142
2281
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2143
2282
  };
2144
- });
2283
+ }));
2145
2284
  return enhancedSessions.filter((s) => s.isTrulyActive);
2146
2285
  } catch (err) {
2147
2286
  log.error("Error getting active sessions:", err);
@@ -2151,6 +2290,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2151
2290
  /**
2152
2291
  * Get all sessions for a specific user
2153
2292
  * Supports both numeric id (legacy) and documentId (Strapi v5)
2293
+ * Fetches geolocation on-demand for sessions without it
2154
2294
  * @param {string|number} userId - User documentId or numeric id
2155
2295
  * @returns {Promise<Array>} User's sessions with accurate online status
2156
2296
  */
@@ -2169,8 +2309,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2169
2309
  });
2170
2310
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2171
2311
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2312
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2172
2313
  const now = /* @__PURE__ */ new Date();
2173
- const enhancedSessions = sessions.map((session2) => {
2314
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2174
2315
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2175
2316
  const timeSinceActive = now - lastActiveTime;
2176
2317
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2186,6 +2327,31 @@ var session$1 = ({ strapi: strapi2 }) => {
2186
2327
  geoLocation = null;
2187
2328
  }
2188
2329
  }
2330
+ if (!geoLocation && session2.ipAddress) {
2331
+ try {
2332
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2333
+ if (geoData && geoData.country !== "Unknown") {
2334
+ geoLocation = {
2335
+ country: geoData.country,
2336
+ country_code: geoData.country_code,
2337
+ country_flag: geoData.country_flag,
2338
+ city: geoData.city,
2339
+ region: geoData.region,
2340
+ timezone: geoData.timezone
2341
+ };
2342
+ strapi2.documents(SESSION_UID).update({
2343
+ documentId: session2.documentId,
2344
+ data: {
2345
+ geoLocation: JSON.stringify(geoLocation),
2346
+ securityScore: geoData.securityScore || null
2347
+ }
2348
+ }).catch(() => {
2349
+ });
2350
+ }
2351
+ } catch (geoErr) {
2352
+ log.debug("Geolocation lookup failed:", geoErr.message);
2353
+ }
2354
+ }
2189
2355
  const {
2190
2356
  token,
2191
2357
  tokenHash,
@@ -2205,7 +2371,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2205
2371
  isTrulyActive,
2206
2372
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2207
2373
  };
2208
- });
2374
+ }));
2209
2375
  return enhancedSessions;
2210
2376
  } catch (err) {
2211
2377
  log.error("Error getting user sessions:", err);
@@ -2318,7 +2484,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2318
2484
  }
2319
2485
  };
2320
2486
  };
2321
- const version = "4.2.11";
2487
+ const version = "4.2.13";
2322
2488
  const require$$2 = {
2323
2489
  version
2324
2490
  };
@@ -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,11 +234,50 @@ 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 UI
240
+ "/content-manager",
241
+ // Content Manager
242
+ "/content-type-builder",
243
+ // Content-Type Builder
244
+ "/upload",
245
+ // Media Library
246
+ "/i18n",
247
+ // Internationalization
248
+ "/users-permissions",
249
+ // Users & Permissions settings
250
+ "/email",
251
+ // Email plugin
252
+ "/_health",
253
+ // Health check
254
+ "/favicon.ico",
255
+ // Static assets
256
+ "/api/auth/local",
257
+ // Login endpoint
258
+ "/api/auth/register",
259
+ // Registration endpoint
260
+ "/api/auth/forgot-password",
261
+ // Password reset
262
+ "/api/auth/reset-password",
263
+ // Password reset
264
+ "/api/auth/logout",
265
+ // Logout endpoint (handled separately)
266
+ "/api/auth/refresh",
267
+ // Refresh token (has own validation in bootstrap.js)
268
+ "/api/connect",
269
+ // OAuth providers
270
+ "/api/magic-link"
271
+ // Magic link auth (if using magic-link plugin)
272
+ ];
226
273
  if (skipPaths.some((p) => ctx.path.startsWith(p))) {
227
274
  await next();
228
275
  return;
229
276
  }
277
+ if (!ctx.path.startsWith("/api/")) {
278
+ await next();
279
+ return;
280
+ }
230
281
  let matchingSession = null;
231
282
  try {
232
283
  const currentTokenHash = hashToken$3(currentToken);
@@ -244,10 +295,8 @@ var lastSeen = ({ strapi: strapi2 }) => {
244
295
  ctx.state.sessionUserId = matchingSession.user.documentId;
245
296
  }
246
297
  } 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
- }
298
+ strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Request blocked - session terminated or invalid (token hash: ${currentTokenHash.substring(0, 8)}...)`);
299
+ return ctx.unauthorized("This session has been terminated. Please login again.");
251
300
  }
252
301
  } catch (err) {
253
302
  strapi2.log.debug("[magic-sessionmanager] Error checking session:", err.message);
@@ -261,6 +310,7 @@ var lastSeen = ({ strapi: strapi2 }) => {
261
310
  const lastTouch = lastTouchCache.get(matchingSession.documentId) || 0;
262
311
  if (now - lastTouch > rateLimit) {
263
312
  lastTouchCache.set(matchingSession.documentId, now);
313
+ cleanupOldCacheEntries();
264
314
  await strapi2.documents(SESSION_UID$3).update({
265
315
  documentId: matchingSession.documentId,
266
316
  data: { lastActive: /* @__PURE__ */ new Date() }
@@ -347,20 +397,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
347
397
  ctx.body = { message: "Logged out successfully" };
348
398
  return;
349
399
  }
350
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
400
+ const tokenHashValue = hashToken$2(token);
401
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
351
402
  filters: {
403
+ tokenHash: tokenHashValue,
352
404
  isActive: true
353
405
  }
354
406
  });
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
407
  if (matchingSession) {
365
408
  await sessionService.terminateSession({ sessionId: matchingSession.documentId });
366
409
  log.info(`[LOGOUT] Logout via /api/auth/logout - Session ${matchingSession.documentId} terminated`);
@@ -505,20 +548,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
505
548
  try {
506
549
  const refreshToken = ctx.request.body?.refreshToken;
507
550
  if (refreshToken) {
508
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
551
+ const refreshTokenHashValue = hashToken$2(refreshToken);
552
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
509
553
  filters: {
554
+ refreshTokenHash: refreshTokenHashValue,
510
555
  isActive: true
511
556
  }
512
557
  });
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
558
  if (!matchingSession) {
523
559
  log.warn("[BLOCKED] Blocked refresh token request - no active session");
524
560
  ctx.status = 401;
@@ -544,20 +580,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
544
580
  const newAccessToken = ctx.body.jwt;
545
581
  const newRefreshToken = ctx.body.refreshToken;
546
582
  if (oldRefreshToken) {
547
- const allSessions = await strapi2.documents(SESSION_UID$2).findMany({
583
+ const oldRefreshTokenHash = hashToken$2(oldRefreshToken);
584
+ const matchingSession = await strapi2.documents(SESSION_UID$2).findFirst({
548
585
  filters: {
586
+ refreshTokenHash: oldRefreshTokenHash,
549
587
  isActive: true
550
588
  }
551
589
  });
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
590
  if (matchingSession) {
562
591
  const encryptedToken = newAccessToken ? encryptToken$1(newAccessToken) : matchingSession.token;
563
592
  const encryptedRefreshToken = newRefreshToken ? encryptToken$1(newRefreshToken) : matchingSession.refreshToken;
@@ -686,11 +715,11 @@ var destroy$1 = async ({ strapi: strapi2 }) => {
686
715
  const log = createLogger$2(strapi2);
687
716
  if (strapi2.licenseGuard && strapi2.licenseGuard.pingInterval) {
688
717
  clearInterval(strapi2.licenseGuard.pingInterval);
689
- log.info("🛑 License pinging stopped");
718
+ log.info("[STOP] License pinging stopped");
690
719
  }
691
720
  if (strapi2.sessionManagerIntervals && strapi2.sessionManagerIntervals.cleanup) {
692
721
  clearInterval(strapi2.sessionManagerIntervals.cleanup);
693
- log.info("🛑 Session cleanup interval stopped");
722
+ log.info("[STOP] Session cleanup interval stopped");
694
723
  }
695
724
  log.info("[SUCCESS] Plugin cleanup completed");
696
725
  };
@@ -1171,6 +1200,7 @@ var session$3 = {
1171
1200
  * GET /api/magic-sessionmanager/my-sessions
1172
1201
  * Automatically uses the authenticated user's documentId
1173
1202
  * Marks which session is the current one (based on JWT token hash)
1203
+ * Fetches geolocation data on-demand if not already stored
1174
1204
  */
1175
1205
  async getOwnSessions(ctx) {
1176
1206
  try {
@@ -1187,7 +1217,8 @@ var session$3 = {
1187
1217
  const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
1188
1218
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
1189
1219
  const now = /* @__PURE__ */ new Date();
1190
- const sessionsWithCurrent = allSessions.map((session2) => {
1220
+ const geolocationService = strapi.plugin("magic-sessionmanager").service("geolocation");
1221
+ const sessionsWithCurrent = await Promise.all(allSessions.map(async (session2) => {
1191
1222
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1192
1223
  const timeSinceActive = now - lastActiveTime;
1193
1224
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -1204,6 +1235,31 @@ var session$3 = {
1204
1235
  geoLocation = null;
1205
1236
  }
1206
1237
  }
1238
+ if (!geoLocation && session2.ipAddress) {
1239
+ try {
1240
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
1241
+ if (geoData && geoData.country !== "Unknown") {
1242
+ geoLocation = {
1243
+ country: geoData.country,
1244
+ country_code: geoData.country_code,
1245
+ country_flag: geoData.country_flag,
1246
+ city: geoData.city,
1247
+ region: geoData.region,
1248
+ timezone: geoData.timezone
1249
+ };
1250
+ strapi.documents(SESSION_UID$1).update({
1251
+ documentId: session2.documentId,
1252
+ data: {
1253
+ geoLocation: JSON.stringify(geoLocation),
1254
+ securityScore: geoData.securityScore || null
1255
+ }
1256
+ }).catch(() => {
1257
+ });
1258
+ }
1259
+ } catch (geoErr) {
1260
+ strapi.log.debug("[magic-sessionmanager] Geolocation lookup failed:", geoErr.message);
1261
+ }
1262
+ }
1207
1263
  const {
1208
1264
  token,
1209
1265
  tokenHash,
@@ -1227,7 +1283,7 @@ var session$3 = {
1227
1283
  isTrulyActive,
1228
1284
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1229
1285
  };
1230
- });
1286
+ }));
1231
1287
  sessionsWithCurrent.sort((a, b) => {
1232
1288
  if (a.isCurrentSession) return -1;
1233
1289
  if (b.isCurrentSession) return 1;
@@ -1329,6 +1385,7 @@ var session$3 = {
1329
1385
  * Get current session info based on JWT token hash
1330
1386
  * GET /api/magic-sessionmanager/current-session
1331
1387
  * Returns the session associated with the current JWT token
1388
+ * Fetches geolocation on-demand if not already stored
1332
1389
  */
1333
1390
  async getCurrentSession(ctx) {
1334
1391
  try {
@@ -1365,6 +1422,32 @@ var session$3 = {
1365
1422
  geoLocation = null;
1366
1423
  }
1367
1424
  }
1425
+ if (!geoLocation && currentSession.ipAddress) {
1426
+ try {
1427
+ const geolocationService = strapi.plugin("magic-sessionmanager").service("geolocation");
1428
+ const geoData = await geolocationService.getIpInfo(currentSession.ipAddress);
1429
+ if (geoData && geoData.country !== "Unknown") {
1430
+ geoLocation = {
1431
+ country: geoData.country,
1432
+ country_code: geoData.country_code,
1433
+ country_flag: geoData.country_flag,
1434
+ city: geoData.city,
1435
+ region: geoData.region,
1436
+ timezone: geoData.timezone
1437
+ };
1438
+ strapi.documents(SESSION_UID$1).update({
1439
+ documentId: currentSession.documentId,
1440
+ data: {
1441
+ geoLocation: JSON.stringify(geoLocation),
1442
+ securityScore: geoData.securityScore || null
1443
+ }
1444
+ }).catch(() => {
1445
+ });
1446
+ }
1447
+ } catch (geoErr) {
1448
+ strapi.log.debug("[magic-sessionmanager] Geolocation lookup failed:", geoErr.message);
1449
+ }
1450
+ }
1368
1451
  const {
1369
1452
  token: _,
1370
1453
  tokenHash: _th,
@@ -2031,6 +2114,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2031
2114
  },
2032
2115
  /**
2033
2116
  * Get ALL sessions (active + inactive) with accurate online status
2117
+ * Fetches geolocation on-demand for sessions without it (limited to prevent API abuse)
2034
2118
  * @returns {Promise<Array>} All sessions with enhanced data
2035
2119
  */
2036
2120
  async getAllSessions() {
@@ -2043,8 +2127,10 @@ var session$1 = ({ strapi: strapi2 }) => {
2043
2127
  });
2044
2128
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2045
2129
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2130
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2131
+ let geoLookupsRemaining = 20;
2046
2132
  const now = /* @__PURE__ */ new Date();
2047
- const enhancedSessions = sessions.map((session2) => {
2133
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2048
2134
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2049
2135
  const timeSinceActive = now - lastActiveTime;
2050
2136
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2060,6 +2146,32 @@ var session$1 = ({ strapi: strapi2 }) => {
2060
2146
  geoLocation = null;
2061
2147
  }
2062
2148
  }
2149
+ if (!geoLocation && session2.ipAddress && geoLookupsRemaining > 0) {
2150
+ geoLookupsRemaining--;
2151
+ try {
2152
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2153
+ if (geoData && geoData.country !== "Unknown") {
2154
+ geoLocation = {
2155
+ country: geoData.country,
2156
+ country_code: geoData.country_code,
2157
+ country_flag: geoData.country_flag,
2158
+ city: geoData.city,
2159
+ region: geoData.region,
2160
+ timezone: geoData.timezone
2161
+ };
2162
+ strapi2.documents(SESSION_UID).update({
2163
+ documentId: session2.documentId,
2164
+ data: {
2165
+ geoLocation: JSON.stringify(geoLocation),
2166
+ securityScore: geoData.securityScore || null
2167
+ }
2168
+ }).catch(() => {
2169
+ });
2170
+ }
2171
+ } catch (geoErr) {
2172
+ log.debug("Geolocation lookup failed:", geoErr.message);
2173
+ }
2174
+ }
2063
2175
  const {
2064
2176
  token,
2065
2177
  tokenHash,
@@ -2081,7 +2193,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2081
2193
  isTrulyActive,
2082
2194
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2083
2195
  };
2084
- });
2196
+ }));
2085
2197
  return enhancedSessions;
2086
2198
  } catch (err) {
2087
2199
  log.error("Error getting all sessions:", err);
@@ -2090,6 +2202,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2090
2202
  },
2091
2203
  /**
2092
2204
  * Get all active sessions with accurate online status
2205
+ * Fetches geolocation on-demand for sessions without it
2093
2206
  * @returns {Promise<Array>} Active sessions with user data and online status
2094
2207
  */
2095
2208
  async getActiveSessions() {
@@ -2101,8 +2214,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2101
2214
  });
2102
2215
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2103
2216
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2217
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2104
2218
  const now = /* @__PURE__ */ new Date();
2105
- const enhancedSessions = sessions.map((session2) => {
2219
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2106
2220
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2107
2221
  const timeSinceActive = now - lastActiveTime;
2108
2222
  const isTrulyActive = timeSinceActive < inactivityTimeout;
@@ -2118,6 +2232,31 @@ var session$1 = ({ strapi: strapi2 }) => {
2118
2232
  geoLocation = null;
2119
2233
  }
2120
2234
  }
2235
+ if (!geoLocation && session2.ipAddress) {
2236
+ try {
2237
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2238
+ if (geoData && geoData.country !== "Unknown") {
2239
+ geoLocation = {
2240
+ country: geoData.country,
2241
+ country_code: geoData.country_code,
2242
+ country_flag: geoData.country_flag,
2243
+ city: geoData.city,
2244
+ region: geoData.region,
2245
+ timezone: geoData.timezone
2246
+ };
2247
+ strapi2.documents(SESSION_UID).update({
2248
+ documentId: session2.documentId,
2249
+ data: {
2250
+ geoLocation: JSON.stringify(geoLocation),
2251
+ securityScore: geoData.securityScore || null
2252
+ }
2253
+ }).catch(() => {
2254
+ });
2255
+ }
2256
+ } catch (geoErr) {
2257
+ log.debug("Geolocation lookup failed:", geoErr.message);
2258
+ }
2259
+ }
2121
2260
  const {
2122
2261
  token,
2123
2262
  tokenHash,
@@ -2137,7 +2276,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2137
2276
  isTrulyActive,
2138
2277
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2139
2278
  };
2140
- });
2279
+ }));
2141
2280
  return enhancedSessions.filter((s) => s.isTrulyActive);
2142
2281
  } catch (err) {
2143
2282
  log.error("Error getting active sessions:", err);
@@ -2147,6 +2286,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2147
2286
  /**
2148
2287
  * Get all sessions for a specific user
2149
2288
  * Supports both numeric id (legacy) and documentId (Strapi v5)
2289
+ * Fetches geolocation on-demand for sessions without it
2150
2290
  * @param {string|number} userId - User documentId or numeric id
2151
2291
  * @returns {Promise<Array>} User's sessions with accurate online status
2152
2292
  */
@@ -2165,8 +2305,9 @@ var session$1 = ({ strapi: strapi2 }) => {
2165
2305
  });
2166
2306
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
2167
2307
  const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
2308
+ const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
2168
2309
  const now = /* @__PURE__ */ new Date();
2169
- const enhancedSessions = sessions.map((session2) => {
2310
+ const enhancedSessions = await Promise.all(sessions.map(async (session2) => {
2170
2311
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
2171
2312
  const timeSinceActive = now - lastActiveTime;
2172
2313
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
@@ -2182,6 +2323,31 @@ var session$1 = ({ strapi: strapi2 }) => {
2182
2323
  geoLocation = null;
2183
2324
  }
2184
2325
  }
2326
+ if (!geoLocation && session2.ipAddress) {
2327
+ try {
2328
+ const geoData = await geolocationService.getIpInfo(session2.ipAddress);
2329
+ if (geoData && geoData.country !== "Unknown") {
2330
+ geoLocation = {
2331
+ country: geoData.country,
2332
+ country_code: geoData.country_code,
2333
+ country_flag: geoData.country_flag,
2334
+ city: geoData.city,
2335
+ region: geoData.region,
2336
+ timezone: geoData.timezone
2337
+ };
2338
+ strapi2.documents(SESSION_UID).update({
2339
+ documentId: session2.documentId,
2340
+ data: {
2341
+ geoLocation: JSON.stringify(geoLocation),
2342
+ securityScore: geoData.securityScore || null
2343
+ }
2344
+ }).catch(() => {
2345
+ });
2346
+ }
2347
+ } catch (geoErr) {
2348
+ log.debug("Geolocation lookup failed:", geoErr.message);
2349
+ }
2350
+ }
2185
2351
  const {
2186
2352
  token,
2187
2353
  tokenHash,
@@ -2201,7 +2367,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2201
2367
  isTrulyActive,
2202
2368
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
2203
2369
  };
2204
- });
2370
+ }));
2205
2371
  return enhancedSessions;
2206
2372
  } catch (err) {
2207
2373
  log.error("Error getting user sessions:", err);
@@ -2314,7 +2480,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2314
2480
  }
2315
2481
  };
2316
2482
  };
2317
- const version = "4.2.11";
2483
+ const version = "4.2.13";
2318
2484
  const require$$2 = {
2319
2485
  version
2320
2486
  };
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.2.12",
2
+ "version": "4.2.14",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",