strapi-plugin-magic-sessionmanager 4.2.10 → 4.2.11

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
@@ -390,30 +390,204 @@ Trigger a suspicious login (e.g., use a VPN) and check if the email arrives!
390
390
 
391
391
  ---
392
392
 
393
- ## 📋 Simple API Guide
393
+ ## 📋 Content-API Endpoints (For Frontend/Apps)
394
394
 
395
- ### Get Sessions
395
+ All Content-API endpoints require a valid JWT token in the `Authorization` header.
396
+ Users can only access their **own** sessions.
397
+
398
+ ### Get My Sessions
399
+
400
+ Returns all sessions for the authenticated user.
401
+
402
+ ```bash
403
+ GET /api/magic-sessionmanager/my-sessions
404
+ Authorization: Bearer <JWT>
405
+ ```
406
+
407
+ **Response:**
408
+ ```json
409
+ {
410
+ "data": [
411
+ {
412
+ "id": 41,
413
+ "documentId": "abc123xyz",
414
+ "sessionId": "sess_m5k2h_8a3b1c2d_f9e8d7c6",
415
+ "ipAddress": "192.168.1.100",
416
+ "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36...",
417
+ "loginTime": "2026-01-02T10:30:00.000Z",
418
+ "lastActive": "2026-01-02T13:45:00.000Z",
419
+ "logoutTime": null,
420
+ "isActive": true,
421
+ "deviceType": "desktop",
422
+ "browserName": "Chrome 143",
423
+ "osName": "macOS 10.15.7",
424
+ "geoLocation": null,
425
+ "securityScore": null,
426
+ "isCurrentSession": true,
427
+ "isTrulyActive": true,
428
+ "minutesSinceActive": 2
429
+ },
430
+ {
431
+ "id": 40,
432
+ "documentId": "def456uvw",
433
+ "sessionId": "sess_m5k1g_7b2a0c1d_e8d7c6b5",
434
+ "ipAddress": "10.0.0.50",
435
+ "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)...",
436
+ "loginTime": "2026-01-01T08:15:00.000Z",
437
+ "lastActive": "2026-01-01T12:00:00.000Z",
438
+ "logoutTime": null,
439
+ "isActive": true,
440
+ "deviceType": "mobile",
441
+ "browserName": "Safari",
442
+ "osName": "iOS 17",
443
+ "geoLocation": null,
444
+ "securityScore": null,
445
+ "isCurrentSession": false,
446
+ "isTrulyActive": false,
447
+ "minutesSinceActive": 1545
448
+ }
449
+ ],
450
+ "meta": {
451
+ "count": 2,
452
+ "active": 1
453
+ }
454
+ }
455
+ ```
456
+
457
+ ### Get Current Session
458
+
459
+ Returns only the session associated with the current JWT token.
460
+
461
+ ```bash
462
+ GET /api/magic-sessionmanager/current-session
463
+ Authorization: Bearer <JWT>
464
+ ```
465
+
466
+ **Response:**
467
+ ```json
468
+ {
469
+ "data": {
470
+ "id": 41,
471
+ "documentId": "abc123xyz",
472
+ "sessionId": "sess_m5k2h_8a3b1c2d_f9e8d7c6",
473
+ "ipAddress": "192.168.1.100",
474
+ "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36...",
475
+ "loginTime": "2026-01-02T10:30:00.000Z",
476
+ "lastActive": "2026-01-02T13:45:00.000Z",
477
+ "logoutTime": null,
478
+ "isActive": true,
479
+ "deviceType": "desktop",
480
+ "browserName": "Chrome 143",
481
+ "osName": "macOS 10.15.7",
482
+ "geoLocation": null,
483
+ "securityScore": null,
484
+ "isCurrentSession": true,
485
+ "isTrulyActive": true,
486
+ "minutesSinceActive": 2
487
+ }
488
+ }
489
+ ```
490
+
491
+ ### Logout (Current Session)
492
+
493
+ Terminates only the current session.
494
+
495
+ ```bash
496
+ POST /api/magic-sessionmanager/logout
497
+ Authorization: Bearer <JWT>
498
+ ```
499
+
500
+ **Response:**
501
+ ```json
502
+ {
503
+ "message": "Logged out successfully"
504
+ }
505
+ ```
506
+
507
+ ### Logout All Devices
508
+
509
+ Terminates ALL sessions for the authenticated user (logs out everywhere).
510
+
511
+ ```bash
512
+ POST /api/magic-sessionmanager/logout-all
513
+ Authorization: Bearer <JWT>
514
+ ```
515
+
516
+ **Response:**
517
+ ```json
518
+ {
519
+ "message": "Logged out from all devices successfully"
520
+ }
521
+ ```
522
+
523
+ ### Terminate Specific Session
524
+
525
+ Terminates a specific session (not the current one). Useful for "Log out other devices".
526
+
527
+ ```bash
528
+ DELETE /api/magic-sessionmanager/my-sessions/:sessionId
529
+ Authorization: Bearer <JWT>
530
+ ```
531
+
532
+ **Response:**
533
+ ```json
534
+ {
535
+ "message": "Session abc123xyz terminated successfully",
536
+ "success": true
537
+ }
538
+ ```
539
+
540
+ **Error (trying to terminate current session):**
541
+ ```json
542
+ {
543
+ "error": {
544
+ "status": 400,
545
+ "message": "Cannot terminate current session. Use /logout instead."
546
+ }
547
+ }
548
+ ```
549
+
550
+ ---
551
+
552
+ ## 📋 Admin-API Endpoints (For Admin Panel)
553
+
554
+ These endpoints require admin authentication.
555
+
556
+ ### Get All Sessions
396
557
 
