strapi-plugin-magic-sessionmanager 4.2.15 → 4.3.0

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.
@@ -1,9 +1,9 @@
1
- import { useEffect, useState } from "react";
1
+ import { useRef, useEffect, useState } from "react";
2
+ import { useFetchClient, useNotification } from "@strapi/strapi/admin";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  import { useIntl } from "react-intl";
4
5
  import { Box, Typography, Flex, Badge, Divider, Button } from "@strapi/design-system";
5
6
  import { Server, Clock, Cross, Check, Phone, Monitor } from "@strapi/icons";
6
- import { useFetchClient, useNotification } from "@strapi/strapi/admin";
7
7
  const strapi = {
8
8
  name: "magic-sessionmanager",
9
9
  displayName: "Magic Sessionmanager"
@@ -13,10 +13,29 @@ const pluginPkg = {
13
13
  };
14
14
  const pluginId = "magic-sessionmanager";
15
15
  const PLUGIN_ID = pluginId;
16
- const Initializer = () => {
16
+ const Initializer = ({ setPlugin }) => {
17
+ const ref = useRef(setPlugin);
18
+ const { get } = useFetchClient();
17
19
  useEffect(() => {
18
- console.log("[magic-sessionmanager] Plugin initialized");
20
+ if (ref.current) {
21
+ ref.current(pluginId);
22
+ }
19
23
  }, []);
24
+ useEffect(() => {
25
+ const HEARTBEAT_INTERVAL = 4 * 60 * 1e3;
26
+ const heartbeat = async () => {
27
+ try {
28
+ await get(`/${pluginId}/license/status`);
29
+ } catch (error) {
30
+ }
31
+ };
32
+ const initialTimeout = setTimeout(heartbeat, 60 * 1e3);
33
+ const interval = setInterval(heartbeat, HEARTBEAT_INTERVAL);
34
+ return () => {
35
+ clearTimeout(initialTimeout);
36
+ clearInterval(interval);
37
+ };
38
+ }, [get]);
20
39
  return null;
21
40
  };
22
41
  const PluginIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 32 32", fill: "currentColor", width: "24", height: "24", children: /* @__PURE__ */ jsx("path", { d: "M26 5H6a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h20a3 3 0 0 0 3-3V8a3 3 0 0 0-3-3M20 27h-8a1 1 0 0 0 0 2h8a1 1 0 0 0 0-2" }) });
@@ -396,7 +415,7 @@ const index = {
396
415
  id: `${pluginId}.plugin.name`,
397
416
  defaultMessage: pluginPkg.strapi.displayName
398
417
  },
399
- Component: () => import("./App-CahdcIEB.mjs")
418
+ Component: () => import("./App-Bgq2FgRY.mjs")
400
419
  });
