strapi-plugin-magic-sessionmanager 4.2.6 → 4.2.7

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.
@@ -179,7 +179,7 @@ function encryptToken$2(token) {
179
179
  throw new Error("Failed to encrypt token");
180
180
  }
181
181
  }
182
- function decryptToken$3(encryptedToken) {
182
+ function decryptToken$4(encryptedToken) {
183
183
  if (!encryptedToken) return null;
184
184
  try {
185
185
  const key = getEncryptionKey();
@@ -208,38 +208,59 @@ function generateSessionId$1(userId) {
208
208
  }
209
209
  var encryption = {
210
210
  encryptToken: encryptToken$2,
211
- decryptToken: decryptToken$3,
211
+ decryptToken: decryptToken$4,
212
212
  generateSessionId: generateSessionId$1
213
213
  };
214
214
  const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
215
+ const { decryptToken: decryptToken$3 } = encryption;
215
216
  var lastSeen = ({ strapi: strapi2, sessionService }) => {
216
217
  return async (ctx, next) => {
217
218
  if (ctx.state.user && ctx.state.user.documentId) {
218
219
  try {
219
220
  const userId = ctx.state.user.documentId;
221
+ const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
222
+ if (!currentToken) {
223
+ await next();
224
+ return;
225
+ }
220
226
  const activeSessions = await strapi2.documents(SESSION_UID$3).findMany({
221
227
  filters: {
222
228
  user: { documentId: userId },
223
229
  isActive: true
224
- },
225
- limit: 1
230
+ }
226
231
  });
227
232
  if (!activeSessions || activeSessions.length === 0) {
228
- strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Blocked request - User ${userId} has no active sessions`);
233
+ strapi2.log.info(`[magic-sessionmanager] [BLOCKED] User ${userId} has no active sessions`);
229
234
  return ctx.unauthorized("All sessions have been terminated. Please login again.");
230
235
  }
236
+ let matchingSession = null;
237
+ for (const session2 of activeSessions) {
238
+ if (!session2.token) continue;
239
+ try {
240
+ const decrypted = decryptToken$3(session2.token);
241
+ if (decrypted === currentToken) {
242
+ matchingSession = session2;
243
+ break;
244
+ }
245
+ } catch (err) {
246
+ }
247
+ }
248
+ if (!matchingSession) {
249
+ strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Session for user ${userId} has been terminated`);
250
+ return ctx.unauthorized("This session has been terminated. Please login again.");
251
+ }
252
+ ctx.state.sessionId = matchingSession.documentId;
253
+ ctx.state.currentSession = matchingSession;
231
254
  } catch (err) {
232
- strapi2.log.debug("[magic-sessionmanager] Error checking active sessions:", err.message);
255
+ strapi2.log.debug("[magic-sessionmanager] Error checking session:", err.message);
233
256
  }
234
257
  }
235
258
  await next();
236
- if (ctx.state.user && ctx.state.user.documentId) {
259
+ if (ctx.state.user && ctx.state.user.documentId && ctx.state.sessionId) {
237
260
  try {
238
- const userId = ctx.state.user.documentId;
239
- const sessionId = ctx.state.sessionId;
240
261
  await sessionService.touch({
241
- userId,
242
- sessionId
262
+ userId: ctx.state.user.documentId,
263
+ sessionId: ctx.state.sessionId
243
264
  });
244
265
  } catch (err) {
245
266
  strapi2.log.debug("[magic-sessionmanager] Error updating lastSeen:", err.message);
@@ -562,9 +583,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
562
583
  };
563
584
  async function ensureContentApiPermissions(strapi2, log) {
564
585
  try {
565
- const authenticatedRole = await strapi2.query("plugin::users-permissions.role").findOne({
566
- where: { type: "authenticated" }
586
+ const ROLE_UID = "plugin::users-permissions.role";
587
+ const PERMISSION_UID = "plugin::users-permissions.permission";
588
+ const roles = await strapi2.entityService.findMany(ROLE_UID, {
589
+ filters: { type: "authenticated" },
590
+ limit: 1
567
591
  });
592
+ const authenticatedRole = roles?.[0];
568
593
  if (!authenticatedRole) {
569
594
  log.warn("Authenticated role not found - skipping permission setup");
570
595
  return;
@@ -573,10 +598,12 @@ async function ensureContentApiPermissions(strapi2, log) {
573
598
  "plugin::magic-sessionmanager.session.logout",
574
599
  "plugin::magic-sessionmanager.session.logoutAll",
575
600
  "plugin::magic-sessionmanager.session.getOwnSessions",
576
- "plugin::magic-sessionmanager.session.getUserSessions"
601
+ "plugin::magic-sessionmanager.session.getUserSessions",
602
+ "plugin::magic-sessionmanager.session.getCurrentSession",
603
+ "plugin::magic-sessionmanager.session.terminateOwnSession"
577
604
  ];
578
- const existingPermissions = await strapi2.query("plugin::users-permissions.permission").findMany({
579
- where: {
605
+ const existingPermissions = await strapi2.entityService.findMany(PERMISSION_UID, {
606
+ filters: {
580
607
  role: authenticatedRole.id,
581
608
  action: { $in: requiredActions }
582
609
  }
@@ -588,7 +615,7 @@ async function ensureContentApiPermissions(strapi2, log) {
588
615
  return;
589
616
  }
590
617
  for (const action of missingActions) {
591
- await strapi2.query("plugin::users-permissions.permission").create({
618
+ await strapi2.entityService.create(PERMISSION_UID, {
592
619
  data: {
593
620
  action,
594
621
  role: authenticatedRole.id
@@ -767,6 +794,15 @@ var contentApi$1 = {
767
794
  description: "Get own sessions (automatically uses authenticated user)"
768
795
  }
769
796
  },
797
+ {
798
+ method: "GET",
799
+ path: "/current-session",
800
+ handler: "session.getCurrentSession",
801
+ config: {
802
+ auth: { strategies: ["users-permissions"] },
803
+ description: "Get current session info based on JWT token"
804
+ }
805
+ },
770
806
  {
771
807
  method: "GET",
772
808
  path: "/user/:userId/sessions",
@@ -775,6 +811,18 @@ var contentApi$1 = {
775
811
  auth: { strategies: ["users-permissions"] },
776
812
  description: "Get sessions by userId (validates user can only see own sessions)"
777
813
  }
814
+ },
815
+ // ============================================================
816
+ // SESSION MANAGEMENT (for own sessions only)
817
+ // ============================================================
818
+ {
819
+ method: "DELETE",
820
+ path: "/my-sessions/:sessionId",
821
+ handler: "session.terminateOwnSession",
822
+ config: {
823
+ auth: { strategies: ["users-permissions"] },
824
+ description: "Terminate a specific own session (not current)"
825
+ }
778
826
  }
779
827
  ]
780
828
  };
@@ -977,19 +1025,52 @@ var session$3 = {
977
1025
  * Get own sessions (authenticated user)
978
1026
  * GET /api/magic-sessionmanager/my-sessions
979
1027
  * Automatically uses the authenticated user's documentId
1028
+ * Marks which session is the current one (based on JWT token)
980
1029
  */
981
1030
  async getOwnSessions(ctx) {
982
1031
  try {
983
1032
  const userId = ctx.state.user?.documentId;
1033
+ const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
984
1034
  if (!userId) {
985
1035
  return ctx.throw(401, "Unauthorized");
986
1036
  }
987
- const sessionService = strapi.plugin("magic-sessionmanager").service("session");
988
- const sessions = await sessionService.getUserSessions(userId);
1037
+ const allSessions = await strapi.documents(SESSION_UID$1).findMany({
1038
+ filters: { user: { documentId: userId } },
1039
+ sort: { loginTime: "desc" }
1040
+ });
1041
+ const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
1042
+ const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
1043
+ const now = /* @__PURE__ */ new Date();
1044
+ const sessionsWithCurrent = allSessions.map((session2) => {
1045
+ const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1046
+ const timeSinceActive = now - lastActiveTime;
1047
+ const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
1048
+ let isCurrentSession = false;
1049
+ if (session2.token && currentToken) {
1050
+ try {
1051
+ const decrypted = decryptToken$1(session2.token);
1052
+ isCurrentSession = decrypted === currentToken;
1053
+ } catch (err) {
1054
+ }
1055
+ }
1056
+ const { token, refreshToken, ...sessionWithoutTokens } = session2;
1057
+ return {
1058
+ ...sessionWithoutTokens,
1059
+ isCurrentSession,
1060
+ isTrulyActive,
1061
+ minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1062
+ };
1063
+ });
1064
+ sessionsWithCurrent.sort((a, b) => {
1065
+ if (a.isCurrentSession) return -1;
1066
+ if (b.isCurrentSession) return 1;
1067
+ return new Date(b.loginTime) - new Date(a.loginTime);
1068
+ });
989
1069
  ctx.body = {
990
- data: sessions,
1070
+ data: sessionsWithCurrent,
991
1071
  meta: {
992
- count: sessions.length
1072
+ count: sessionsWithCurrent.length,
1073
+ active: sessionsWithCurrent.filter((s) => s.isTrulyActive).length
993
1074
  }
994
1075
  };
995
1076
  } catch (err) {
@@ -1084,6 +1165,104 @@ var session$3 = {
1084
1165
  ctx.throw(500, "Error during logout");
1085
1166
  }
1086
1167
  },
1168
+ /**
1169
+ * Get current session info based on JWT token
1170
+ * GET /api/magic-sessionmanager/current-session
1171
+ * Returns the session associated with the current JWT token
1172
+ */
1173
+ async getCurrentSession(ctx) {
1174
+ try {
1175
+ const userId = ctx.state.user?.documentId;
1176
+ const token = ctx.request.headers.authorization?.replace("Bearer ", "");
1177
+ if (!userId || !token) {
1178
+ return ctx.throw(401, "Unauthorized");
1179
+ }
1180
+ const sessions = await strapi.documents(SESSION_UID$1).findMany({
1181
+ filters: {
1182
+ user: { documentId: userId },
1183
+ isActive: true
1184
+ }
1185
+ });
1186
+ const currentSession = sessions.find((session2) => {
1187
+ if (!session2.token) return false;
1188
+ try {
1189
+ const decrypted = decryptToken$1(session2.token);
1190
+ return decrypted === token;
1191
+ } catch (err) {
1192
+ return false;
1193
+ }
1194
+ });
1195
+ if (!currentSession) {
1196
+ return ctx.notFound("Current session not found");
1197
+ }
1198
+ const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
1199
+ const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
1200
+ const now = /* @__PURE__ */ new Date();
1201
+ const lastActiveTime = currentSession.lastActive ? new Date(currentSession.lastActive) : new Date(currentSession.loginTime);
1202
+ const timeSinceActive = now - lastActiveTime;
1203
+ const { token: _, refreshToken: __, ...sessionWithoutTokens } = currentSession;
1204
+ ctx.body = {
1205
+ data: {
1206
+ ...sessionWithoutTokens,
1207
+ isCurrentSession: true,
1208
+ isTrulyActive: timeSinceActive < inactivityTimeout,
1209
+ minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1210
+ }
1211
+ };
1212
+ } catch (err) {
1213
+ strapi.log.error("[magic-sessionmanager] Error getting current session:", err);
1214
+ ctx.throw(500, "Error fetching current session");
1215
+ }
1216
+ },
1217
+ /**
1218
+ * Terminate a specific own session (not the current one)
1219
+ * DELETE /api/magic-sessionmanager/my-sessions/:sessionId
1220
+ * SECURITY: User can only terminate their OWN sessions
1221
+ */
1222
+ async terminateOwnSession(ctx) {
1223
+ try {
1224
+ const userId = ctx.state.user?.documentId;
1225
+ const { sessionId } = ctx.params;
1226
+ const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
1227
+ if (!userId) {
1228
+ return ctx.throw(401, "Unauthorized");
1229
+ }
1230
+ if (!sessionId) {
1231
+ return ctx.badRequest("Session ID is required");
1232
+ }
1233
+ const sessionToTerminate = await strapi.documents(SESSION_UID$1).findOne({
1234
+ documentId: sessionId,
1235
+ populate: { user: { fields: ["documentId"] } }
1236
+ });
1237
+ if (!sessionToTerminate) {
1238
+ return ctx.notFound("Session not found");
1239
+ }
1240
+ const sessionUserId = sessionToTerminate.user?.documentId;
1241
+ if (sessionUserId !== userId) {
1242
+ strapi.log.warn(`[magic-sessionmanager] Security: User ${userId} tried to terminate session ${sessionId} of user ${sessionUserId}`);
1243
+ return ctx.forbidden("You can only terminate your own sessions");
1244
+ }
1245
+ if (sessionToTerminate.token && currentToken) {
1246
+ try {
1247
+ const decrypted = decryptToken$1(sessionToTerminate.token);
1248
+ if (decrypted === currentToken) {
1249
+ return ctx.badRequest("Cannot terminate current session. Use /logout instead.");
1250
+ }
1251
+ } catch (err) {
1252
+ }
1253
+ }
1254
+ const sessionService = strapi.plugin("magic-sessionmanager").service("session");
1255
+ await sessionService.terminateSession({ sessionId });
1256
+ strapi.log.info(`[magic-sessionmanager] User ${userId} terminated own session ${sessionId}`);
1257
+ ctx.body = {
1258
+ message: `Session ${sessionId} terminated successfully`,
1259
+ success: true
1260
+ };
1261
+ } catch (err) {
1262
+ strapi.log.error("[magic-sessionmanager] Error terminating own session:", err);
1263
+ ctx.throw(500, "Error terminating session");
1264
+ }
1265
+ },
1087
1266
  /**
1088
1267
  * Terminate specific session
1089
1268
  * DELETE /magic-sessionmanager/sessions/:sessionId
@@ -1862,7 +2041,7 @@ var session$1 = ({ strapi: strapi2 }) => {
1862
2041
  }
1863
2042
  };
1864
2043
  };
1865
- const version = "4.2.5";
2044
+ const version = "4.2.7";
1866
2045
  const require$$2 = {
1867
2046
  version
1868
2047
  };
@@ -175,7 +175,7 @@ function encryptToken$2(token) {
175
175
  throw new Error("Failed to encrypt token");
176
176
  }
177
177
  }
178
- function decryptToken$3(encryptedToken) {
178
+ function decryptToken$4(encryptedToken) {
179
179
  if (!encryptedToken) return null;
180
180
  try {
181
181
  const key = getEncryptionKey();
@@ -204,38 +204,59 @@ function generateSessionId$1(userId) {
204
204
  }
205
205
  var encryption = {
206
206
  encryptToken: encryptToken$2,
207
- decryptToken: decryptToken$3,
207
+ decryptToken: decryptToken$4,
208
208
  generateSessionId: generateSessionId$1
209
209
  };
210
210
  const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
211
+ const { decryptToken: decryptToken$3 } = encryption;
211
212
  var lastSeen = ({ strapi: strapi2, sessionService }) => {
212
213
  return async (ctx, next) => {
213
214
  if (ctx.state.user && ctx.state.user.documentId) {
214
215
  try {
215
216
  const userId = ctx.state.user.documentId;
217
+ const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
218
+ if (!currentToken) {
219
+ await next();
220
+ return;
221
+ }
216
222
  const activeSessions = await strapi2.documents(SESSION_UID$3).findMany({
217
223
  filters: {
218
224
  user: { documentId: userId },
219
225
  isActive: true
220
- },
221
- limit: 1
226
+ }
222
227
  });
223
228
  if (!activeSessions || activeSessions.length === 0) {
224
- strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Blocked request - User ${userId} has no active sessions`);
229
+ strapi2.log.info(`[magic-sessionmanager] [BLOCKED] User ${userId} has no active sessions`);
225
230
  return ctx.unauthorized("All sessions have been terminated. Please login again.");
226
231
  }
232
+ let matchingSession = null;
233
+ for (const session2 of activeSessions) {
234
+ if (!session2.token) continue;
235
+ try {
236
+ const decrypted = decryptToken$3(session2.token);
237
+ if (decrypted === currentToken) {
238
+ matchingSession = session2;
239
+ break;
240
+ }
241
+ } catch (err) {
242
+ }
243
+ }
244
+ if (!matchingSession) {
245
+ strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Session for user ${userId} has been terminated`);
246
+ return ctx.unauthorized("This session has been terminated. Please login again.");
247
+ }
248
+ ctx.state.sessionId = matchingSession.documentId;
249
+ ctx.state.currentSession = matchingSession;
227
250
  } catch (err) {
228
- strapi2.log.debug("[magic-sessionmanager] Error checking active sessions:", err.message);
251
+ strapi2.log.debug("[magic-sessionmanager] Error checking session:", err.message);
229
252
  }
230
253
  }
231
254
  await next();
232
- if (ctx.state.user && ctx.state.user.documentId) {
255
+ if (ctx.state.user && ctx.state.user.documentId && ctx.state.sessionId) {
233
256
  try {
234
- const userId = ctx.state.user.documentId;
235
- const sessionId = ctx.state.sessionId;
236
257
  await sessionService.touch({
237
- userId,
238
- sessionId
258
+ userId: ctx.state.user.documentId,
259
+ sessionId: ctx.state.sessionId
239
260
  });
240
261
  } catch (err) {
241
262
  strapi2.log.debug("[magic-sessionmanager] Error updating lastSeen:", err.message);
@@ -558,9 +579,13 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
558
579
  };
559
580
  async function ensureContentApiPermissions(strapi2, log) {
560
581
  try {
561
- const authenticatedRole = await strapi2.query("plugin::users-permissions.role").findOne({
562
- where: { type: "authenticated" }
582
+ const ROLE_UID = "plugin::users-permissions.role";
583
+ const PERMISSION_UID = "plugin::users-permissions.permission";
584
+ const roles = await strapi2.entityService.findMany(ROLE_UID, {
585
+ filters: { type: "authenticated" },
586
+ limit: 1
563
587
  });
588
+ const authenticatedRole = roles?.[0];
564
589
  if (!authenticatedRole) {
565
590
  log.warn("Authenticated role not found - skipping permission setup");
566
591
  return;
@@ -569,10 +594,12 @@ async function ensureContentApiPermissions(strapi2, log) {
569
594
  "plugin::magic-sessionmanager.session.logout",
570
595
  "plugin::magic-sessionmanager.session.logoutAll",
571
596
  "plugin::magic-sessionmanager.session.getOwnSessions",
572
- "plugin::magic-sessionmanager.session.getUserSessions"
597
+ "plugin::magic-sessionmanager.session.getUserSessions",
598
+ "plugin::magic-sessionmanager.session.getCurrentSession",
599
+ "plugin::magic-sessionmanager.session.terminateOwnSession"
573
600
  ];
574
- const existingPermissions = await strapi2.query("plugin::users-permissions.permission").findMany({
575
- where: {
601
+ const existingPermissions = await strapi2.entityService.findMany(PERMISSION_UID, {
602
+ filters: {
576
603
  role: authenticatedRole.id,
577
604
  action: { $in: requiredActions }
578
605
  }
@@ -584,7 +611,7 @@ async function ensureContentApiPermissions(strapi2, log) {
584
611
  return;
585
612
  }
586
613
  for (const action of missingActions) {
587
- await strapi2.query("plugin::users-permissions.permission").create({
614
+ await strapi2.entityService.create(PERMISSION_UID, {
588
615
  data: {
589
616
  action,
590
617
  role: authenticatedRole.id
@@ -763,6 +790,15 @@ var contentApi$1 = {
763
790
  description: "Get own sessions (automatically uses authenticated user)"
764
791
  }
765
792
  },
793
+ {
794
+ method: "GET",
795
+ path: "/current-session",
796
+ handler: "session.getCurrentSession",
797
+ config: {
798
+ auth: { strategies: ["users-permissions"] },
799
+ description: "Get current session info based on JWT token"
800
+ }
801
+ },
766
802
  {
767
803
  method: "GET",
768
804
  path: "/user/:userId/sessions",
@@ -771,6 +807,18 @@ var contentApi$1 = {
771
807
  auth: { strategies: ["users-permissions"] },
772
808
  description: "Get sessions by userId (validates user can only see own sessions)"
773
809
  }
810
+ },
811
+ // ============================================================
812
+ // SESSION MANAGEMENT (for own sessions only)
813
+ // ============================================================
814
+ {
815
+ method: "DELETE",
816
+ path: "/my-sessions/:sessionId",
817
+ handler: "session.terminateOwnSession",
818
+ config: {
819
+ auth: { strategies: ["users-permissions"] },
820
+ description: "Terminate a specific own session (not current)"
821
+ }
774
822
  }
775
823
  ]
776
824
  };
@@ -973,19 +1021,52 @@ var session$3 = {
973
1021
  * Get own sessions (authenticated user)
974
1022
  * GET /api/magic-sessionmanager/my-sessions
975
1023
  * Automatically uses the authenticated user's documentId
1024
+ * Marks which session is the current one (based on JWT token)
976
1025
  */
977
1026
  async getOwnSessions(ctx) {
978
1027
  try {
979
1028
  const userId = ctx.state.user?.documentId;
1029
+ const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
980
1030
  if (!userId) {
981
1031
  return ctx.throw(401, "Unauthorized");
982
1032
  }
983
- const sessionService = strapi.plugin("magic-sessionmanager").service("session");
984
- const sessions = await sessionService.getUserSessions(userId);
1033
+ const allSessions = await strapi.documents(SESSION_UID$1).findMany({
1034
+ filters: { user: { documentId: userId } },
1035
+ sort: { loginTime: "desc" }
1036
+ });
1037
+ const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
1038
+ const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
1039
+ const now = /* @__PURE__ */ new Date();
1040
+ const sessionsWithCurrent = allSessions.map((session2) => {
1041
+ const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1042
+ const timeSinceActive = now - lastActiveTime;
1043
+ const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
1044
+ let isCurrentSession = false;
1045
+ if (session2.token && currentToken) {
1046
+ try {
1047
+ const decrypted = decryptToken$1(session2.token);
1048
+ isCurrentSession = decrypted === currentToken;
1049
+ } catch (err) {
1050
+ }
1051
+ }
1052
+ const { token, refreshToken, ...sessionWithoutTokens } = session2;
1053
+ return {
1054
+ ...sessionWithoutTokens,
1055
+ isCurrentSession,
1056
+ isTrulyActive,
1057
+ minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1058
+ };
1059
+ });
1060
+ sessionsWithCurrent.sort((a, b) => {
1061
+ if (a.isCurrentSession) return -1;
1062
+ if (b.isCurrentSession) return 1;
1063
+ return new Date(b.loginTime) - new Date(a.loginTime);
1064
+ });
985
1065
  ctx.body = {
986
- data: sessions,
1066
+ data: sessionsWithCurrent,
987
1067
  meta: {
988
- count: sessions.length
1068
+ count: sessionsWithCurrent.length,
1069
+ active: sessionsWithCurrent.filter((s) => s.isTrulyActive).length
989
1070
  }
990
1071
  };
991
1072
  } catch (err) {
@@ -1080,6 +1161,104 @@ var session$3 = {
1080
1161
  ctx.throw(500, "Error during logout");
1081
1162
  }
1082
1163
  },
1164
+ /**
1165
+ * Get current session info based on JWT token
1166
+ * GET /api/magic-sessionmanager/current-session
1167
+ * Returns the session associated with the current JWT token
1168
+ */
1169
+ async getCurrentSession(ctx) {
1170
+ try {
1171
+ const userId = ctx.state.user?.documentId;
1172
+ const token = ctx.request.headers.authorization?.replace("Bearer ", "");
1173
+ if (!userId || !token) {
1174
+ return ctx.throw(401, "Unauthorized");
1175
+ }
1176
+ const sessions = await strapi.documents(SESSION_UID$1).findMany({
1177
+ filters: {
1178
+ user: { documentId: userId },
1179
+ isActive: true
1180
+ }
1181
+ });
1182
+ const currentSession = sessions.find((session2) => {
1183
+ if (!session2.token) return false;
1184
+ try {
1185
+ const decrypted = decryptToken$1(session2.token);
1186
+ return decrypted === token;
1187
+ } catch (err) {
1188
+ return false;
1189
+ }
1190
+ });
1191
+ if (!currentSession) {
1192
+ return ctx.notFound("Current session not found");
1193
+ }
1194
+ const config2 = strapi.config.get("plugin::magic-sessionmanager") || {};
1195
+ const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
1196
+ const now = /* @__PURE__ */ new Date();
1197
+ const lastActiveTime = currentSession.lastActive ? new Date(currentSession.lastActive) : new Date(currentSession.loginTime);
1198
+ const timeSinceActive = now - lastActiveTime;
1199
+ const { token: _, refreshToken: __, ...sessionWithoutTokens } = currentSession;
1200
+ ctx.body = {
1201
+ data: {
1202
+ ...sessionWithoutTokens,
1203
+ isCurrentSession: true,
1204
+ isTrulyActive: timeSinceActive < inactivityTimeout,
1205
+ minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1206
+ }
1207
+ };
1208
+ } catch (err) {
1209
+ strapi.log.error("[magic-sessionmanager] Error getting current session:", err);
1210
+ ctx.throw(500, "Error fetching current session");
1211
+ }
1212
+ },
1213
+ /**
1214
+ * Terminate a specific own session (not the current one)
1215
+ * DELETE /api/magic-sessionmanager/my-sessions/:sessionId
1216
+ * SECURITY: User can only terminate their OWN sessions
1217
+ */
1218
+ async terminateOwnSession(ctx) {
1219
+ try {
1220
+ const userId = ctx.state.user?.documentId;
1221
+ const { sessionId } = ctx.params;
1222
+ const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
1223
+ if (!userId) {
1224
+ return ctx.throw(401, "Unauthorized");
1225
+ }
1226
+ if (!sessionId) {
1227
+ return ctx.badRequest("Session ID is required");
1228
+ }
1229
+ const sessionToTerminate = await strapi.documents(SESSION_UID$1).findOne({
1230
+ documentId: sessionId,
1231
+ populate: { user: { fields: ["documentId"] } }
1232
+ });
1233
+ if (!sessionToTerminate) {
1234
+ return ctx.notFound("Session not found");
1235
+ }
1236
+ const sessionUserId = sessionToTerminate.user?.documentId;
1237
+ if (sessionUserId !== userId) {
1238
+ strapi.log.warn(`[magic-sessionmanager] Security: User ${userId} tried to terminate session ${sessionId} of user ${sessionUserId}`);
1239
+ return ctx.forbidden("You can only terminate your own sessions");
1240
+ }
1241
+ if (sessionToTerminate.token && currentToken) {
1242
+ try {
1243
+ const decrypted = decryptToken$1(sessionToTerminate.token);
1244
+ if (decrypted === currentToken) {
1245
+ return ctx.badRequest("Cannot terminate current session. Use /logout instead.");
1246
+ }
1247
+ } catch (err) {
1248
+ }
1249
+ }
1250
+ const sessionService = strapi.plugin("magic-sessionmanager").service("session");
1251
+ await sessionService.terminateSession({ sessionId });
1252
+ strapi.log.info(`[magic-sessionmanager] User ${userId} terminated own session ${sessionId}`);
1253
+ ctx.body = {
1254
+ message: `Session ${sessionId} terminated successfully`,
1255
+ success: true
1256
+ };
1257
+ } catch (err) {
1258
+ strapi.log.error("[magic-sessionmanager] Error terminating own session:", err);
1259
+ ctx.throw(500, "Error terminating session");
1260
+ }
1261
+ },
1083
1262
  /**
1084
1263
  * Terminate specific session
1085
1264
  * DELETE /magic-sessionmanager/sessions/:sessionId
@@ -1858,7 +2037,7 @@ var session$1 = ({ strapi: strapi2 }) => {
1858
2037
  }
1859
2038
  };
1860
2039
  };
1861
- const version = "4.2.5";
2040
+ const version = "4.2.7";
1862
2041
  const require$$2 = {
1863
2042
  version
1864
2043
  };
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.2.6",
2
+ "version": "4.2.7",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",