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 +5 -0
- package/dist/server/index.js +81 -22
- package/dist/server/index.mjs +81 -22
- package/package.json +1 -1
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
|
package/dist/server/index.js
CHANGED
|
@@ -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
|
|
824
|
+
const thisSession = await strapi2.documents(SESSION_UID$3).findFirst({
|
|
824
825
|
filters: {
|
|
825
826
|
user: { documentId: userDocId },
|
|
826
|
-
|
|
827
|
+
tokenHash: tokenHashValue
|
|
827
828
|
},
|
|
828
|
-
|
|
829
|
+
fields: ["documentId", "isActive", "terminatedManually", "lastActive"]
|
|
829
830
|
});
|
|
830
|
-
if (
|
|
831
|
-
|
|
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]
|
|
834
|
+
`[magic-sessionmanager] [JWT-BLOCKED] Session was manually terminated (user: ${userDocId.substring(0, 8)}...)`
|
|
847
835
|
);
|
|
848
836
|
return null;
|
|
849
837
|
}
|
|
850
|
-
|
|
838
|
+
if (thisSession.isActive) {
|
|
839
|
+
return decoded;
|
|
840
|
+
}
|
|
851
841
|
await strapi2.documents(SESSION_UID$3).update({
|
|
852
|
-
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.
|
|
2743
|
+
const version$1 = "4.5.0";
|
|
2685
2744
|
const require$$2 = {
|
|
2686
2745
|
version: version$1
|
|
2687
2746
|
};
|
package/dist/server/index.mjs
CHANGED
|
@@ -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
|
|
811
|
+
const thisSession = await strapi2.documents(SESSION_UID$3).findFirst({
|
|
811
812
|
filters: {
|
|
812
813
|
user: { documentId: userDocId },
|
|
813
|
-
|
|
814
|
+
tokenHash: tokenHashValue
|
|
814
815
|
},
|
|
815
|
-
|
|
816
|
+
fields: ["documentId", "isActive", "terminatedManually", "lastActive"]
|
|
816
817
|
});
|
|
817
|
-
if (
|
|
818
|
-
|
|
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]
|
|
821
|
+
`[magic-sessionmanager] [JWT-BLOCKED] Session was manually terminated (user: ${userDocId.substring(0, 8)}...)`
|
|
834
822
|
);
|
|
835
823
|
return null;
|
|
836
824
|
}
|
|
837
|
-
|
|
825
|
+
if (thisSession.isActive) {
|
|
826
|
+
return decoded;
|
|
827
|
+
}
|
|
838
828
|
await strapi2.documents(SESSION_UID$3).update({
|
|
839
|
-
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.
|
|
2730
|
+
const version$1 = "4.5.0";
|
|
2672
2731
|
const require$$2 = {
|
|
2673
2732
|
version: version$1
|
|
2674
2733
|
};
|
package/package.json
CHANGED