401
420
  app.createSettingSection(
402
421
  {
@@ -412,7 +431,7 @@ const index = {
412
431
  },
413
432
  id: "upgrade",
414
433
  to: `${pluginId}/upgrade`,
415
- Component: () => import("./UpgradePage-mqr6dLVY.mjs")
434
+ Component: () => import("./UpgradePage-B_2RowKN.mjs")
416
435
  },
417
436
  {
418
437
  intlLabel: {
@@ -421,7 +440,7 @@ const index = {
421
440
  },
422
441
  id: "general",
423
442
  to: `${pluginId}/general`,
424
- Component: () => import("./Settings-DOUUwwxB.mjs")
443
+ Component: () => import("./Settings-B3FaQckW.mjs")
425
444
  },
426
445
  {
427
446
  intlLabel: {
@@ -430,7 +449,7 @@ const index = {
430
449
  },
431
450
  id: "analytics",
432
451
  to: `${pluginId}/analytics`,
433
- Component: () => import("./Analytics-BC4jdzBT.mjs")
452
+ Component: () => import("./Analytics-CYYb-qXQ.mjs")
434
453
  },
435
454
  {
436
455
  intlLabel: {
@@ -439,7 +458,7 @@ const index = {
439
458
  },
440
459
  id: "license",
441
460
  to: `${pluginId}/license`,
442
- Component: () => import("./License-BFx721o7.mjs")
461
+ Component: () => import("./License-CcD3WDcC.mjs")
443
462
  }
444
463
  ]
445
464
  );
@@ -457,7 +476,7 @@ const index = {
457
476
  defaultMessage: "Online Users"
458
477
  },
459
478
  component: async () => {
460
- const component = await import("./OnlineUsersWidget-r2ZgSnok.mjs");
479
+ const component = await import("./OnlineUsersWidget-DHElrP3_.mjs");
461
480
  return component.default;
462
481
  },
463
482
  id: "online-users-widget",
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect } from "react";
2
2
  import { useFetchClient } from "@strapi/strapi/admin";
3
- import { a as pluginId } from "./index-CUSrDKCG.mjs";
3
+ import { a as pluginId } from "./index-Dpdcg2zl.mjs";
4
4
  const useLicense = () => {
5
5
  const { get } = useFetchClient();
6
6
  const [isPremium, setIsPremium] = useState(false);
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  const react = require("react");
3
3
  const admin = require("@strapi/strapi/admin");
4
- const index = require("./index-BuxWeACw.js");
4
+ const index = require("./index-Cf1Wqdeg.js");
5
5
  const useLicense = () => {
6
6
  const { get } = admin.useFetchClient();
7
7
  const [isPremium, setIsPremium] = react.useState(false);
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-BuxWeACw.js");
2
+ const index = require("../_chunks/index-Cf1Wqdeg.js");
3
3
  module.exports = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "../_chunks/index-CUSrDKCG.mjs";
1
+ import { i } from "../_chunks/index-Dpdcg2zl.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -206,7 +206,7 @@ function generateSessionId$1(userId) {
206
206
  const userHash = crypto$1.createHash("sha256").update(userId.toString()).digest("hex").substring(0, 8);
207
207
  return `sess_${timestamp}_${userHash}_${randomBytes}`;
208
208
  }
209
- function hashToken$4(token) {
209
+ function hashToken$3(token) {
210
210
  if (!token) return null;
211
211
  return crypto$1.createHash("sha256").update(token).digest("hex");
212
212
  }
@@ -214,113 +214,63 @@ var encryption = {
214
214
  encryptToken: encryptToken$2,
215
215
  decryptToken: decryptToken$3,
216
216
  generateSessionId: generateSessionId$1,
217
- hashToken: hashToken$4
217
+ hashToken: hashToken$3
218
218
  };
219
219
  const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
220
- const { hashToken: hashToken$3 } = encryption;
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
- }
220
+ const AUTH_PATTERNS = [
221
+ "/auth/",
222
+ // All /api/auth/* endpoints (login, logout, refresh, etc.)
223
+ "/magic-link/",
224
+ // All Magic-Link endpoints
225
+ "/passwordless/",
226
+ // Legacy passwordless endpoints
227
+ "/otp/",
228
+ // OTP endpoints (any plugin)
229
+ "/login",
230
+ // Any login endpoint
231
+ "/register",
232
+ // Any register endpoint
233
+ "/forgot-password",
234
+ // Password reset
235
+ "/reset-password"
236
+ // Password reset
237
+ ];
238
+ function isAuthEndpoint(path) {
239
+ return AUTH_PATTERNS.some((pattern) => path.includes(pattern));
233
240
  }
234
- var lastSeen = ({ strapi: strapi2 }) => {
241
+ var lastSeen = ({ strapi: strapi2, sessionService }) => {
235
242
  return async (ctx, next) => {
236
- const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
237
- if (!currentToken) {
238
- await next();
239
- return;
240
- }
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
- ];
277
- if (skipPaths.some((p) => ctx.path.startsWith(p))) {
278
- await next();
279
- return;
280
- }
281
- if (!ctx.path.startsWith("/api/")) {
243
+ if (isAuthEndpoint(ctx.path)) {
282
244
  await next();
283
245
  return;
284
246
  }
285
- let matchingSession = null;
286
- try {
287
- const currentTokenHash = hashToken$3(currentToken);
288
- matchingSession = await strapi2.documents(SESSION_UID$3).findFirst({
289
- filters: {
290
- tokenHash: currentTokenHash,
291
- isActive: true
292
- },
293
- populate: { user: { fields: ["documentId"] } }
294
- });
295
- if (matchingSession) {
296
- ctx.state.sessionId = matchingSession.documentId;
297
- ctx.state.currentSession = matchingSession;
298
- if (matchingSession.user?.documentId) {
299
- ctx.state.sessionUserId = matchingSession.user.documentId;
247
+ if (ctx.state.user && ctx.state.user.documentId) {
248
+ try {
249
+ const userId = ctx.state.user.documentId;
250
+ const activeSessions = await strapi2.documents(SESSION_UID$3).findMany({
251
+ filters: {
252
+ user: { documentId: userId },
253
+ isActive: true
254
+ },
255
+ limit: 1
256
+ });
257
+ if (!activeSessions || activeSessions.length === 0) {
258
+ strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Request blocked - session terminated or invalid (user: ${userId.substring(0, 8)}...)`);
259
+ return ctx.unauthorized("All sessions have been terminated. Please login again.");
300
260
  }
301
- } else {
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.");
261
+ } catch (err) {
262
+ strapi2.log.debug("[magic-sessionmanager] Error checking active sessions:", err.message);
304
263
  }
305
- } catch (err) {
306
- strapi2.log.debug("[magic-sessionmanager] Error checking session:", err.message);
307
264
  }
308
265
  await next();
309
- if (matchingSession) {
266
+ if (ctx.state.user && ctx.state.user.documentId) {
310
267
  try {
311
- const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
312
- const rateLimit = config2.lastSeenRateLimit || 3e4;
313
- const now = Date.now();
314
- const lastTouch = lastTouchCache.get(matchingSession.documentId) || 0;
315
- if (now - lastTouch > rateLimit) {
316
- lastTouchCache.set(matchingSession.documentId, now);
317
- cleanupOldCacheEntries();
318
- await strapi2.documents(SESSION_UID$3).update({
319
- documentId: matchingSession.documentId,
320
- data: { lastActive: /* @__PURE__ */ new Date() }
321
- });
322
- strapi2.log.debug(`[magic-sessionmanager] [TOUCH] Session ${matchingSession.documentId} activity updated`);
323
- }
268
+ const userId = ctx.state.user.documentId;
269
+ const sessionId = ctx.state.sessionId;
270
+ await sessionService.touch({
271
+ userId,
272
+ sessionId
273
+ });
324
274
  } catch (err) {
325
275
  strapi2.log.debug("[magic-sessionmanager] Error updating lastSeen:", err.message);
326
276
  }
@@ -2484,7 +2434,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2484
2434
  }
2485
2435
  };
2486
2436
  };
2487
- const version = "4.2.14";
2437
+ const version = "4.2.16";
2488
2438
  const require$$2 = {
2489
2439
  version
2490
2440
  };
@@ -202,7 +202,7 @@ function generateSessionId$1(userId) {
202
202
  const userHash = crypto$1.createHash("sha256").update(userId.toString()).digest("hex").substring(0, 8);
203
203
  return `sess_${timestamp}_${userHash}_${randomBytes}`;
204
204
  }
205
- function hashToken$4(token) {
205
+ function hashToken$3(token) {
206
206
  if (!token) return null;
207
207
  return crypto$1.createHash("sha256").update(token).digest("hex");
208
208
  }
@@ -210,113 +210,63 @@ var encryption = {
210
210
  encryptToken: encryptToken$2,
211
211
  decryptToken: decryptToken$3,
212
212
  generateSessionId: generateSessionId$1,
213
- hashToken: hashToken$4
213
+ hashToken: hashToken$3
214
214
  };
215
215
  const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
216
- const { hashToken: hashToken$3 } = encryption;
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
- }
216
+ const AUTH_PATTERNS = [
217
+ "/auth/",
218
+ // All /api/auth/* endpoints (login, logout, refresh, etc.)
219
+ "/magic-link/",
220
+ // All Magic-Link endpoints
221
+ "/passwordless/",
222
+ // Legacy passwordless endpoints
223
+ "/otp/",
224
+ // OTP endpoints (any plugin)
225
+ "/login",
226
+ // Any login endpoint
227
+ "/register",
228
+ // Any register endpoint
229
+ "/forgot-password",
230
+ // Password reset
231
+ "/reset-password"
232
+ // Password reset
233
+ ];
234
+ function isAuthEndpoint(path) {
235
+ return AUTH_PATTERNS.some((pattern) => path.includes(pattern));
229
236
  }
230
- var lastSeen = ({ strapi: strapi2 }) => {
237
+ var lastSeen = ({ strapi: strapi2, sessionService }) => {
231
238
  return async (ctx, next) => {
232
- const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
233
- if (!currentToken) {
234
- await next();
235
- return;
236
- }
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
- ];
273
- if (skipPaths.some((p) => ctx.path.startsWith(p))) {
274
- await next();
275
- return;
276
- }
277
- if (!ctx.path.startsWith("/api/")) {
239
+ if (isAuthEndpoint(ctx.path)) {
278
240
  await next();
279
241
  return;
280
242
  }
281
- let matchingSession = null;
282
- try {
283
- const currentTokenHash = hashToken$3(currentToken);
284
- matchingSession = await strapi2.documents(SESSION_UID$3).findFirst({
285
- filters: {
286
- tokenHash: currentTokenHash,
287
- isActive: true
288
- },
289
- populate: { user: { fields: ["documentId"] } }
290
- });
291
- if (matchingSession) {
292
- ctx.state.sessionId = matchingSession.documentId;
293
- ctx.state.currentSession = matchingSession;
294
- if (matchingSession.user?.documentId) {
295
- ctx.state.sessionUserId = matchingSession.user.documentId;
243
+ if (ctx.state.user && ctx.state.user.documentId) {
244
+ try {
245
+ const userId = ctx.state.user.documentId;
246
+ const activeSessions = await strapi2.documents(SESSION_UID$3).findMany({
247
+ filters: {
248
+ user: { documentId: userId },
249
+ isActive: true
250
+ },
251
+ limit: 1
252
+ });
253
+ if (!activeSessions || activeSessions.length === 0) {
254
+ strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Request blocked - session terminated or invalid (user: ${userId.substring(0, 8)}...)`);
255
+ return ctx.unauthorized("All sessions have been terminated. Please login again.");
296
256
  }
297
- } else {
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.");
257
+ } catch (err) {
258
+ strapi2.log.debug("[magic-sessionmanager] Error checking active sessions:", err.message);
300
259
  }
301
- } catch (err) {
302
- strapi2.log.debug("[magic-sessionmanager] Error checking session:", err.message);
303
260
  }
304
261
  await next();
305
- if (matchingSession) {
262
+ if (ctx.state.user && ctx.state.user.documentId) {
306
263
  try {
307
- const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
308
- const rateLimit = config2.lastSeenRateLimit || 3e4;
309
- const now = Date.now();
310
- const lastTouch = lastTouchCache.get(matchingSession.documentId) || 0;
311
- if (now - lastTouch > rateLimit) {
312
- lastTouchCache.set(matchingSession.documentId, now);
313
- cleanupOldCacheEntries();
314
- await strapi2.documents(SESSION_UID$3).update({
315
- documentId: matchingSession.documentId,
316
- data: { lastActive: /* @__PURE__ */ new Date() }
317
- });
318
- strapi2.log.debug(`[magic-sessionmanager] [TOUCH] Session ${matchingSession.documentId} activity updated`);
319
- }
264
+ const userId = ctx.state.user.documentId;
265
+ const sessionId = ctx.state.sessionId;
266
+ await sessionService.touch({
267
+ userId,
268
+ sessionId
269
+ });
320
270
  } catch (err) {
321
271
  strapi2.log.debug("[magic-sessionmanager] Error updating lastSeen:", err.message);
322
272
  }
@@ -2480,7 +2430,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2480
2430
  }
2481
2431
  };
2482
2432
  };
2483
- const version = "4.2.14";
2433
+ const version = "4.2.16";
2484
2434
  const require$$2 = {
2485
2435
  version
2486
2436
  };
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.2.15",
2
+ "version": "4.3.0",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",