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 +183 -9
- package/dist/server/index.js +188 -52
- package/dist/server/index.mjs +188 -52
- package/package.json +1 -1
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
|
-
## 📋
|
|
393
|
+
## 📋 Content-API Endpoints (For Frontend/Apps)
|
|
394
394
|
|
|
395
|
-
|
|
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
|
-
###
|
|
562
|
+
### Get Active Sessions Only
|
|
403
563
|
|
|
404
564
|
```bash
|
|
405
|
-
|
|
406
|
-
POST /api/auth/logout
|
|
565
|
+
GET /magic-sessionmanager/sessions/active
|
|
407
566
|
```
|
|
408
567
|
|
|
409
|
-
### Force
|
|
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
|
-
|
|
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
|
|
package/dist/server/index.js
CHANGED
|
@@ -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$
|
|
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$
|
|
217
|
+
hashToken: hashToken$4
|
|
218
218
|
};
|
|
219
219
|
const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
|
|
220
|
-
const { hashToken: hashToken$
|
|
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$
|
|
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$
|
|
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$
|
|
567
|
-
const newRefreshTokenHash = newRefreshToken ? hashToken$
|
|
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
|
-
|
|
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
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
1309
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
2249
|
+
const version = "4.2.10";
|
|
2114
2250
|
const require$$2 = {
|
|
2115
2251
|
version
|
|
2116
2252
|
};
|
package/dist/server/index.mjs
CHANGED
|
@@ -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$
|
|
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$
|
|
213
|
+
hashToken: hashToken$4
|
|
214
214
|
};
|
|
215
215
|
const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
|
|
216
|
-
const { hashToken: hashToken$
|
|
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$
|
|
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$
|
|
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$
|
|
563
|
-
const newRefreshTokenHash = newRefreshToken ? hashToken$
|
|
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
|
-
|
|
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
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
1305
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
2245
|
+
const version = "4.2.10";
|
|
2110
2246
|
const require$$2 = {
|
|
2111
2247
|
version
|
|
2112
2248
|
};
|
package/package.json
CHANGED