strapi-plugin-magic-sessionmanager 4.4.0 → 4.4.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.
package/README.md CHANGED
@@ -95,6 +95,11 @@ Track logins, monitor active users, and secure your app with one simple plugin.
95
95
  **When users logout:**
96
96
  - Plugin marks their session as "logged out"
97
97
  - They disappear from the active sessions list
98
+ - Manual logout permanently blocks session reactivation (security feature)
99
+
100
+ **Session Timeout vs Manual Logout:**
101
+ - **Timeout:** Session can be reactivated on next request (seamless UX)
102
+ - **Manual Logout:** Session is permanently terminated (security-first)
98
103
 
99
104
  **While users are active:**
100
105
  - Plugin updates their "last seen" time
@@ -809,6 +809,7 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
809
809
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
810
810
  const strictMode = config2.strictSessionEnforcement === true;
811
811
  try {
812
+ const tokenHashValue = hashToken$2(token);
812
813
  let userDocId = null;
813
814
  const user = await strapi2.entityService.findOne(
814
815
  "plugin::users-permissions.user",
@@ -820,36 +821,25 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
820
821
  strapi2.log.debug("[magic-sessionmanager] [JWT] No documentId found, allowing through");
821
822
  return decoded;
822
823
  }
823
- const activeSessions = await strapi2.documents(SESSION_UID$3).findMany({
824
+ const thisSession = await strapi2.documents(SESSION_UID$3).findFirst({
824
825
  filters: {
825
826
  user: { documentId: userDocId },
826
- isActive: true
827
+ tokenHash: tokenHashValue
827
828
  },
828
- limit: 1
829
+ fields: ["documentId", "isActive", "terminatedManually", "lastActive"]
829
830
  });
830
- if (activeSessions && activeSessions.length > 0) {
831
- return decoded;
832
- }
833
- const inactiveSessions = await strapi2.documents(SESSION_UID$3).findMany({
834
- filters: {
835
- user: { documentId: userDocId },
836
- isActive: false
837
- },
838
- limit: 5,
839
- fields: ["documentId", "terminatedManually", "lastActive"],
840
- sort: [{ lastActive: "desc" }]
841
- });
842
- if (inactiveSessions && inactiveSessions.length > 0) {
843
- const manuallyTerminated = inactiveSessions.find((s3) => s3.terminatedManually === true);
844
- if (manuallyTerminated) {
831
+ if (thisSession) {
832
+ if (thisSession.terminatedManually === true) {
845
833
  strapi2.log.info(
846
- `[magic-sessionmanager] [JWT-BLOCKED] User ${userDocId.substring(0, 8)}... was manually logged out`
834
+ `[magic-sessionmanager] [JWT-BLOCKED] Session was manually terminated (user: ${userDocId.substring(0, 8)}...)`
847
835
  );
848
836
  return null;
849
837
  }
850
- const sessionToReactivate = inactiveSessions[0];
838
+ if (thisSession.isActive) {
839
+ return decoded;
840
+ }
851
841
  await strapi2.documents(SESSION_UID$3).update({
852
- documentId: sessionToReactivate.documentId,
842
+ documentId: thisSession.documentId,
853
843
  data: {
854
844
  isActive: true,
855
845
  lastActive: /* @__PURE__ */ new Date()
@@ -860,6 +850,32 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
860
850
  );
861
851
  return decoded;
862
852
  }
853
+ const anyActiveSessions = await strapi2.documents(SESSION_UID$3).findMany({
854
+ filters: {
855
+ user: { documentId: userDocId },
856
+ isActive: true
857
+ },
858
+ limit: 1
859
+ });
860
+ if (anyActiveSessions && anyActiveSessions.length > 0) {
861
+ strapi2.log.debug(
862
+ `[magic-sessionmanager] [JWT] No session for token but user has other active sessions (allowing)`
863
+ );
864
+ return decoded;
865
+ }
866
+ const terminatedSessions = await strapi2.documents(SESSION_UID$3).findMany({
867
+ filters: {
868
+ user: { documentId: userDocId },
869
+ terminatedManually: true
870
+ },
871
+ limit: 1
872
+ });
873
+ if (terminatedSessions && terminatedSessions.length > 0) {
874
+ strapi2.log.info(
875
+ `[magic-sessionmanager] [JWT-BLOCKED] User ${userDocId.substring(0, 8)}... has terminated sessions`
876
+ );
877
+ return null;
878
+ }
863
879
  if (strictMode) {
864
880
  strapi2.log.info(
865
881
  `[magic-sessionmanager] [JWT-BLOCKED] No sessions exist for user ${userDocId.substring(0, 8)}... (strictMode)`
@@ -1131,6 +1147,15 @@ var admin$1 = {
1131
1147
  description: "Terminate a specific session (admin)"
1132
1148
  }
1133
1149
  },
1150
+ {
1151
+ method: "POST",
1152
+ path: "/sessions/:sessionId/simulate-timeout",
1153
+ handler: "session.simulateTimeout",
1154
+ config: {
1155
+ policies: ["admin::isAuthenticatedAdmin"],
1156
+ description: "Simulate session timeout for testing (sets isActive: false, terminatedManually: false)"
1157
+ }
1158
+ },
1134
1159
  {
1135
1160
  method: "DELETE",
1136
1161
  path: "/sessions/:sessionId",
@@ -1712,6 +1737,40 @@ var session$3 = {
1712
1737
  ctx.throw(500, "Error terminating session");
1713
1738
  }
1714
1739
  },
1740
+ /**
1741
+ * Simulate session timeout for testing (Admin action)
1742
+ * POST /magic-sessionmanager/sessions/:sessionId/simulate-timeout
1743
+ * Sets isActive: false, terminatedManually: false (as if cleanup job ran)
1744
+ * This allows testing session reactivation behavior
1745
+ */
1746
+ async simulateTimeout(ctx) {
1747
+ try {
1748
+ const { sessionId } = ctx.params;
1749
+ const session2 = await strapi.documents(SESSION_UID$2).findOne({
1750
+ documentId: sessionId
1751
+ });
1752
+ if (!session2) {
1753
+ return ctx.notFound("Session not found");
1754
+ }
1755
+ await strapi.documents(SESSION_UID$2).update({
1756
+ documentId: sessionId,
1757
+ data: {
1758
+ isActive: false,
1759
+ terminatedManually: false
1760
+ // This allows reactivation!
1761
+ }
1762
+ });
1763
+ strapi.log.info(`[magic-sessionmanager] [TEST] Session ${sessionId} simulated timeout (terminatedManually: false)`);
1764
+ ctx.body = {
1765
+ message: `Session ${sessionId} marked as timed out (reactivatable)`,
1766
+ success: true,
1767
+ terminatedManually: false
1768
+ };
1769
+ } catch (err) {
1770
+ strapi.log.error("[magic-sessionmanager] Error simulating timeout:", err);
1771
+ ctx.throw(500, "Error simulating session timeout");
1772
+ }
1773
+ },
1715
1774
  /**
1716
1775
  * Terminate a single session (Admin action)
1717
1776
  * POST /magic-sessionmanager/sessions/:sessionId/terminate
@@ -2681,7 +2740,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2681
2740
  }
2682
2741
  };
2683
2742
  };
2684
- const version$1 = "4.3.4";
2743
+ const version$1 = "4.5.0";
2685
2744
  const require$$2 = {
2686
2745
  version: version$1
2687
2746
  };
@@ -796,6 +796,7 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
796
796
  const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
797
797
  const strictMode = config2.strictSessionEnforcement === true;
798
798
  try {
799
+ const tokenHashValue = hashToken$2(token);
799
800
  let userDocId = null;
800
801
  const user = await strapi2.entityService.findOne(
801
802
  "plugin::users-permissions.user",
@@ -807,36 +808,25 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
807
808
  strapi2.log.debug("[magic-sessionmanager] [JWT] No documentId found, allowing through");
808
809
  return decoded;
809
810
  }
810
- const activeSessions = await strapi2.documents(SESSION_UID$3).findMany({
811
+ const thisSession = await strapi2.documents(SESSION_UID$3).findFirst({
811
812
  filters: {
812
813
  user: { documentId: userDocId },
813
- isActive: true
814
+ tokenHash: tokenHashValue
814
815
  },
815
- limit: 1
816
+ fields: ["documentId", "isActive", "terminatedManually", "lastActive"]
816
817
  });
817
- if (activeSessions && activeSessions.length > 0) {
818
- return decoded;
819
- }
820
- const inactiveSessions = await strapi2.documents(SESSION_UID$3).findMany({
821
- filters: {
822
- user: { documentId: userDocId },
823
- isActive: false
824
- },
825
- limit: 5,
826
- fields: ["documentId", "terminatedManually", "lastActive"],
827
- sort: [{ lastActive: "desc" }]
828
- });
829
- if (inactiveSessions && inactiveSessions.length > 0) {
830
- const manuallyTerminated = inactiveSessions.find((s3) => s3.terminatedManually === true);
831
- if (manuallyTerminated) {
818
+ if (thisSession) {
819
+ if (thisSession.terminatedManually === true) {
832
820
  strapi2.log.info(
833
- `[magic-sessionmanager] [JWT-BLOCKED] User ${userDocId.substring(0, 8)}... was manually logged out`
821
+ `[magic-sessionmanager] [JWT-BLOCKED] Session was manually terminated (user: ${userDocId.substring(0, 8)}...)`
834
822
  );
835
823
  return null;
836
824
  }
837
- const sessionToReactivate = inactiveSessions[0];
825
+ if (thisSession.isActive) {
826
+ return decoded;
827
+ }
838
828
  await strapi2.documents(SESSION_UID$3).update({
839
- documentId: sessionToReactivate.documentId,
829
+ documentId: thisSession.documentId,
840
830
  data: {
841
831
  isActive: true,
842
832
  lastActive: /* @__PURE__ */ new Date()
@@ -847,6 +837,32 @@ async function registerSessionAwareAuthStrategy(strapi2, log) {
847
837
  );
848
838
  return decoded;
849
839
  }
840
+ const anyActiveSessions = await strapi2.documents(SESSION_UID$3).findMany({
841
+ filters: {
842
+ user: { documentId: userDocId },
843
+ isActive: true
844
+ },
845
+ limit: 1
846
+ });
847
+ if (anyActiveSessions && anyActiveSessions.length > 0) {
848
+ strapi2.log.debug(
849
+ `[magic-sessionmanager] [JWT] No session for token but user has other active sessions (allowing)`
850
+ );
851
+ return decoded;
852
+ }
853
+ const terminatedSessions = await strapi2.documents(SESSION_UID$3).findMany({
854
+ filters: {
855
+ user: { documentId: userDocId },
856
+ terminatedManually: true
857
+ },
858
+ limit: 1
859
+ });
860
+ if (terminatedSessions && terminatedSessions.length > 0) {
861
+ strapi2.log.info(
862
+ `[magic-sessionmanager] [JWT-BLOCKED] User ${userDocId.substring(0, 8)}... has terminated sessions`
863
+ );
864
+ return null;
865
+ }
850
866
  if (strictMode) {
851
867
  strapi2.log.info(
852
868
  `[magic-sessionmanager] [JWT-BLOCKED] No sessions exist for user ${userDocId.substring(0, 8)}... (strictMode)`
@@ -1118,6 +1134,15 @@ var admin$1 = {
1118
1134
  description: "Terminate a specific session (admin)"
1119
1135
  }
1120
1136
  },
1137
+ {
1138
+ method: "POST",
1139
+ path: "/sessions/:sessionId/simulate-timeout",
1140
+ handler: "session.simulateTimeout",
1141
+ config: {
1142
+ policies: ["admin::isAuthenticatedAdmin"],
1143
+ description: "Simulate session timeout for testing (sets isActive: false, terminatedManually: false)"
1144
+ }
1145
+ },
1121
1146
  {
1122
1147
  method: "DELETE",
1123
1148
  path: "/sessions/:sessionId",
@@ -1699,6 +1724,40 @@ var session$3 = {
1699
1724
  ctx.throw(500, "Error terminating session");
1700
1725
  }
1701
1726
  },
1727
+ /**
1728
+ * Simulate session timeout for testing (Admin action)
1729
+ * POST /magic-sessionmanager/sessions/:sessionId/simulate-timeout
1730
+ * Sets isActive: false, terminatedManually: false (as if cleanup job ran)
1731
+ * This allows testing session reactivation behavior
1732
+ */
1733
+ async simulateTimeout(ctx) {
1734
+ try {
1735
+ const { sessionId } = ctx.params;
1736
+ const session2 = await strapi.documents(SESSION_UID$2).findOne({
1737
+ documentId: sessionId
1738
+ });
1739
+ if (!session2) {
1740
+ return ctx.notFound("Session not found");
1741
+ }
1742
+ await strapi.documents(SESSION_UID$2).update({
1743
+ documentId: sessionId,
1744
+ data: {
1745
+ isActive: false,
1746
+ terminatedManually: false
1747
+ // This allows reactivation!
1748
+ }
1749
+ });
1750
+ strapi.log.info(`[magic-sessionmanager] [TEST] Session ${sessionId} simulated timeout (terminatedManually: false)`);
1751
+ ctx.body = {
1752
+ message: `Session ${sessionId} marked as timed out (reactivatable)`,
1753
+ success: true,
1754
+ terminatedManually: false
1755
+ };
1756
+ } catch (err) {
1757
+ strapi.log.error("[magic-sessionmanager] Error simulating timeout:", err);
1758
+ ctx.throw(500, "Error simulating session timeout");
1759
+ }
1760
+ },
1702
1761
  /**
1703
1762
  * Terminate a single session (Admin action)
1704
1763
  * POST /magic-sessionmanager/sessions/:sessionId/terminate
@@ -2668,7 +2727,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2668
2727
  }
2669
2728
  };
2670
2729
  };
2671
- const version$1 = "4.3.4";
2730
+ const version$1 = "4.5.0";
2672
2731
  const require$$2 = {
2673
2732
  version: version$1
2674
2733
  };
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.4.0",
2
+ "version": "4.4.2",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",