strapi-plugin-magic-sessionmanager 4.0.0 → 4.0.2

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.
Files changed (68) hide show
  1. package/README.md +2 -2
  2. package/admin/src/components/LicenseGuard.jsx +6 -6
  3. package/admin/src/components/OnlineUsersWidget.jsx +11 -7
  4. package/admin/src/components/SessionDetailModal.jsx +45 -41
  5. package/admin/src/components/SessionInfoCard.jsx +3 -3
  6. package/admin/src/components/SessionInfoPanel.jsx +31 -21
  7. package/admin/src/index.js +9 -0
  8. package/admin/src/pages/Analytics.jsx +2 -2
  9. package/admin/src/pages/HomePage.jsx +129 -165
  10. package/admin/src/pages/License.jsx +5 -5
  11. package/admin/src/pages/Settings.jsx +148 -144
  12. package/admin/src/pages/SettingsNew.jsx +21 -21
  13. package/admin/src/pages/UpgradePage.jsx +448 -0
  14. package/admin/src/pluginId.js +1 -0
  15. package/admin/src/translations/de.json +294 -15
  16. package/admin/src/translations/en.json +293 -14
  17. package/admin/src/translations/es.json +284 -18
  18. package/admin/src/translations/fr.json +284 -18
  19. package/admin/src/translations/pt.json +284 -18
  20. package/admin/src/utils/parseUserAgent.js +6 -6
  21. package/admin/src/utils/theme.js +85 -0
  22. package/dist/_chunks/{Analytics-mYu_uGwU.mjs → Analytics-DTE_zmRV.mjs} +4 -4
  23. package/dist/_chunks/{Analytics-ioaeEh-E.js → Analytics-lw_JaOVy.js} +4 -4
  24. package/dist/_chunks/{App-DdnUYWbC.js → App-DDKYCjKw.js} +221 -216
  25. package/dist/_chunks/{App-BXpIS12l.mjs → App-DJW1ZNl5.mjs} +221 -216
  26. package/dist/_chunks/{License-C03C2j9P.mjs → License-DaOFuImm.mjs} +6 -10
  27. package/dist/_chunks/{License-DZYrOgcx.js → License-Tk-6UfPl.js} +6 -10
  28. package/dist/_chunks/{OnlineUsersWidget-B8JS1xZu.js → OnlineUsersWidget-C1qTpsws.js} +11 -7
  29. package/dist/_chunks/{OnlineUsersWidget-ArMl0nen.mjs → OnlineUsersWidget-CADphbXG.mjs} +11 -7
  30. package/dist/_chunks/{Settings-0ocB3qHk.mjs → Settings-C9xvckgq.mjs} +200 -188
  31. package/dist/_chunks/{Settings-C6_CqpCC.js → Settings-DyEAuTNQ.js} +200 -188
  32. package/dist/_chunks/UpgradePage-Dssk8A0Z.js +354 -0
  33. package/dist/_chunks/UpgradePage-cINvE9zY.mjs +352 -0
  34. package/dist/_chunks/de-CDA1V0rF.mjs +292 -0
  35. package/dist/_chunks/de-I-Q-pWqu.js +292 -0
  36. package/dist/_chunks/en-Bd7_h-4e.js +292 -0
  37. package/dist/_chunks/en-DzmOCyzQ.mjs +292 -0
  38. package/dist/_chunks/es-BcAx18XG.js +277 -0
  39. package/dist/_chunks/es-Cx-SN6qV.mjs +277 -0
  40. package/dist/_chunks/fr-DCzYMuJ-.js +277 -0
  41. package/dist/_chunks/fr-DXlXE5Eo.mjs +277 -0
  42. package/dist/_chunks/{index-DC8Y0qxx.js → index-CWcvrfXc.js} +52 -49
  43. package/dist/_chunks/{index-DBRS3kt5.mjs → index-DQO9bNP7.mjs} +52 -49
  44. package/dist/_chunks/pt-21-MAb72.js +277 -0
  45. package/dist/_chunks/pt-zsdTSjba.mjs +277 -0
  46. package/dist/_chunks/{useLicense-qgGfMvse.js → useLicense-DtvJOszr.js} +1 -1
  47. package/dist/_chunks/{useLicense-DSLL9n3Y.mjs → useLicense-DxbD4Wf8.mjs} +1 -1
  48. package/dist/admin/index.js +1 -1
  49. package/dist/admin/index.mjs +1 -1
  50. package/dist/server/index.js +142 -33
  51. package/dist/server/index.mjs +142 -33
  52. package/package.json +1 -1
  53. package/server/src/bootstrap.js +76 -4
  54. package/server/src/controllers/session.js +59 -9
  55. package/server/src/middlewares/last-seen.js +5 -4
  56. package/server/src/routes/content-api.js +11 -2
  57. package/server/src/services/notifications.js +10 -10
  58. package/server/src/services/session.js +24 -4
  59. package/dist/_chunks/de-BxFx1pwE.js +0 -23
  60. package/dist/_chunks/de-CdO3s01z.mjs +0 -23
  61. package/dist/_chunks/en-CsPpPJL3.mjs +0 -23
  62. package/dist/_chunks/en-RqmpDHdS.js +0 -23
  63. package/dist/_chunks/es-CuLHazN1.js +0 -23
  64. package/dist/_chunks/es-Dkmjhy9c.mjs +0 -23
  65. package/dist/_chunks/fr-BAJp2yhI.js +0 -23
  66. package/dist/_chunks/fr-Bssg_3UF.mjs +0 -23
  67. package/dist/_chunks/pt-BAP9cKs3.js +0 -23
  68. package/dist/_chunks/pt-BVNoNcuY.mjs +0 -23