397
558
  ```bash
398
- # Get all active sessions
399
559
  GET /magic-sessionmanager/sessions
400
560
  ```
401
561
 
402
- ### Logout
562
+ ### Get Active Sessions Only
403
563
 
404
564
  ```bash
405
- # Logout current user
406
- POST /api/auth/logout
565
+ GET /magic-sessionmanager/sessions/active
407
566
  ```
408
567
 
409
- ### Force Logout
568
+ ### Force Terminate Session
410
569
 
411
570
  ```bash
412
- # Admin force-logout a session
413
571
  POST /magic-sessionmanager/sessions/:sessionId/terminate
414
572
  ```
415
573
 
416
- **That's all you need to know!**
574
+ ### Terminate All User Sessions
575
+
576
+ ```bash
577
+ POST /magic-sessionmanager/user/:userId/terminate-all
578
+ ```
579
+
580
+ ### Block/Unblock User
581
+
582
+ ```bash
583
+ POST /magic-sessionmanager/user/:userId/toggle-block
584
+ ```
585
+
586
+ ### Clean Inactive Sessions
587
+
588
+ ```bash
589
+ POST /magic-sessionmanager/sessions/clean-inactive
590
+ ```
417
591
 
418
592
  ---
419
593
 
@@ -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$3(token) {
209
+ function hashToken$4(token) {
210
210
  if (!token) return null;
211
211
  return crypto$1.createHash("sha256").update(token).digest("hex");
212
212
  }
@@ -214,10 +214,10 @@ var encryption = {
214
214
  encryptToken: encryptToken$2,
215
215
  decryptToken: decryptToken$3,
216
216
  generateSessionId: generateSessionId$1,
217
- hashToken: hashToken$3
217
+ hashToken: hashToken$4
218
218
  };
219
219
  const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
220
- const { hashToken: hashToken$2 } = encryption;
220
+ const { hashToken: hashToken$3 } = encryption;
221
221
  const lastTouchCache = /* @__PURE__ */ new Map();
222
222
  var lastSeen = ({ strapi: strapi2 }) => {
223
223
  return async (ctx, next) => {
@@ -233,7 +233,7 @@ var lastSeen = ({ strapi: strapi2 }) => {
233
233
  }
234
234
  let matchingSession = null;
235
235
  try {
236
- const currentTokenHash = hashToken$2(currentToken);
236
+ const currentTokenHash = hashToken$3(currentToken);
237
237
  matchingSession = await strapi2.documents(SESSION_UID$3).findFirst({
238
238
  filters: {
239
239
  tokenHash: currentTokenHash,
@@ -278,7 +278,7 @@ var lastSeen = ({ strapi: strapi2 }) => {
278
278
  };
279
279
  };
280
280
  const getClientIp = getClientIp_1;
281
- const { encryptToken: encryptToken$1, decryptToken: decryptToken$2, hashToken: hashToken$1 } = encryption;
281
+ const { encryptToken: encryptToken$1, decryptToken: decryptToken$2, hashToken: hashToken$2 } = encryption;
282
282
  const { createLogger: createLogger$3 } = logger;
283
283
  const SESSION_UID$2 = "plugin::magic-sessionmanager.session";
284
284
  const USER_UID$2 = "plugin::users-permissions.user";
@@ -563,8 +563,8 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
563
563
  if (matchingSession) {
564
564
  const encryptedToken = newAccessToken ? encryptToken$1(newAccessToken) : matchingSession.token;
565
565
  const encryptedRefreshToken = newRefreshToken ? encryptToken$1(newRefreshToken) : matchingSession.refreshToken;
566
- const newTokenHash = newAccessToken ? hashToken$1(newAccessToken) : matchingSession.tokenHash;
567
- const newRefreshTokenHash = newRefreshToken ? hashToken$1(newRefreshToken) : matchingSession.refreshTokenHash;
566
+ const newTokenHash = newAccessToken ? hashToken$2(newAccessToken) : matchingSession.tokenHash;
567
+ const newRefreshTokenHash = newRefreshToken ? hashToken$2(newRefreshToken) : matchingSession.refreshTokenHash;
568
568
  await strapi2.documents(SESSION_UID$2).update({
569
569
  documentId: matchingSession.documentId,
570
570
  data: {
@@ -1042,7 +1042,91 @@ var routes$1 = {
1042
1042
  admin,
1043
1043
  "content-api": contentApi
1044
1044
  };
1045
- const { decryptToken: decryptToken$1 } = encryption;
1045
+ function parseUserAgent$2(userAgent) {
1046
+ if (!userAgent) {
1047
+ return {
1048
+ deviceType: "unknown",
1049
+ browserName: "unknown",
1050
+ browserVersion: null,
1051
+ osName: "unknown",
1052
+ osVersion: null
1053
+ };
1054
+ }
1055
+ userAgent.toLowerCase();
1056
+ let deviceType = "desktop";
1057
+ if (/mobile|android.*mobile|iphone|ipod|blackberry|iemobile|opera mini|opera mobi/i.test(userAgent)) {
1058
+ deviceType = "mobile";
1059
+ } else if (/tablet|ipad|android(?!.*mobile)|kindle|silk/i.test(userAgent)) {
1060
+ deviceType = "tablet";
1061
+ } else if (/bot|crawl|spider|slurp|mediapartners/i.test(userAgent)) {
1062
+ deviceType = "bot";
1063
+ }
1064
+ let browserName = "unknown";
1065
+ let browserVersion = null;
1066
+ if (/edg\//i.test(userAgent)) {
1067
+ browserName = "Edge";
1068
+ browserVersion = extractVersion(userAgent, /edg\/(\d+[\.\d]*)/i);
1069
+ } else if (/opr\//i.test(userAgent) || /opera/i.test(userAgent)) {
1070
+ browserName = "Opera";
1071
+ browserVersion = extractVersion(userAgent, /(?:opr|opera)[\s\/](\d+[\.\d]*)/i);
1072
+ } else if (/chrome|crios/i.test(userAgent) && !/edg/i.test(userAgent)) {
1073
+ browserName = "Chrome";
1074
+ browserVersion = extractVersion(userAgent, /(?:chrome|crios)\/(\d+[\.\d]*)/i);
1075
+ } else if (/firefox|fxios/i.test(userAgent)) {
1076
+ browserName = "Firefox";
1077
+ browserVersion = extractVersion(userAgent, /(?:firefox|fxios)\/(\d+[\.\d]*)/i);
1078
+ } else if (/safari/i.test(userAgent) && !/chrome|chromium/i.test(userAgent)) {
1079
+ browserName = "Safari";
1080
+ browserVersion = extractVersion(userAgent, /version\/(\d+[\.\d]*)/i);
1081
+ } else if (/msie|trident/i.test(userAgent)) {
1082
+ browserName = "Internet Explorer";
1083
+ browserVersion = extractVersion(userAgent, /(?:msie |rv:)(\d+[\.\d]*)/i);
1084
+ }
1085
+ let osName = "unknown";
1086
+ let osVersion = null;
1087
+ if (/windows nt/i.test(userAgent)) {
1088
+ osName = "Windows";
1089
+ const winVersion = extractVersion(userAgent, /windows nt (\d+[\.\d]*)/i);
1090
+ const winVersionMap = {
1091
+ "10.0": "10/11",
1092
+ "6.3": "8.1",
1093
+ "6.2": "8",
1094
+ "6.1": "7",
1095
+ "6.0": "Vista",
1096
+ "5.1": "XP"
1097
+ };
1098
+ osVersion = winVersionMap[winVersion] || winVersion;
1099
+ } else if (/mac os x/i.test(userAgent)) {
1100
+ osName = "macOS";
1101
+ osVersion = extractVersion(userAgent, /mac os x (\d+[_\.\d]*)/i)?.replace(/_/g, ".");
1102
+ } else if (/iphone|ipad|ipod/i.test(userAgent)) {
1103
+ osName = "iOS";
1104
+ osVersion = extractVersion(userAgent, /os (\d+[_\.\d]*)/i)?.replace(/_/g, ".");
1105
+ } else if (/android/i.test(userAgent)) {
1106
+ osName = "Android";
1107
+ osVersion = extractVersion(userAgent, /android (\d+[\.\d]*)/i);
1108
+ } else if (/linux/i.test(userAgent)) {
1109
+ osName = "Linux";
1110
+ } else if (/cros/i.test(userAgent)) {
1111
+ osName = "Chrome OS";
1112
+ }
1113
+ return {
1114
+ deviceType,
1115
+ browserName,
1116
+ browserVersion,
1117
+ osName,
1118
+ osVersion
1119
+ };
1120
+ }
1121
+ function extractVersion(userAgent, regex) {
1122
+ const match = userAgent.match(regex);
1123
+ return match ? match[1] : null;
1124
+ }
1125
+ var userAgentParser = {
1126
+ parseUserAgent: parseUserAgent$2
1127
+ };
1128
+ const { decryptToken: decryptToken$1, hashToken: hashToken$1 } = encryption;
1129
+ const { parseUserAgent: parseUserAgent$1 } = userAgentParser;
1046
1130
  const SESSION_UID$1 = "plugin::magic-sessionmanager.session";
1047
1131
  const USER_UID$1 = "plugin::users-permissions.user";
1048
1132
  var session$3 = {
@@ -1088,12 +1172,13 @@ var session$3 = {
1088
1172
  * Get own sessions (authenticated user)
1089
1173
  * GET /api/magic-sessionmanager/my-sessions
1090
1174
  * Automatically uses the authenticated user's documentId
1091
- * Marks which session is the current one (based on JWT token)
1175
+ * Marks which session is the current one (based on JWT token hash)
1092
1176
  */
1093
1177
  async getOwnSessions(ctx) {
1094
1178
  try {
1095
1179
  const userId = ctx.state.user?.documentId;
1096
1180
  const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
1181
+ const currentTokenHash = currentToken ? hashToken$1(currentToken) : null;
1097
1182
  if (!userId) {
1098
1183
  return ctx.throw(401, "Unauthorized");
1099
1184
  }
@@ -1108,17 +1193,26 @@ var session$3 = {
1108
1193
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1109
1194
  const timeSinceActive = now - lastActiveTime;
1110
1195
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
1111
- let isCurrentSession = false;
1112
- if (session2.token && currentToken) {
1113
- try {
1114
- const decrypted = decryptToken$1(session2.token);
1115
- isCurrentSession = decrypted === currentToken;
1116
- } catch (err) {
1117
- }
1118
- }
1119
- const { token, refreshToken, ...sessionWithoutTokens } = session2;
1196
+ const isCurrentSession = currentTokenHash && session2.tokenHash === currentTokenHash;
1197
+ const parsedUA = parseUserAgent$1(session2.userAgent);
1198
+ const deviceType = session2.deviceType || parsedUA.deviceType;
1199
+ const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
1200
+ const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
1201
+ const {
1202
+ token,
1203
+ tokenHash,
1204
+ refreshToken,
1205
+ refreshTokenHash,
1206
+ locale,
1207
+ publishedAt,
1208
+ // Remove Strapi internal fields
1209
+ ...sessionWithoutTokens
1210
+ } = session2;
1120
1211
  return {
1121
1212
  ...sessionWithoutTokens,
1213
+ deviceType,
1214
+ browserName,
1215
+ osName,
1122
1216
  isCurrentSession,
1123
1217
  isTrulyActive,
1124
1218
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
@@ -1180,21 +1274,14 @@ var session$3 = {
1180
1274
  return ctx.throw(401, "Unauthorized");
1181
1275
  }
1182
1276
  const sessionService = strapi.plugin("magic-sessionmanager").service("session");
1183
- const sessions = await strapi.documents(SESSION_UID$1).findMany({
1277
+ const currentTokenHash = hashToken$1(token);
1278
+ const matchingSession = await strapi.documents(SESSION_UID$1).findFirst({
1184
1279
  filters: {
1185
1280
  user: { documentId: userId },
1281
+ tokenHash: currentTokenHash,
1186
1282
  isActive: true
1187
1283
  }
1188
1284
  });
1189
- const matchingSession = sessions.find((session2) => {
1190
- if (!session2.token) return false;
1191
- try {
1192
- const decrypted = decryptToken$1(session2.token);
1193
- return decrypted === token;
1194
- } catch (err) {
1195
- return false;
1196
- }
1197
- });
1198
1285
  if (matchingSession) {
1199
1286
  await sessionService.terminateSession({ sessionId: matchingSession.documentId });
1200
1287
  strapi.log.info(`[magic-sessionmanager] User ${userId} logged out (session ${matchingSession.documentId})`);
@@ -1229,7 +1316,7 @@ var session$3 = {
1229
1316
  }
1230
1317
  },
1231
1318
  /**
1232
- * Get current session info based on JWT token
1319
+ * Get current session info based on JWT token hash
1233
1320
  * GET /api/magic-sessionmanager/current-session
1234
1321
  * Returns the session associated with the current JWT token
1235
1322
  */
@@ -1240,21 +1327,14 @@ var session$3 = {
1240
1327
  if (!userId || !token) {
1241
1328
  return ctx.throw(401, "Unauthorized");
1242
1329
  }
1243
- const sessions = await strapi.documents(SESSION_UID$1).findMany({
1330
+ const currentTokenHash = hashToken$1(token);
1331
+ const currentSession = await strapi.documents(SESSION_UID$1).findFirst({
1244
1332
  filters: {
1245
1333
  user: { documentId: userId },
1334
+ tokenHash: currentTokenHash,
1246
1335
  isActive: true
1247
1336
  }
1248
1337
  });
1249
- const currentSession = sessions.find((session2) => {
1250
- if (!session2.token) return false;
1251
- try {
1252
- const decrypted = decryptToken$1(session2.token);
1253
- return decrypted === token;
1254
- } catch (err) {
1255
- return false;
1256
- }
1257
- });
1258
1338
  if (!currentSession) {
1259
1339
  return ctx.notFound("Current session not found");
1260
1340
  }
@@ -1263,10 +1343,25 @@ var session$3 = {
1263
1343
  const now = /* @__PURE__ */ new Date();
1264
1344
  const lastActiveTime = currentSession.lastActive ? new Date(currentSession.lastActive) : new Date(currentSession.loginTime);
1265
1345
  const timeSinceActive = now - lastActiveTime;
1266
- const { token: _, refreshToken: __, ...sessionWithoutTokens } = currentSession;
1346
+ const parsedUA = parseUserAgent$1(currentSession.userAgent);
1347
+ const deviceType = currentSession.deviceType || parsedUA.deviceType;
1348
+ const browserName = currentSession.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
1349
+ const osName = currentSession.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
1350
+ const {
1351
+ token: _,
1352
+ tokenHash: _th,
1353
+ refreshToken: __,
1354
+ refreshTokenHash: _rth,
1355
+ locale: _l,
1356
+ publishedAt: _p,
1357
+ ...sessionWithoutTokens
1358
+ } = currentSession;
1267
1359
  ctx.body = {
1268
1360
  data: {
1269
1361
  ...sessionWithoutTokens,
1362
+ deviceType,
1363
+ browserName,
1364
+ osName,
1270
1365
  isCurrentSession: true,
1271
1366
  isTrulyActive: timeSinceActive < inactivityTimeout,
1272
1367
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
@@ -1287,6 +1382,7 @@ var session$3 = {
1287
1382
  const userId = ctx.state.user?.documentId;
1288
1383
  const { sessionId } = ctx.params;
1289
1384
  const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
1385
+ const currentTokenHash = currentToken ? hashToken$1(currentToken) : null;
1290
1386
  if (!userId) {
1291
1387
  return ctx.throw(401, "Unauthorized");
1292
1388
  }
@@ -1305,14 +1401,8 @@ var session$3 = {
1305
1401
  strapi.log.warn(`[magic-sessionmanager] Security: User ${userId} tried to terminate session ${sessionId} of user ${sessionUserId}`);
1306
1402
  return ctx.forbidden("You can only terminate your own sessions");
1307
1403
  }
1308
- if (sessionToTerminate.token && currentToken) {
1309
- try {
1310
- const decrypted = decryptToken$1(sessionToTerminate.token);
1311
- if (decrypted === currentToken) {
1312
- return ctx.badRequest("Cannot terminate current session. Use /logout instead.");
1313
- }
1314
- } catch (err) {
1315
- }
1404
+ if (currentTokenHash && sessionToTerminate.tokenHash === currentTokenHash) {
1405
+ return ctx.badRequest("Cannot terminate current session. Use /logout instead.");
1316
1406
  }
1317
1407
  const sessionService = strapi.plugin("magic-sessionmanager").service("session");
1318
1408
  await sessionService.terminateSession({ sessionId });
@@ -1807,6 +1897,7 @@ var controllers$1 = {
1807
1897
  };
1808
1898
  const { encryptToken, decryptToken, generateSessionId, hashToken } = encryption;
1809
1899
  const { createLogger: createLogger$1 } = logger;
1900
+ const { parseUserAgent } = userAgentParser;
1810
1901
  const SESSION_UID = "plugin::magic-sessionmanager.session";
1811
1902
  const USER_UID = "plugin::users-permissions.user";
1812
1903
  var session$1 = ({ strapi: strapi2 }) => {
@@ -1921,9 +2012,24 @@ var session$1 = ({ strapi: strapi2 }) => {
1921
2012
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1922
2013
  const timeSinceActive = now - lastActiveTime;
1923
2014
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
1924
- const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
2015
+ const parsedUA = parseUserAgent(session2.userAgent);
2016
+ const deviceType = session2.deviceType || parsedUA.deviceType;
2017
+ const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2018
+ const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2019
+ const {
2020
+ token,
2021
+ tokenHash,
2022
+ refreshToken,
2023
+ refreshTokenHash,
2024
+ locale,
2025
+ publishedAt,
2026
+ ...safeSession
2027
+ } = session2;
1925
2028
  return {
1926
2029
  ...safeSession,
2030
+ deviceType,
2031
+ browserName,
2032
+ osName,
1927
2033
  isTrulyActive,
1928
2034
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1929
2035
  };
@@ -1952,9 +2058,24 @@ var session$1 = ({ strapi: strapi2 }) => {
1952
2058
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1953
2059
  const timeSinceActive = now - lastActiveTime;
1954
2060
  const isTrulyActive = timeSinceActive < inactivityTimeout;
1955
- const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
2061
+ const parsedUA = parseUserAgent(session2.userAgent);
2062
+ const deviceType = session2.deviceType || parsedUA.deviceType;
2063
+ const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2064
+ const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2065
+ const {
2066
+ token,
2067
+ tokenHash,
2068
+ refreshToken,
2069
+ refreshTokenHash,
2070
+ locale,
2071
+ publishedAt,
2072
+ ...safeSession
2073
+ } = session2;
1956
2074
  return {
1957
2075
  ...safeSession,
2076
+ deviceType,
2077
+ browserName,
2078
+ osName,
1958
2079
  isTrulyActive,
1959
2080
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1960
2081
  };
@@ -1991,9 +2112,24 @@ var session$1 = ({ strapi: strapi2 }) => {
1991
2112
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1992
2113
  const timeSinceActive = now - lastActiveTime;
1993
2114
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
1994
- const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
2115
+ const parsedUA = parseUserAgent(session2.userAgent);
2116
+ const deviceType = session2.deviceType || parsedUA.deviceType;
2117
+ const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2118
+ const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2119
+ const {
2120
+ token,
2121
+ tokenHash,
2122
+ refreshToken,
2123
+ refreshTokenHash,
2124
+ locale,
2125
+ publishedAt,
2126
+ ...safeSession
2127
+ } = session2;
1995
2128
  return {
1996
2129
  ...safeSession,
2130
+ deviceType,
2131
+ browserName,
2132
+ osName,
1997
2133
  isTrulyActive,
1998
2134
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1999
2135
  };
@@ -2110,7 +2246,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2110
2246
  }
2111
2247
  };
2112
2248
  };
2113
- const version = "4.2.9";
2249
+ const version = "4.2.10";
2114
2250
  const require$$2 = {
2115
2251
  version
2116
2252
  };
@@ -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$3(token) {
205
+ function hashToken$4(token) {
206
206
  if (!token) return null;
207
207
  return crypto$1.createHash("sha256").update(token).digest("hex");
208
208
  }
@@ -210,10 +210,10 @@ var encryption = {
210
210
  encryptToken: encryptToken$2,
211
211
  decryptToken: decryptToken$3,
212
212
  generateSessionId: generateSessionId$1,
213
- hashToken: hashToken$3
213
+ hashToken: hashToken$4
214
214
  };
215
215
  const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
216
- const { hashToken: hashToken$2 } = encryption;
216
+ const { hashToken: hashToken$3 } = encryption;
217
217
  const lastTouchCache = /* @__PURE__ */ new Map();
218
218
  var lastSeen = ({ strapi: strapi2 }) => {
219
219
  return async (ctx, next) => {
@@ -229,7 +229,7 @@ var lastSeen = ({ strapi: strapi2 }) => {
229
229
  }
230
230
  let matchingSession = null;
231
231
  try {
232
- const currentTokenHash = hashToken$2(currentToken);
232
+ const currentTokenHash = hashToken$3(currentToken);
233
233
  matchingSession = await strapi2.documents(SESSION_UID$3).findFirst({
234
234
  filters: {
235
235
  tokenHash: currentTokenHash,
@@ -274,7 +274,7 @@ var lastSeen = ({ strapi: strapi2 }) => {
274
274
  };
275
275
  };
276
276
  const getClientIp = getClientIp_1;
277
- const { encryptToken: encryptToken$1, decryptToken: decryptToken$2, hashToken: hashToken$1 } = encryption;
277
+ const { encryptToken: encryptToken$1, decryptToken: decryptToken$2, hashToken: hashToken$2 } = encryption;
278
278
  const { createLogger: createLogger$3 } = logger;
279
279
  const SESSION_UID$2 = "plugin::magic-sessionmanager.session";
280
280
  const USER_UID$2 = "plugin::users-permissions.user";
@@ -559,8 +559,8 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
559
559
  if (matchingSession) {
560
560
  const encryptedToken = newAccessToken ? encryptToken$1(newAccessToken) : matchingSession.token;
561
561
  const encryptedRefreshToken = newRefreshToken ? encryptToken$1(newRefreshToken) : matchingSession.refreshToken;
562
- const newTokenHash = newAccessToken ? hashToken$1(newAccessToken) : matchingSession.tokenHash;
563
- const newRefreshTokenHash = newRefreshToken ? hashToken$1(newRefreshToken) : matchingSession.refreshTokenHash;
562
+ const newTokenHash = newAccessToken ? hashToken$2(newAccessToken) : matchingSession.tokenHash;
563
+ const newRefreshTokenHash = newRefreshToken ? hashToken$2(newRefreshToken) : matchingSession.refreshTokenHash;
564
564
  await strapi2.documents(SESSION_UID$2).update({
565
565
  documentId: matchingSession.documentId,
566
566
  data: {
@@ -1038,7 +1038,91 @@ var routes$1 = {
1038
1038
  admin,
1039
1039
  "content-api": contentApi
1040
1040
  };
1041
- const { decryptToken: decryptToken$1 } = encryption;
1041
+ function parseUserAgent$2(userAgent) {
1042
+ if (!userAgent) {
1043
+ return {
1044
+ deviceType: "unknown",
1045
+ browserName: "unknown",
1046
+ browserVersion: null,
1047
+ osName: "unknown",
1048
+ osVersion: null
1049
+ };
1050
+ }
1051
+ userAgent.toLowerCase();
1052
+ let deviceType = "desktop";
1053
+ if (/mobile|android.*mobile|iphone|ipod|blackberry|iemobile|opera mini|opera mobi/i.test(userAgent)) {
1054
+ deviceType = "mobile";
1055
+ } else if (/tablet|ipad|android(?!.*mobile)|kindle|silk/i.test(userAgent)) {
1056
+ deviceType = "tablet";
1057
+ } else if (/bot|crawl|spider|slurp|mediapartners/i.test(userAgent)) {
1058
+ deviceType = "bot";
1059
+ }
1060
+ let browserName = "unknown";
1061
+ let browserVersion = null;
1062
+ if (/edg\//i.test(userAgent)) {
1063
+ browserName = "Edge";
1064
+ browserVersion = extractVersion(userAgent, /edg\/(\d+[\.\d]*)/i);
1065
+ } else if (/opr\//i.test(userAgent) || /opera/i.test(userAgent)) {
1066
+ browserName = "Opera";
1067
+ browserVersion = extractVersion(userAgent, /(?:opr|opera)[\s\/](\d+[\.\d]*)/i);
1068
+ } else if (/chrome|crios/i.test(userAgent) && !/edg/i.test(userAgent)) {
1069
+ browserName = "Chrome";
1070
+ browserVersion = extractVersion(userAgent, /(?:chrome|crios)\/(\d+[\.\d]*)/i);
1071
+ } else if (/firefox|fxios/i.test(userAgent)) {
1072
+ browserName = "Firefox";
1073
+ browserVersion = extractVersion(userAgent, /(?:firefox|fxios)\/(\d+[\.\d]*)/i);
1074
+ } else if (/safari/i.test(userAgent) && !/chrome|chromium/i.test(userAgent)) {
1075
+ browserName = "Safari";
1076
+ browserVersion = extractVersion(userAgent, /version\/(\d+[\.\d]*)/i);
1077
+ } else if (/msie|trident/i.test(userAgent)) {
1078
+ browserName = "Internet Explorer";
1079
+ browserVersion = extractVersion(userAgent, /(?:msie |rv:)(\d+[\.\d]*)/i);
1080
+ }
1081
+ let osName = "unknown";
1082
+ let osVersion = null;
1083
+ if (/windows nt/i.test(userAgent)) {
1084
+ osName = "Windows";
1085
+ const winVersion = extractVersion(userAgent, /windows nt (\d+[\.\d]*)/i);
1086
+ const winVersionMap = {
1087
+ "10.0": "10/11",
1088
+ "6.3": "8.1",
1089
+ "6.2": "8",
1090
+ "6.1": "7",
1091
+ "6.0": "Vista",
1092
+ "5.1": "XP"
1093
+ };
1094
+ osVersion = winVersionMap[winVersion] || winVersion;
1095
+ } else if (/mac os x/i.test(userAgent)) {
1096
+ osName = "macOS";
1097
+ osVersion = extractVersion(userAgent, /mac os x (\d+[_\.\d]*)/i)?.replace(/_/g, ".");
1098
+ } else if (/iphone|ipad|ipod/i.test(userAgent)) {
1099
+ osName = "iOS";
1100
+ osVersion = extractVersion(userAgent, /os (\d+[_\.\d]*)/i)?.replace(/_/g, ".");
1101
+ } else if (/android/i.test(userAgent)) {
1102
+ osName = "Android";
1103
+ osVersion = extractVersion(userAgent, /android (\d+[\.\d]*)/i);
1104
+ } else if (/linux/i.test(userAgent)) {
1105
+ osName = "Linux";
1106
+ } else if (/cros/i.test(userAgent)) {
1107
+ osName = "Chrome OS";
1108
+ }
1109
+ return {
1110
+ deviceType,
1111
+ browserName,
1112
+ browserVersion,
1113
+ osName,
1114
+ osVersion
1115
+ };
1116
+ }
1117
+ function extractVersion(userAgent, regex) {
1118
+ const match = userAgent.match(regex);
1119
+ return match ? match[1] : null;
1120
+ }
1121
+ var userAgentParser = {
1122
+ parseUserAgent: parseUserAgent$2
1123
+ };
1124
+ const { decryptToken: decryptToken$1, hashToken: hashToken$1 } = encryption;
1125
+ const { parseUserAgent: parseUserAgent$1 } = userAgentParser;
1042
1126
  const SESSION_UID$1 = "plugin::magic-sessionmanager.session";
1043
1127
  const USER_UID$1 = "plugin::users-permissions.user";
1044
1128
  var session$3 = {
@@ -1084,12 +1168,13 @@ var session$3 = {
1084
1168
  * Get own sessions (authenticated user)
1085
1169
  * GET /api/magic-sessionmanager/my-sessions
1086
1170
  * Automatically uses the authenticated user's documentId
1087
- * Marks which session is the current one (based on JWT token)
1171
+ * Marks which session is the current one (based on JWT token hash)
1088
1172
  */
1089
1173
  async getOwnSessions(ctx) {
1090
1174
  try {
1091
1175
  const userId = ctx.state.user?.documentId;
1092
1176
  const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
1177
+ const currentTokenHash = currentToken ? hashToken$1(currentToken) : null;
1093
1178
  if (!userId) {
1094
1179
  return ctx.throw(401, "Unauthorized");
1095
1180
  }
@@ -1104,17 +1189,26 @@ var session$3 = {
1104
1189
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1105
1190
  const timeSinceActive = now - lastActiveTime;
1106
1191
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
1107
- let isCurrentSession = false;
1108
- if (session2.token && currentToken) {
1109
- try {
1110
- const decrypted = decryptToken$1(session2.token);
1111
- isCurrentSession = decrypted === currentToken;
1112
- } catch (err) {
1113
- }
1114
- }
1115
- const { token, refreshToken, ...sessionWithoutTokens } = session2;
1192
+ const isCurrentSession = currentTokenHash && session2.tokenHash === currentTokenHash;
1193
+ const parsedUA = parseUserAgent$1(session2.userAgent);
1194
+ const deviceType = session2.deviceType || parsedUA.deviceType;
1195
+ const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
1196
+ const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
1197
+ const {
1198
+ token,
1199
+ tokenHash,
1200
+ refreshToken,
1201
+ refreshTokenHash,
1202
+ locale,
1203
+ publishedAt,
1204
+ // Remove Strapi internal fields
1205
+ ...sessionWithoutTokens
1206
+ } = session2;
1116
1207
  return {
1117
1208
  ...sessionWithoutTokens,
1209
+ deviceType,
1210
+ browserName,
1211
+ osName,
1118
1212
  isCurrentSession,
1119
1213
  isTrulyActive,
1120
1214
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
@@ -1176,21 +1270,14 @@ var session$3 = {
1176
1270
  return ctx.throw(401, "Unauthorized");
1177
1271
  }
1178
1272
  const sessionService = strapi.plugin("magic-sessionmanager").service("session");
1179
- const sessions = await strapi.documents(SESSION_UID$1).findMany({
1273
+ const currentTokenHash = hashToken$1(token);
1274
+ const matchingSession = await strapi.documents(SESSION_UID$1).findFirst({
1180
1275
  filters: {
1181
1276
  user: { documentId: userId },
1277
+ tokenHash: currentTokenHash,
1182
1278
  isActive: true
1183
1279
  }
1184
1280
  });
1185
- const matchingSession = sessions.find((session2) => {
1186
- if (!session2.token) return false;
1187
- try {
1188
- const decrypted = decryptToken$1(session2.token);
1189
- return decrypted === token;
1190
- } catch (err) {
1191
- return false;
1192
- }
1193
- });
1194
1281
  if (matchingSession) {
1195
1282
  await sessionService.terminateSession({ sessionId: matchingSession.documentId });
1196
1283
  strapi.log.info(`[magic-sessionmanager] User ${userId} logged out (session ${matchingSession.documentId})`);
@@ -1225,7 +1312,7 @@ var session$3 = {
1225
1312
  }
1226
1313
  },
1227
1314
  /**
1228
- * Get current session info based on JWT token
1315
+ * Get current session info based on JWT token hash
1229
1316
  * GET /api/magic-sessionmanager/current-session
1230
1317
  * Returns the session associated with the current JWT token
1231
1318
  */
@@ -1236,21 +1323,14 @@ var session$3 = {
1236
1323
  if (!userId || !token) {
1237
1324
  return ctx.throw(401, "Unauthorized");
1238
1325
  }
1239
- const sessions = await strapi.documents(SESSION_UID$1).findMany({
1326
+ const currentTokenHash = hashToken$1(token);
1327
+ const currentSession = await strapi.documents(SESSION_UID$1).findFirst({
1240
1328
  filters: {
1241
1329
  user: { documentId: userId },
1330
+ tokenHash: currentTokenHash,
1242
1331
  isActive: true
1243
1332
  }
1244
1333
  });
1245
- const currentSession = sessions.find((session2) => {
1246
- if (!session2.token) return false;
1247
- try {
1248
- const decrypted = decryptToken$1(session2.token);
1249
- return decrypted === token;
1250
- } catch (err) {
1251
- return false;
1252
- }
1253
- });
1254
1334
  if (!currentSession) {
1255
1335
  return ctx.notFound("Current session not found");
1256
1336
  }
@@ -1259,10 +1339,25 @@ var session$3 = {
1259
1339
  const now = /* @__PURE__ */ new Date();
1260
1340
  const lastActiveTime = currentSession.lastActive ? new Date(currentSession.lastActive) : new Date(currentSession.loginTime);
1261
1341
  const timeSinceActive = now - lastActiveTime;
1262
- const { token: _, refreshToken: __, ...sessionWithoutTokens } = currentSession;
1342
+ const parsedUA = parseUserAgent$1(currentSession.userAgent);
1343
+ const deviceType = currentSession.deviceType || parsedUA.deviceType;
1344
+ const browserName = currentSession.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
1345
+ const osName = currentSession.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
1346
+ const {
1347
+ token: _,
1348
+ tokenHash: _th,
1349
+ refreshToken: __,
1350
+ refreshTokenHash: _rth,
1351
+ locale: _l,
1352
+ publishedAt: _p,
1353
+ ...sessionWithoutTokens
1354
+ } = currentSession;
1263
1355
  ctx.body = {
1264
1356
  data: {
1265
1357
  ...sessionWithoutTokens,
1358
+ deviceType,
1359
+ browserName,
1360
+ osName,
1266
1361
  isCurrentSession: true,
1267
1362
  isTrulyActive: timeSinceActive < inactivityTimeout,
1268
1363
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
@@ -1283,6 +1378,7 @@ var session$3 = {
1283
1378
  const userId = ctx.state.user?.documentId;
1284
1379
  const { sessionId } = ctx.params;
1285
1380
  const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
1381
+ const currentTokenHash = currentToken ? hashToken$1(currentToken) : null;
1286
1382
  if (!userId) {
1287
1383
  return ctx.throw(401, "Unauthorized");
1288
1384
  }
@@ -1301,14 +1397,8 @@ var session$3 = {
1301
1397
  strapi.log.warn(`[magic-sessionmanager] Security: User ${userId} tried to terminate session ${sessionId} of user ${sessionUserId}`);
1302
1398
  return ctx.forbidden("You can only terminate your own sessions");
1303
1399
  }
1304
- if (sessionToTerminate.token && currentToken) {
1305
- try {
1306
- const decrypted = decryptToken$1(sessionToTerminate.token);
1307
- if (decrypted === currentToken) {
1308
- return ctx.badRequest("Cannot terminate current session. Use /logout instead.");
1309
- }
1310
- } catch (err) {
1311
- }
1400
+ if (currentTokenHash && sessionToTerminate.tokenHash === currentTokenHash) {
1401
+ return ctx.badRequest("Cannot terminate current session. Use /logout instead.");
1312
1402
  }
1313
1403
  const sessionService = strapi.plugin("magic-sessionmanager").service("session");
1314
1404
  await sessionService.terminateSession({ sessionId });
@@ -1803,6 +1893,7 @@ var controllers$1 = {
1803
1893
  };
1804
1894
  const { encryptToken, decryptToken, generateSessionId, hashToken } = encryption;
1805
1895
  const { createLogger: createLogger$1 } = logger;
1896
+ const { parseUserAgent } = userAgentParser;
1806
1897
  const SESSION_UID = "plugin::magic-sessionmanager.session";
1807
1898
  const USER_UID = "plugin::users-permissions.user";
1808
1899
  var session$1 = ({ strapi: strapi2 }) => {
@@ -1917,9 +2008,24 @@ var session$1 = ({ strapi: strapi2 }) => {
1917
2008
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1918
2009
  const timeSinceActive = now - lastActiveTime;
1919
2010
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
1920
- const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
2011
+ const parsedUA = parseUserAgent(session2.userAgent);
2012
+ const deviceType = session2.deviceType || parsedUA.deviceType;
2013
+ const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2014
+ const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2015
+ const {
2016
+ token,
2017
+ tokenHash,
2018
+ refreshToken,
2019
+ refreshTokenHash,
2020
+ locale,
2021
+ publishedAt,
2022
+ ...safeSession
2023
+ } = session2;
1921
2024
  return {
1922
2025
  ...safeSession,
2026
+ deviceType,
2027
+ browserName,
2028
+ osName,
1923
2029
  isTrulyActive,
1924
2030
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1925
2031
  };
@@ -1948,9 +2054,24 @@ var session$1 = ({ strapi: strapi2 }) => {
1948
2054
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1949
2055
  const timeSinceActive = now - lastActiveTime;
1950
2056
  const isTrulyActive = timeSinceActive < inactivityTimeout;
1951
- const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
2057
+ const parsedUA = parseUserAgent(session2.userAgent);
2058
+ const deviceType = session2.deviceType || parsedUA.deviceType;
2059
+ const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2060
+ const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2061
+ const {
2062
+ token,
2063
+ tokenHash,
2064
+ refreshToken,
2065
+ refreshTokenHash,
2066
+ locale,
2067
+ publishedAt,
2068
+ ...safeSession
2069
+ } = session2;
1952
2070
  return {
1953
2071
  ...safeSession,
2072
+ deviceType,
2073
+ browserName,
2074
+ osName,
1954
2075
  isTrulyActive,
1955
2076
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1956
2077
  };
@@ -1987,9 +2108,24 @@ var session$1 = ({ strapi: strapi2 }) => {
1987
2108
  const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
1988
2109
  const timeSinceActive = now - lastActiveTime;
1989
2110
  const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
1990
- const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
2111
+ const parsedUA = parseUserAgent(session2.userAgent);
2112
+ const deviceType = session2.deviceType || parsedUA.deviceType;
2113
+ const browserName = session2.browserName || (parsedUA.browserVersion ? `${parsedUA.browserName} ${parsedUA.browserVersion}` : parsedUA.browserName);
2114
+ const osName = session2.osName || (parsedUA.osVersion ? `${parsedUA.osName} ${parsedUA.osVersion}` : parsedUA.osName);
2115
+ const {
2116
+ token,
2117
+ tokenHash,
2118
+ refreshToken,
2119
+ refreshTokenHash,
2120
+ locale,
2121
+ publishedAt,
2122
+ ...safeSession
2123
+ } = session2;
1991
2124
  return {
1992
2125
  ...safeSession,
2126
+ deviceType,
2127
+ browserName,
2128
+ osName,
1993
2129
  isTrulyActive,
1994
2130
  minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
1995
2131
  };
@@ -2106,7 +2242,7 @@ var session$1 = ({ strapi: strapi2 }) => {
2106
2242
  }
2107
2243
  };
2108
2244
  };
2109
- const version = "4.2.9";
2245
+ const version = "4.2.10";
2110
2246
  const require$$2 = {
2111
2247
  version
2112
2248
  };
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.2.10",
2
+ "version": "4.2.11",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",