@@ -148,9 +148,9 @@ var encryption = {
148
148
  const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
149
149
  var lastSeen = ({ strapi: strapi2, sessionService }) => {
150
150
  return async (ctx, next) => {
151
- if (ctx.state.user && ctx.state.user.id) {
151
+ if (ctx.state.user && ctx.state.user.documentId) {
152
152
  try {
153
- const userId = ctx.state.user.id;
153
+ const userId = ctx.state.user.documentId;
154
154
  const activeSessions = await strapi2.documents(SESSION_UID$3).findMany({
155
155
  filters: {
156
156
  user: { documentId: userId },
@@ -167,9 +167,9 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
167
167
  }
168
168
  }
169
169
  await next();
170
- if (ctx.state.user && ctx.state.user.id) {
170
+ if (ctx.state.user && ctx.state.user.documentId) {
171
171
  try {
172
- const userId = ctx.state.user.id;
172
+ const userId = ctx.state.user.documentId;
173
173
  const sessionId = ctx.state.sessionId;
174
174
  await sessionService.touch({
175
175
  userId,
@@ -184,6 +184,7 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
184
184
  const getClientIp = getClientIp_1;
185
185
  const { encryptToken: encryptToken$1, decryptToken: decryptToken$2 } = encryption;
186
186
  const SESSION_UID$2 = "plugin::magic-sessionmanager.session";
187
+ const USER_UID$2 = "plugin::users-permissions.user";
187
188
  var bootstrap$1 = async ({ strapi: strapi2 }) => {
188
189
  strapi2.log.info("[magic-sessionmanager] [START] Bootstrap starting...");
189
190
  try {
@@ -291,7 +292,7 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
291
292
  const user = ctx.body.user;
292
293
  const ip = getClientIp(ctx);
293
294
  const userAgent = ctx.request.headers?.["user-agent"] || ctx.request.header?.["user-agent"] || "unknown";
294
- strapi2.log.info(`[magic-sessionmanager] [CHECK] Login detected! User: ${user.id} (${user.email || user.username}) from IP: ${ip}`);
295
+ strapi2.log.info(`[magic-sessionmanager] [CHECK] Login detected! User: ${user.documentId || user.id} (${user.email || user.username}) from IP: ${ip}`);
295
296
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
296
297
  let shouldBlock = false;
297
298
  let blockReason = "";
@@ -344,8 +345,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
344
345
  };
345
346
  return;
346
347
  }
348
+ let userDocId = user.documentId;
349
+ if (!userDocId && user.id) {
350
+ const fullUser = await strapi2.entityService.findOne(USER_UID$2, user.id);
351
+ userDocId = fullUser?.documentId || user.id;
352
+ }
347
353
  const newSession = await sessionService.createSession({
348
- userId: user.id,
354
+ userId: userDocId,
349
355
  ip,
350
356
  userAgent,
351
357
  token: ctx.body.jwt,
@@ -353,7 +359,7 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
353
359
  refreshToken: ctx.body.refreshToken
354
360
  // Store Refresh Token (encrypted) if exists
355
361
  });
356
- strapi2.log.info(`[magic-sessionmanager] [SUCCESS] Session created for user ${user.id} (IP: ${ip})`);
362
+ strapi2.log.info(`[magic-sessionmanager] [SUCCESS] Session created for user ${userDocId} (IP: ${ip})`);
357
363
  if (geoData && (config2.enableEmailAlerts || config2.enableWebhooks)) {
358
364
  try {
359
365
  const notificationService = strapi2.plugin("magic-sessionmanager").service("notifications");
@@ -479,12 +485,55 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
479
485
  lastSeen({ strapi: strapi2, sessionService })
480
486
  );
481
487
  strapi2.log.info("[magic-sessionmanager] [SUCCESS] LastSeen middleware mounted");
488
+ await ensureContentApiPermissions(strapi2);
482
489
  strapi2.log.info("[magic-sessionmanager] [SUCCESS] Bootstrap complete");
483
490
  strapi2.log.info("[magic-sessionmanager] [READY] Session Manager ready! Sessions stored in plugin::magic-sessionmanager.session");
484
491
  } catch (err) {
485
492
  strapi2.log.error("[magic-sessionmanager] [ERROR] Bootstrap error:", err);
486
493
  }
487
494
  };
495
+ async function ensureContentApiPermissions(strapi2) {
496
+ try {
497
+ const authenticatedRole = await strapi2.query("plugin::users-permissions.role").findOne({
498
+ where: { type: "authenticated" }
499
+ });
500
+ if (!authenticatedRole) {
501
+ strapi2.log.warn("[magic-sessionmanager] Authenticated role not found - skipping permission setup");
502
+ return;
503
+ }
504
+ const requiredActions = [
505
+ "plugin::magic-sessionmanager.session.logout",
506
+ "plugin::magic-sessionmanager.session.logoutAll",
507
+ "plugin::magic-sessionmanager.session.getOwnSessions",
508
+ "plugin::magic-sessionmanager.session.getUserSessions"
509
+ ];
510
+ const existingPermissions = await strapi2.query("plugin::users-permissions.permission").findMany({
511
+ where: {
512
+ role: authenticatedRole.id,
513
+ action: { $in: requiredActions }
514
+ }
515
+ });
516
+ const existingActions = existingPermissions.map((p) => p.action);
517
+ const missingActions = requiredActions.filter((action) => !existingActions.includes(action));
518
+ if (missingActions.length === 0) {
519
+ strapi2.log.debug("[magic-sessionmanager] Content-API permissions already configured");
520
+ return;
521
+ }
522
+ for (const action of missingActions) {
523
+ await strapi2.query("plugin::users-permissions.permission").create({
524
+ data: {
525
+ action,
526
+ role: authenticatedRole.id
527
+ }
528
+ });
529
+ strapi2.log.info(`[magic-sessionmanager] [PERMISSION] Enabled ${action} for authenticated users`);
530
+ }
531
+ strapi2.log.info("[magic-sessionmanager] [SUCCESS] Content-API permissions configured for authenticated users");
532
+ } catch (err) {
533
+ strapi2.log.warn("[magic-sessionmanager] Could not auto-configure permissions:", err.message);
534
+ strapi2.log.warn("[magic-sessionmanager] Please manually enable plugin permissions in Settings > Users & Permissions > Roles > Authenticated");
535
+ }
536
+ }
488
537
  var destroy$1 = async ({ strapi: strapi2 }) => {
489
538
  if (strapi2.licenseGuard && strapi2.licenseGuard.pingInterval) {
490
539
  clearInterval(strapi2.licenseGuard.pingInterval);
@@ -637,13 +686,22 @@ var contentApi$1 = {
637
686
  // ============================================================
638
687
  // SESSION QUERIES
639
688
  // ============================================================
689
+ {
690
+ method: "GET",
691
+ path: "/my-sessions",
692
+ handler: "session.getOwnSessions",
693
+ config: {
694
+ auth: { strategies: ["users-permissions"] },
695
+ description: "Get own sessions (automatically uses authenticated user)"
696
+ }
697
+ },
640
698
  {
641
699
  method: "GET",
642
700
  path: "/user/:userId/sessions",
643
701
  handler: "session.getUserSessions",
644
702
  config: {
645
703
  auth: { strategies: ["users-permissions"] },
646
- description: "Get own sessions (controller validates user can only see own sessions)"
704
+ description: "Get sessions by userId (validates user can only see own sessions)"
647
705
  }
648
706
  }
649
707
  ]
@@ -803,7 +861,7 @@ var routes$1 = {
803
861
  };
804
862
  const { decryptToken: decryptToken$1 } = encryption;
805
863
  const SESSION_UID$1 = "plugin::magic-sessionmanager.session";
806
- const USER_UID = "plugin::users-permissions.user";
864
+ const USER_UID$1 = "plugin::users-permissions.user";
807
865
  var session$3 = {
808
866
  /**
809
867
  * Get ALL sessions (active + inactive) - Admin only
@@ -843,6 +901,30 @@ var session$3 = {
843
901
  ctx.throw(500, "Error fetching active sessions");
844
902
  }
845
903
  },
904
+ /**
905
+ * Get own sessions (authenticated user)
906
+ * GET /api/magic-sessionmanager/my-sessions
907
+ * Automatically uses the authenticated user's documentId
908
+ */
909
+ async getOwnSessions(ctx) {
910
+ try {
911
+ const userId = ctx.state.user?.documentId;
912
+ if (!userId) {
913
+ return ctx.throw(401, "Unauthorized");
914
+ }
915
+ const sessionService = strapi.plugin("magic-sessionmanager").service("session");
916
+ const sessions = await sessionService.getUserSessions(userId);
917
+ ctx.body = {
918
+ data: sessions,
919
+ meta: {
920
+ count: sessions.length
921
+ }
922
+ };
923
+ } catch (err) {
924
+ strapi.log.error("[magic-sessionmanager] Error fetching own sessions:", err);
925
+ ctx.throw(500, "Error fetching sessions");
926
+ }
927
+ },
846
928
  /**
847
929
  * Get user's sessions
848
930
  * GET /magic-sessionmanager/user/:userId/sessions (Admin API)
@@ -853,9 +935,9 @@ var session$3 = {
853
935
  try {
854
936
  const { userId } = ctx.params;
855
937
  const isAdminRequest = ctx.state.userAbility || ctx.state.admin;
856
- const requestingUserId = ctx.state.user?.id;
857
- if (!isAdminRequest && requestingUserId && String(requestingUserId) !== String(userId)) {
858
- strapi.log.warn(`[magic-sessionmanager] Security: User ${requestingUserId} tried to access sessions of user ${userId}`);
938
+ const requestingUserDocId = ctx.state.user?.documentId;
939
+ if (!isAdminRequest && requestingUserDocId && String(requestingUserDocId) !== String(userId)) {
940
+ strapi.log.warn(`[magic-sessionmanager] Security: User ${requestingUserDocId} tried to access sessions of user ${userId}`);
859
941
  return ctx.forbidden("You can only access your own sessions");
860
942
  }
861
943
  const sessionService = strapi.plugin("magic-sessionmanager").service("session");
@@ -876,7 +958,7 @@ var session$3 = {
876
958
  */
877
959
  async logout(ctx) {
878
960
  try {
879
- const userId = ctx.state.user?.id;
961
+ const userId = ctx.state.user?.documentId;
880
962
  const token = ctx.request.headers.authorization?.replace("Bearer ", "");
881
963
  if (!userId) {
882
964
  return ctx.throw(401, "Unauthorized");
@@ -915,7 +997,7 @@ var session$3 = {
915
997
  */
916
998
  async logoutAll(ctx) {
917
999
  try {
918
- const userId = ctx.state.user?.id;
1000
+ const userId = ctx.state.user?.documentId;
919
1001
  if (!userId) {
920
1002
  return ctx.throw(401, "Unauthorized");
921
1003
  }
@@ -1055,24 +1137,34 @@ var session$3 = {
1055
1137
  /**
1056
1138
  * Toggle user blocked status
1057
1139
  * POST /magic-sessionmanager/user/:userId/toggle-block
1140
+ * Supports both numeric id (from Content Manager) and documentId
1058
1141
  */
1059
1142
  async toggleUserBlock(ctx) {
1060
1143
  try {
1061
1144
  const { userId } = ctx.params;
1062
- const user = await strapi.documents(USER_UID).findOne({ documentId: userId });
1145
+ let userDocumentId = userId;
1146
+ let user = null;
1147
+ user = await strapi.documents(USER_UID$1).findOne({ documentId: userId });
1148
+ if (!user && !isNaN(userId)) {
1149
+ const numericUser = await strapi.entityService.findOne(USER_UID$1, parseInt(userId, 10));
1150
+ if (numericUser) {
1151
+ userDocumentId = numericUser.documentId;
1152
+ user = numericUser;
1153
+ }
1154
+ }
1063
1155
  if (!user) {
1064
1156
  return ctx.throw(404, "User not found");
1065
1157
  }
1066
1158
  const newBlockedStatus = !user.blocked;
1067
- await strapi.documents(USER_UID).update({
1068
- documentId: userId,
1159
+ await strapi.documents(USER_UID$1).update({
1160
+ documentId: userDocumentId,
1069
1161
  data: {
1070
1162
  blocked: newBlockedStatus
1071
1163
  }
1072
1164
  });
1073
1165
  if (newBlockedStatus) {
1074
1166
  const sessionService = strapi.plugin("magic-sessionmanager").service("session");
1075
- await sessionService.terminateSession({ userId });
1167
+ await sessionService.terminateSession({ userId: userDocumentId });
1076
1168
  }
1077
1169
  ctx.body = {
1078
1170
  message: `User ${newBlockedStatus ? "blocked" : "unblocked"} successfully`,
@@ -1401,6 +1493,7 @@ var controllers$1 = {
1401
1493
  };
1402
1494
  const { encryptToken, decryptToken, generateSessionId } = encryption;
1403
1495
  const SESSION_UID = "plugin::magic-sessionmanager.session";
1496
+ const USER_UID = "plugin::users-permissions.user";
1404
1497
  var session$1 = ({ strapi: strapi2 }) => ({
1405
1498
  /**
1406
1499
  * Create a new session record
@@ -1439,6 +1532,7 @@ var session$1 = ({ strapi: strapi2 }) => ({
1439
1532
  },
1440
1533
  /**
1441
1534
  * Terminate a session or all sessions for a user
1535
+ * Supports both numeric id (legacy) and documentId (Strapi v5)
1442
1536
  * @param {Object} params - { sessionId | userId }
1443
1537
  * @returns {Promise<void>}
1444
1538
  */
@@ -1455,9 +1549,16 @@ var session$1 = ({ strapi: strapi2 }) => ({
1455
1549
  });
1456
1550
  strapi2.log.info(`[magic-sessionmanager] Session ${sessionId} terminated`);
1457
1551
  } else if (userId) {
1552
+ let userDocumentId = userId;
1553
+ if (!isNaN(userId)) {
1554
+ const user = await strapi2.entityService.findOne(USER_UID, parseInt(userId, 10));
1555
+ if (user) {
1556
+ userDocumentId = user.documentId;
1557
+ }
1558
+ }
1458
1559
  const activeSessions = await strapi2.documents(SESSION_UID).findMany({
1459
1560
  filters: {
1460
- user: { documentId: userId },
1561
+ user: { documentId: userDocumentId },
1461
1562
  // Deep filtering syntax
1462
1563
  isActive: true
1463
1564
  }
@@ -1471,7 +1572,7 @@ var session$1 = ({ strapi: strapi2 }) => ({
1471
1572
  }
1472
1573
  });
1473
1574
  }
1474
- strapi2.log.info(`[magic-sessionmanager] All sessions terminated for user ${userId}`);
1575
+ strapi2.log.info(`[magic-sessionmanager] All sessions terminated for user ${userDocumentId}`);
1475
1576
  }
1476
1577
  } catch (err) {
1477
1578
  strapi2.log.error("[magic-sessionmanager] Error terminating session:", err);
@@ -1543,13 +1644,21 @@ var session$1 = ({ strapi: strapi2 }) => ({
1543
1644
  },
1544
1645
  /**
1545
1646
  * Get all sessions for a specific user
1546
- * @param {number} userId
1647
+ * Supports both numeric id (legacy) and documentId (Strapi v5)
1648
+ * @param {string|number} userId - User documentId or numeric id
1547
1649
  * @returns {Promise<Array>} User's sessions with accurate online status
1548
1650
  */
1549
1651
  async getUserSessions(userId) {
1550
1652
  try {
1653
+ let userDocumentId = userId;
1654
+ if (!isNaN(userId)) {
1655
+ const user = await strapi2.entityService.findOne(USER_UID, parseInt(userId, 10));
1656
+ if (user) {
1657
+ userDocumentId = user.documentId;
1658
+ }
1659
+ }
1551
1660
  const sessions = await strapi2.documents(SESSION_UID).findMany({
1552
- filters: { user: { documentId: userId } },
1661
+ filters: { user: { documentId: userDocumentId } },
1553
1662
  sort: { loginTime: "desc" }
1554
1663
  });
1555
1664
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
@@ -1677,7 +1786,7 @@ var session$1 = ({ strapi: strapi2 }) => ({
1677
1786
  }
1678
1787
  }
1679
1788
  });
1680
- const version = "3.7.0";
1789
+ const version = "4.0.1";
1681
1790
  const require$$2 = {
1682
1791
  version
1683
1792
  };
@@ -2108,12 +2217,12 @@ var notifications$1 = ({ strapi: strapi2 }) => ({
2108
2217
  strapi2.log.debug("[magic-sessionmanager/notifications] Using default fallback templates");
2109
2218
  return {
2110
2219
  suspiciousLogin: {
2111
- subject: "🚨 Suspicious Login Alert - Session Manager",
2220
+ subject: "[ALERT] Suspicious Login Alert - Session Manager",
2112
2221
  html: `
2113
2222
  <html>
2114
2223
  <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
2115
2224
  <div style="max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f9fafb; border-radius: 10px;">
2116
- <h2 style="color: #dc2626;">🚨 Suspicious Login Detected</h2>
2225
+ <h2 style="color: #dc2626;">[ALERT] Suspicious Login Detected</h2>
2117
2226
  <p>A potentially suspicious login was detected for your account.</p>
2118
2227
 
2119
2228
  <div style="background: white; padding: 15px; border-radius: 8px; margin: 20px 0;">
@@ -2148,7 +2257,7 @@ var notifications$1 = ({ strapi: strapi2 }) => ({
2148
2257
  </div>
2149
2258
  </body>
2150
2259
  </html>`,
2151
- text: `🚨 Suspicious Login Detected
2260
+ text: `[ALERT] Suspicious Login Detected
2152
2261
 
2153
2262
  A potentially suspicious login was detected for your account.
2154
2263
 
@@ -2319,10 +2428,10 @@ VPN: {{reason.isVpn}}, Proxy: {{reason.isProxy}}`
2319
2428
  title: this.getEventTitle(event),
2320
2429
  color: this.getEventColor(event),
2321
2430
  fields: [
2322
- { name: "👤 User", value: `${user.email}
2431
+ { name: "User", value: `${user.email}
2323
2432
  ${user.username || "N/A"}`, inline: true },
2324
- { name: "🌐 IP", value: session2.ipAddress, inline: true },
2325
- { name: "📅 Time", value: new Date(session2.loginTime).toLocaleString(), inline: false }
2433
+ { name: "IP", value: session2.ipAddress, inline: true },
2434
+ { name: "Time", value: new Date(session2.loginTime).toLocaleString(), inline: false }
2326
2435
  ],
2327
2436
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2328
2437
  footer: { text: "Magic Session Manager" }
@@ -2350,11 +2459,11 @@ Score: ${geoData.securityScore}/100`,
2350
2459
  },
2351
2460
  getEventTitle(event) {
2352
2461
  const titles = {
2353
- "login.suspicious": "🚨 Suspicious Login",
2462
+ "login.suspicious": "[ALERT] Suspicious Login",
2354
2463
  "login.new_location": "[LOCATION] New Location Login",
2355
- "login.vpn": "🔴 VPN Login Detected",
2356
- "login.threat": " Threat IP Login",
2357
- "session.terminated": "🔴 Session Terminated"
2464
+ "login.vpn": "[WARNING] VPN Login Detected",
2465
+ "login.threat": "[THREAT] Threat IP Login",
2466
+ "session.terminated": "[INFO] Session Terminated"
2358
2467
  };
2359
2468
  return titles[event] || "[STATS] Session Event";
2360
2469
  },
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.0.0",
2
+ "version": "4.0.2",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",
@@ -163,7 +163,8 @@ module.exports = async ({ strapi }) => {
163
163
  const ip = getClientIp(ctx);
164
164
  const userAgent = ctx.request.headers?.['user-agent'] || ctx.request.header?.['user-agent'] || 'unknown';
165
165
 
166
- strapi.log.info(`[magic-sessionmanager] [CHECK] Login detected! User: ${user.id} (${user.email || user.username}) from IP: ${ip}`);
166
+ // Strapi v5: Use documentId for session creation
167
+ strapi.log.info(`[magic-sessionmanager] [CHECK] Login detected! User: ${user.documentId || user.id} (${user.email || user.username}) from IP: ${ip}`);
167
168
 
168
169
  // Get config
169
170
  const config = strapi.config.get('plugin::magic-sessionmanager') || {};
@@ -235,16 +236,23 @@ module.exports = async ({ strapi }) => {
235
236
  return; // Stop here
236
237
  }
237
238
 
238
- // Create a new session
239
+ // Create a new session (Strapi v5: Use documentId instead of numeric id)
240
+ // If login response doesn't include documentId, fetch it from DB
241
+ let userDocId = user.documentId;
242
+ if (!userDocId && user.id) {
243
+ const fullUser = await strapi.entityService.findOne(USER_UID, user.id);
244
+ userDocId = fullUser?.documentId || user.id;
245
+ }
246
+
239
247
  const newSession = await sessionService.createSession({
240
- userId: user.id,
248
+ userId: userDocId,
241
249
  ip,
242
250
  userAgent,
243
251
  token: ctx.body.jwt, // Store Access Token (encrypted)
244
252
  refreshToken: ctx.body.refreshToken, // Store Refresh Token (encrypted) if exists
245
253
  });
246
254
 
247
- strapi.log.info(`[magic-sessionmanager] [SUCCESS] Session created for user ${user.id} (IP: ${ip})`);
255
+ strapi.log.info(`[magic-sessionmanager] [SUCCESS] Session created for user ${userDocId} (IP: ${ip})`);
248
256
 
249
257
  // Advanced: Send notifications
250
258
  if (geoData && (config.enableEmailAlerts || config.enableWebhooks)) {
@@ -407,6 +415,10 @@ module.exports = async ({ strapi }) => {
407
415
  );
408
416
 
409
417
  strapi.log.info('[magic-sessionmanager] [SUCCESS] LastSeen middleware mounted');
418
+
419
+ // Auto-enable Content-API permissions for authenticated users
420
+ await ensureContentApiPermissions(strapi);
421
+
410
422
  strapi.log.info('[magic-sessionmanager] [SUCCESS] Bootstrap complete');
411
423
  strapi.log.info('[magic-sessionmanager] [READY] Session Manager ready! Sessions stored in plugin::magic-sessionmanager.session');
412
424
 
@@ -414,3 +426,63 @@ module.exports = async ({ strapi }) => {
414
426
  strapi.log.error('[magic-sessionmanager] [ERROR] Bootstrap error:', err);
415
427
  }
416
428
  };
429
+
430
+ /**
431
+ * Auto-enable Content-API permissions for authenticated users
432
+ * This ensures plugin endpoints are accessible after installation
433
+ * @param {object} strapi - Strapi instance
434
+ */
435
+ async function ensureContentApiPermissions(strapi) {
436
+ try {
437
+ // Get the authenticated role
438
+ const authenticatedRole = await strapi.query('plugin::users-permissions.role').findOne({
439
+ where: { type: 'authenticated' },
440
+ });
441
+
442
+ if (!authenticatedRole) {
443
+ strapi.log.warn('[magic-sessionmanager] Authenticated role not found - skipping permission setup');
444
+ return;
445
+ }
446
+
447
+ // Content-API actions that should be enabled for authenticated users
448
+ const requiredActions = [
449
+ 'plugin::magic-sessionmanager.session.logout',
450
+ 'plugin::magic-sessionmanager.session.logoutAll',
451
+ 'plugin::magic-sessionmanager.session.getOwnSessions',
452
+ 'plugin::magic-sessionmanager.session.getUserSessions',
453
+ ];
454
+
455
+ // Get existing permissions for this role
456
+ const existingPermissions = await strapi.query('plugin::users-permissions.permission').findMany({
457
+ where: {
458
+ role: authenticatedRole.id,
459
+ action: { $in: requiredActions },
460
+ },
461
+ });
462
+
463
+ // Find which actions are missing
464
+ const existingActions = existingPermissions.map(p => p.action);
465
+ const missingActions = requiredActions.filter(action => !existingActions.includes(action));
466
+
467
+ if (missingActions.length === 0) {
468
+ strapi.log.debug('[magic-sessionmanager] Content-API permissions already configured');
469
+ return;
470
+ }
471
+
472
+ // Create missing permissions
473
+ for (const action of missingActions) {
474
+ await strapi.query('plugin::users-permissions.permission').create({
475
+ data: {
476
+ action,
477
+ role: authenticatedRole.id,
478
+ },
479
+ });
480
+ strapi.log.info(`[magic-sessionmanager] [PERMISSION] Enabled ${action} for authenticated users`);
481
+ }
482
+
483
+ strapi.log.info('[magic-sessionmanager] [SUCCESS] Content-API permissions configured for authenticated users');
484
+ } catch (err) {
485
+ strapi.log.warn('[magic-sessionmanager] Could not auto-configure permissions:', err.message);
486
+ strapi.log.warn('[magic-sessionmanager] Please manually enable plugin permissions in Settings > Users & Permissions > Roles > Authenticated');
487
+ }
488
+ }
@@ -58,6 +58,38 @@ module.exports = {
58
58
  }
59
59
  },
60
60
 
61
+ /**
62
+ * Get own sessions (authenticated user)
63
+ * GET /api/magic-sessionmanager/my-sessions
64
+ * Automatically uses the authenticated user's documentId
65
+ */
66
+ async getOwnSessions(ctx) {
67
+ try {
68
+ // Strapi v5: Use documentId from authenticated user
69
+ const userId = ctx.state.user?.documentId;
70
+
71
+ if (!userId) {
72
+ return ctx.throw(401, 'Unauthorized');
73
+ }
74
+
75
+ const sessionService = strapi
76
+ .plugin('magic-sessionmanager')
77
+ .service('session');
78
+
79
+ const sessions = await sessionService.getUserSessions(userId);
80
+
81
+ ctx.body = {
82
+ data: sessions,
83
+ meta: {
84
+ count: sessions.length,
85
+ },
86
+ };
87
+ } catch (err) {
88
+ strapi.log.error('[magic-sessionmanager] Error fetching own sessions:', err);
89
+ ctx.throw(500, 'Error fetching sessions');
90
+ }
91
+ },
92
+
61
93
  /**
62
94
  * Get user's sessions
63
95
  * GET /magic-sessionmanager/user/:userId/sessions (Admin API)
@@ -70,12 +102,13 @@ module.exports = {
70
102
 
71
103
  // Check if this is an admin request
72
104
  const isAdminRequest = ctx.state.userAbility || ctx.state.admin;
73
- const requestingUserId = ctx.state.user?.id;
105
+ // Strapi v5: Use documentId instead of numeric id
106
+ const requestingUserDocId = ctx.state.user?.documentId;
74
107
 
75
108
  // SECURITY CHECK: Content API users can only see their own sessions
76
109
  // Admins can see any user's sessions
77
- if (!isAdminRequest && requestingUserId && String(requestingUserId) !== String(userId)) {
78
- strapi.log.warn(`[magic-sessionmanager] Security: User ${requestingUserId} tried to access sessions of user ${userId}`);
110
+ if (!isAdminRequest && requestingUserDocId && String(requestingUserDocId) !== String(userId)) {
111
+ strapi.log.warn(`[magic-sessionmanager] Security: User ${requestingUserDocId} tried to access sessions of user ${userId}`);
79
112
  return ctx.forbidden('You can only access your own sessions');
80
113
  }
81
114
 
@@ -102,7 +135,8 @@ module.exports = {
102
135
  */
103
136
  async logout(ctx) {
104
137
  try {
105
- const userId = ctx.state.user?.id;
138
+ // Strapi v5: Use documentId instead of numeric id
139
+ const userId = ctx.state.user?.documentId;
106
140
  const token = ctx.request.headers.authorization?.replace('Bearer ', '');
107
141
 
108
142
  if (!userId) {
@@ -153,7 +187,8 @@ module.exports = {
153
187
  */
154
188
  async logoutAll(ctx) {
155
189
  try {
156
- const userId = ctx.state.user?.id;
190
+ // Strapi v5: Use documentId instead of numeric id
191
+ const userId = ctx.state.user?.documentId;
157
192
 
158
193
  if (!userId) {
159
194
  return ctx.throw(401, 'Unauthorized');
@@ -340,13 +375,28 @@ module.exports = {
340
375
  /**
341
376
  * Toggle user blocked status
342
377
  * POST /magic-sessionmanager/user/:userId/toggle-block
378
+ * Supports both numeric id (from Content Manager) and documentId
343
379
  */
344
380
  async toggleUserBlock(ctx) {
345
381
  try {
346
382
  const { userId } = ctx.params;
347
383
 
348
- // Get current user status
349
- const user = await strapi.documents(USER_UID).findOne({ documentId: userId });
384
+ // Strapi v5: userId from params could be numeric id or documentId
385
+ // If numeric, look up the documentId first using entityService (fallback)
386
+ let userDocumentId = userId;
387
+ let user = null;
388
+
389
+ // Try to find by documentId first (preferred)
390
+ user = await strapi.documents(USER_UID).findOne({ documentId: userId });
391
+
392
+ // If not found, try numeric id lookup via entityService (fallback for Content Manager)
393
+ if (!user && !isNaN(userId)) {
394
+ const numericUser = await strapi.entityService.findOne(USER_UID, parseInt(userId, 10));
395
+ if (numericUser) {
396
+ userDocumentId = numericUser.documentId;
397
+ user = numericUser;
398
+ }
399
+ }
350
400
 
351
401
  if (!user) {
352
402
  return ctx.throw(404, 'User not found');
@@ -356,7 +406,7 @@ module.exports = {
356
406
  const newBlockedStatus = !user.blocked;
357
407
 
358
408
  await strapi.documents(USER_UID).update({
359
- documentId: userId,
409
+ documentId: userDocumentId,
360
410
  data: {
361
411
  blocked: newBlockedStatus,
362
412
  },
@@ -367,7 +417,7 @@ module.exports = {
367
417
  const sessionService = strapi
368
418
  .plugin('magic-sessionmanager')
369
419
  .service('session');
370
- await sessionService.terminateSession({ userId });
420
+ await sessionService.terminateSession({ userId: userDocumentId });
371
421
  }
372
422
 
373
423
  ctx.body = {
@@ -13,9 +13,10 @@ const SESSION_UID = 'plugin::magic-sessionmanager.session';
13
13
  module.exports = ({ strapi, sessionService }) => {
14
14
  return async (ctx, next) => {
15
15
  // BEFORE processing request: Check if user's sessions are active
16
- if (ctx.state.user && ctx.state.user.id) {
16
+ // Strapi v5: Use documentId instead of numeric id for Document Service API
17
+ if (ctx.state.user && ctx.state.user.documentId) {
17
18
  try {
18
- const userId = ctx.state.user.id;
19
+ const userId = ctx.state.user.documentId;
19
20
 
20
21
  // Check if user has ANY active sessions
21
22
  const activeSessions = await strapi.documents(SESSION_UID).findMany( {
@@ -41,9 +42,9 @@ module.exports = ({ strapi, sessionService }) => {
41
42
  await next();
42
43
 
43
44
  // AFTER response: Update activity timestamps if user is authenticated
44
- if (ctx.state.user && ctx.state.user.id) {
45
+ if (ctx.state.user && ctx.state.user.documentId) {
45
46
  try {
46
- const userId = ctx.state.user.id;
47
+ const userId = ctx.state.user.documentId;
47
48
 
48
49
  // Try to find or extract sessionId from context
49
50
  const sessionId = ctx.state.sessionId;
@@ -38,14 +38,23 @@ module.exports = {
38
38
  // SESSION QUERIES
39
39
  // ============================================================
40
40
 
41
+ {
42
+ method: 'GET',
43
+ path: '/my-sessions',
44
+ handler: 'session.getOwnSessions',
45
+ config: {
46
+ auth: { strategies: ['users-permissions'] },
47
+ description: 'Get own sessions (automatically uses authenticated user)',
48
+ },
49
+ },
41
50
  {
42
51
  method: 'GET',
43
52
  path: '/user/:userId/sessions',
44
53
  handler: 'session.getUserSessions',
45
54
  config: {
46
55
  auth: { strategies: ['users-permissions'] },
47
- description: 'Get own sessions (controller validates user can only see own sessions)',
56
+ description: 'Get sessions by userId (validates user can only see own sessions)',
57
+ },
48
58
  },
49
- },
50
59
  ],
51
60
  };