strapi-plugin-magic-sessionmanager 4.2.8 → 4.2.10
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/dist/server/index.js +99 -66
- package/dist/server/index.mjs +99 -66
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -179,7 +179,7 @@ function encryptToken$2(token) {
|
|
|
179
179
|
throw new Error("Failed to encrypt token");
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
-
function decryptToken$
|
|
182
|
+
function decryptToken$3(encryptedToken) {
|
|
183
183
|
if (!encryptedToken) return null;
|
|
184
184
|
try {
|
|
185
185
|
const key = getEncryptionKey();
|
|
@@ -206,15 +206,20 @@ 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) {
|
|
210
|
+
if (!token) return null;
|
|
211
|
+
return crypto$1.createHash("sha256").update(token).digest("hex");
|
|
212
|
+
}
|
|
209
213
|
var encryption = {
|
|
210
214
|
encryptToken: encryptToken$2,
|
|
211
|
-
decryptToken: decryptToken$
|
|
212
|
-
generateSessionId: generateSessionId$1
|
|
215
|
+
decryptToken: decryptToken$3,
|
|
216
|
+
generateSessionId: generateSessionId$1,
|
|
217
|
+
hashToken: hashToken$3
|
|
213
218
|
};
|
|
214
219
|
const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
|
|
215
|
-
const {
|
|
220
|
+
const { hashToken: hashToken$2 } = encryption;
|
|
216
221
|
const lastTouchCache = /* @__PURE__ */ new Map();
|
|
217
|
-
var lastSeen = ({ strapi: strapi2
|
|
222
|
+
var lastSeen = ({ strapi: strapi2 }) => {
|
|
218
223
|
return async (ctx, next) => {
|
|
219
224
|
const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
|
|
220
225
|
if (!currentToken) {
|
|
@@ -227,58 +232,26 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
227
232
|
return;
|
|
228
233
|
}
|
|
229
234
|
let matchingSession = null;
|
|
230
|
-
let userId = null;
|
|
231
235
|
try {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (!activeSessions || activeSessions.length === 0) {
|
|
241
|
-
strapi2.log.info(`[magic-sessionmanager] [BLOCKED] User ${userId} has no active sessions`);
|
|
242
|
-
return ctx.unauthorized("All sessions have been terminated. Please login again.");
|
|
243
|
-
}
|
|
244
|
-
for (const session2 of activeSessions) {
|
|
245
|
-
if (!session2.token) continue;
|
|
246
|
-
try {
|
|
247
|
-
const decrypted = decryptToken$3(session2.token);
|
|
248
|
-
if (decrypted === currentToken) {
|
|
249
|
-
matchingSession = session2;
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
} catch (err) {
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (!matchingSession) {
|
|
256
|
-
strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Session for user ${userId} has been terminated`);
|
|
257
|
-
return ctx.unauthorized("This session has been terminated. Please login again.");
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
const allActiveSessions = await strapi2.documents(SESSION_UID$3).findMany({
|
|
261
|
-
filters: { isActive: true },
|
|
262
|
-
populate: { user: { fields: ["documentId"] } },
|
|
263
|
-
limit: 500
|
|
264
|
-
// Reasonable limit for performance
|
|
265
|
-
});
|
|
266
|
-
for (const session2 of allActiveSessions) {
|
|
267
|
-
if (!session2.token) continue;
|
|
268
|
-
try {
|
|
269
|
-
const decrypted = decryptToken$3(session2.token);
|
|
270
|
-
if (decrypted === currentToken) {
|
|
271
|
-
matchingSession = session2;
|
|
272
|
-
userId = session2.user?.documentId;
|
|
273
|
-
break;
|
|
274
|
-
}
|
|
275
|
-
} catch (err) {
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
236
|
+
const currentTokenHash = hashToken$2(currentToken);
|
|
237
|
+
matchingSession = await strapi2.documents(SESSION_UID$3).findFirst({
|
|
238
|
+
filters: {
|
|
239
|
+
tokenHash: currentTokenHash,
|
|
240
|
+
isActive: true
|
|
241
|
+
},
|
|
242
|
+
populate: { user: { fields: ["documentId"] } }
|
|
243
|
+
});
|
|
279
244
|
if (matchingSession) {
|
|
280
245
|
ctx.state.sessionId = matchingSession.documentId;
|
|
281
246
|
ctx.state.currentSession = matchingSession;
|
|
247
|
+
if (matchingSession.user?.documentId) {
|
|
248
|
+
ctx.state.sessionUserId = matchingSession.user.documentId;
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
if (ctx.state.user && ctx.state.user.documentId) {
|
|
252
|
+
strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Session terminated for user ${ctx.state.user.documentId}`);
|
|
253
|
+
return ctx.unauthorized("This session has been terminated. Please login again.");
|
|
254
|
+
}
|
|
282
255
|
}
|
|
283
256
|
} catch (err) {
|
|
284
257
|
strapi2.log.debug("[magic-sessionmanager] Error checking session:", err.message);
|
|
@@ -305,7 +278,7 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
305
278
|
};
|
|
306
279
|
};
|
|
307
280
|
const getClientIp = getClientIp_1;
|
|
308
|
-
const { encryptToken: encryptToken$1, decryptToken: decryptToken$2 } = encryption;
|
|
281
|
+
const { encryptToken: encryptToken$1, decryptToken: decryptToken$2, hashToken: hashToken$1 } = encryption;
|
|
309
282
|
const { createLogger: createLogger$3 } = logger;
|
|
310
283
|
const SESSION_UID$2 = "plugin::magic-sessionmanager.session";
|
|
311
284
|
const USER_UID$2 = "plugin::users-permissions.user";
|
|
@@ -313,6 +286,7 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
313
286
|
const log = createLogger$3(strapi2);
|
|
314
287
|
log.info("[START] Bootstrap starting...");
|
|
315
288
|
try {
|
|
289
|
+
await ensureTokenHashIndex(strapi2, log);
|
|
316
290
|
const licenseGuardService = strapi2.plugin("magic-sessionmanager").service("license-guard");
|
|
317
291
|
setTimeout(async () => {
|
|
318
292
|
const licenseStatus = await licenseGuardService.initialize();
|
|
@@ -589,11 +563,15 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
589
563
|
if (matchingSession) {
|
|
590
564
|
const encryptedToken = newAccessToken ? encryptToken$1(newAccessToken) : matchingSession.token;
|
|
591
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;
|
|
592
568
|
await strapi2.documents(SESSION_UID$2).update({
|
|
593
569
|
documentId: matchingSession.documentId,
|
|
594
570
|
data: {
|
|
595
571
|
token: encryptedToken,
|
|
572
|
+
tokenHash: newTokenHash,
|
|
596
573
|
refreshToken: encryptedRefreshToken,
|
|
574
|
+
refreshTokenHash: newRefreshTokenHash,
|
|
597
575
|
lastActive: /* @__PURE__ */ new Date()
|
|
598
576
|
}
|
|
599
577
|
});
|
|
@@ -607,7 +585,7 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
607
585
|
});
|
|
608
586
|
log.info("[SUCCESS] Refresh Token interceptor middleware mounted");
|
|
609
587
|
strapi2.server.use(
|
|
610
|
-
lastSeen({ strapi: strapi2
|
|
588
|
+
lastSeen({ strapi: strapi2 })
|
|
611
589
|
);
|
|
612
590
|
log.info("[SUCCESS] LastSeen middleware mounted");
|
|
613
591
|
await ensureContentApiPermissions(strapi2, log);
|
|
@@ -665,6 +643,46 @@ async function ensureContentApiPermissions(strapi2, log) {
|
|
|
665
643
|
log.warn("Please manually enable plugin permissions in Settings > Users & Permissions > Roles > Authenticated");
|
|
666
644
|
}
|
|
667
645
|
}
|
|
646
|
+
async function ensureTokenHashIndex(strapi2, log) {
|
|
647
|
+
try {
|
|
648
|
+
const knex = strapi2.db.connection;
|
|
649
|
+
const tableName = "magic_sessions";
|
|
650
|
+
const indexName = "idx_magic_sessions_token_hash";
|
|
651
|
+
const hasIndex = await knex.schema.hasTable(tableName).then(async (exists) => {
|
|
652
|
+
if (!exists) return false;
|
|
653
|
+
const dialect = strapi2.db.dialect.client;
|
|
654
|
+
if (dialect === "postgres") {
|
|
655
|
+
const result = await knex.raw(`
|
|
656
|
+
SELECT indexname FROM pg_indexes
|
|
657
|
+
WHERE tablename = ? AND indexname = ?
|
|
658
|
+
`, [tableName, indexName]);
|
|
659
|
+
return result.rows.length > 0;
|
|
660
|
+
} else if (dialect === "mysql" || dialect === "mysql2") {
|
|
661
|
+
const result = await knex.raw(`
|
|
662
|
+
SHOW INDEX FROM ${tableName} WHERE Key_name = ?
|
|
663
|
+
`, [indexName]);
|
|
664
|
+
return result[0].length > 0;
|
|
665
|
+
} else if (dialect === "sqlite" || dialect === "better-sqlite3") {
|
|
666
|
+
const result = await knex.raw(`
|
|
667
|
+
SELECT name FROM sqlite_master
|
|
668
|
+
WHERE type='index' AND name = ?
|
|
669
|
+
`, [indexName]);
|
|
670
|
+
return result.length > 0;
|
|
671
|
+
}
|
|
672
|
+
return false;
|
|
673
|
+
});
|
|
674
|
+
if (hasIndex) {
|
|
675
|
+
log.debug("[INDEX] tokenHash index already exists");
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
await knex.schema.alterTable(tableName, (table) => {
|
|
679
|
+
table.index(["token_hash", "is_active"], indexName);
|
|
680
|
+
});
|
|
681
|
+
log.info("[INDEX] Created tokenHash index for O(1) session lookup");
|
|
682
|
+
} catch (err) {
|
|
683
|
+
log.debug("[INDEX] Could not create tokenHash index (will retry on next startup):", err.message);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
668
686
|
const { createLogger: createLogger$2 } = logger;
|
|
669
687
|
var destroy$1 = async ({ strapi: strapi2 }) => {
|
|
670
688
|
const log = createLogger$2(strapi2);
|
|
@@ -743,10 +761,19 @@ const attributes = {
|
|
|
743
761
|
type: "text",
|
|
744
762
|
"private": true
|
|
745
763
|
},
|
|
764
|
+
tokenHash: {
|
|
765
|
+
type: "string",
|
|
766
|
+
configurable: false,
|
|
767
|
+
unique: false
|
|
768
|
+
},
|
|
746
769
|
refreshToken: {
|
|
747
770
|
type: "text",
|
|
748
771
|
"private": true
|
|
749
772
|
},
|
|
773
|
+
refreshTokenHash: {
|
|
774
|
+
type: "string",
|
|
775
|
+
configurable: false
|
|
776
|
+
},
|
|
750
777
|
loginTime: {
|
|
751
778
|
type: "datetime",
|
|
752
779
|
required: true
|
|
@@ -1778,7 +1805,7 @@ var controllers$1 = {
|
|
|
1778
1805
|
license,
|
|
1779
1806
|
settings
|
|
1780
1807
|
};
|
|
1781
|
-
const { encryptToken, decryptToken, generateSessionId } = encryption;
|
|
1808
|
+
const { encryptToken, decryptToken, generateSessionId, hashToken } = encryption;
|
|
1782
1809
|
const { createLogger: createLogger$1 } = logger;
|
|
1783
1810
|
const SESSION_UID = "plugin::magic-sessionmanager.session";
|
|
1784
1811
|
const USER_UID = "plugin::users-permissions.user";
|
|
@@ -1796,6 +1823,8 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
1796
1823
|
const sessionId = generateSessionId(userId);
|
|
1797
1824
|
const encryptedToken = token ? encryptToken(token) : null;
|
|
1798
1825
|
const encryptedRefreshToken = refreshToken ? encryptToken(refreshToken) : null;
|
|
1826
|
+
const tokenHashValue = token ? hashToken(token) : null;
|
|
1827
|
+
const refreshTokenHashValue = refreshToken ? hashToken(refreshToken) : null;
|
|
1799
1828
|
const session2 = await strapi2.documents(SESSION_UID).create({
|
|
1800
1829
|
data: {
|
|
1801
1830
|
user: userId,
|
|
@@ -1806,11 +1835,15 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
1806
1835
|
lastActive: now,
|
|
1807
1836
|
isActive: true,
|
|
1808
1837
|
token: encryptedToken,
|
|
1809
|
-
//
|
|
1838
|
+
// Encrypted Access Token
|
|
1839
|
+
tokenHash: tokenHashValue,
|
|
1840
|
+
// SHA-256 hash for fast lookup
|
|
1810
1841
|
refreshToken: encryptedRefreshToken,
|
|
1811
|
-
//
|
|
1842
|
+
// Encrypted Refresh Token
|
|
1843
|
+
refreshTokenHash: refreshTokenHashValue,
|
|
1844
|
+
// SHA-256 hash for fast lookup
|
|
1812
1845
|
sessionId
|
|
1813
|
-
//
|
|
1846
|
+
// Unique identifier
|
|
1814
1847
|
}
|
|
1815
1848
|
});
|
|
1816
1849
|
log.info(`[SUCCESS] Session ${session2.documentId} (${sessionId}) created for user ${userId}`);
|
|
@@ -1888,9 +1921,9 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
1888
1921
|
const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
|
|
1889
1922
|
const timeSinceActive = now - lastActiveTime;
|
|
1890
1923
|
const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
|
|
1891
|
-
const { token, ...
|
|
1924
|
+
const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
|
|
1892
1925
|
return {
|
|
1893
|
-
...
|
|
1926
|
+
...safeSession,
|
|
1894
1927
|
isTrulyActive,
|
|
1895
1928
|
minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
|
|
1896
1929
|
};
|
|
@@ -1919,9 +1952,9 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
1919
1952
|
const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
|
|
1920
1953
|
const timeSinceActive = now - lastActiveTime;
|
|
1921
1954
|
const isTrulyActive = timeSinceActive < inactivityTimeout;
|
|
1922
|
-
const { token, ...
|
|
1955
|
+
const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
|
|
1923
1956
|
return {
|
|
1924
|
-
...
|
|
1957
|
+
...safeSession,
|
|
1925
1958
|
isTrulyActive,
|
|
1926
1959
|
minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
|
|
1927
1960
|
};
|
|
@@ -1958,9 +1991,9 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
1958
1991
|
const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
|
|
1959
1992
|
const timeSinceActive = now - lastActiveTime;
|
|
1960
1993
|
const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
|
|
1961
|
-
const { token, ...
|
|
1994
|
+
const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
|
|
1962
1995
|
return {
|
|
1963
|
-
...
|
|
1996
|
+
...safeSession,
|
|
1964
1997
|
isTrulyActive,
|
|
1965
1998
|
minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
|
|
1966
1999
|
};
|
|
@@ -2077,7 +2110,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2077
2110
|
}
|
|
2078
2111
|
};
|
|
2079
2112
|
};
|
|
2080
|
-
const version = "4.2.
|
|
2113
|
+
const version = "4.2.9";
|
|
2081
2114
|
const require$$2 = {
|
|
2082
2115
|
version
|
|
2083
2116
|
};
|
package/dist/server/index.mjs
CHANGED
|
@@ -175,7 +175,7 @@ function encryptToken$2(token) {
|
|
|
175
175
|
throw new Error("Failed to encrypt token");
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
|
-
function decryptToken$
|
|
178
|
+
function decryptToken$3(encryptedToken) {
|
|
179
179
|
if (!encryptedToken) return null;
|
|
180
180
|
try {
|
|
181
181
|
const key = getEncryptionKey();
|
|
@@ -202,15 +202,20 @@ 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) {
|
|
206
|
+
if (!token) return null;
|
|
207
|
+
return crypto$1.createHash("sha256").update(token).digest("hex");
|
|
208
|
+
}
|
|
205
209
|
var encryption = {
|
|
206
210
|
encryptToken: encryptToken$2,
|
|
207
|
-
decryptToken: decryptToken$
|
|
208
|
-
generateSessionId: generateSessionId$1
|
|
211
|
+
decryptToken: decryptToken$3,
|
|
212
|
+
generateSessionId: generateSessionId$1,
|
|
213
|
+
hashToken: hashToken$3
|
|
209
214
|
};
|
|
210
215
|
const SESSION_UID$3 = "plugin::magic-sessionmanager.session";
|
|
211
|
-
const {
|
|
216
|
+
const { hashToken: hashToken$2 } = encryption;
|
|
212
217
|
const lastTouchCache = /* @__PURE__ */ new Map();
|
|
213
|
-
var lastSeen = ({ strapi: strapi2
|
|
218
|
+
var lastSeen = ({ strapi: strapi2 }) => {
|
|
214
219
|
return async (ctx, next) => {
|
|
215
220
|
const currentToken = ctx.request.headers.authorization?.replace("Bearer ", "");
|
|
216
221
|
if (!currentToken) {
|
|
@@ -223,58 +228,26 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
223
228
|
return;
|
|
224
229
|
}
|
|
225
230
|
let matchingSession = null;
|
|
226
|
-
let userId = null;
|
|
227
231
|
try {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (!activeSessions || activeSessions.length === 0) {
|
|
237
|
-
strapi2.log.info(`[magic-sessionmanager] [BLOCKED] User ${userId} has no active sessions`);
|
|
238
|
-
return ctx.unauthorized("All sessions have been terminated. Please login again.");
|
|
239
|
-
}
|
|
240
|
-
for (const session2 of activeSessions) {
|
|
241
|
-
if (!session2.token) continue;
|
|
242
|
-
try {
|
|
243
|
-
const decrypted = decryptToken$3(session2.token);
|
|
244
|
-
if (decrypted === currentToken) {
|
|
245
|
-
matchingSession = session2;
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
} catch (err) {
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
if (!matchingSession) {
|
|
252
|
-
strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Session for user ${userId} has been terminated`);
|
|
253
|
-
return ctx.unauthorized("This session has been terminated. Please login again.");
|
|
254
|
-
}
|
|
255
|
-
} else {
|
|
256
|
-
const allActiveSessions = await strapi2.documents(SESSION_UID$3).findMany({
|
|
257
|
-
filters: { isActive: true },
|
|
258
|
-
populate: { user: { fields: ["documentId"] } },
|
|
259
|
-
limit: 500
|
|
260
|
-
// Reasonable limit for performance
|
|
261
|
-
});
|
|
262
|
-
for (const session2 of allActiveSessions) {
|
|
263
|
-
if (!session2.token) continue;
|
|
264
|
-
try {
|
|
265
|
-
const decrypted = decryptToken$3(session2.token);
|
|
266
|
-
if (decrypted === currentToken) {
|
|
267
|
-
matchingSession = session2;
|
|
268
|
-
userId = session2.user?.documentId;
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
271
|
-
} catch (err) {
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
232
|
+
const currentTokenHash = hashToken$2(currentToken);
|
|
233
|
+
matchingSession = await strapi2.documents(SESSION_UID$3).findFirst({
|
|
234
|
+
filters: {
|
|
235
|
+
tokenHash: currentTokenHash,
|
|
236
|
+
isActive: true
|
|
237
|
+
},
|
|
238
|
+
populate: { user: { fields: ["documentId"] } }
|
|
239
|
+
});
|
|
275
240
|
if (matchingSession) {
|
|
276
241
|
ctx.state.sessionId = matchingSession.documentId;
|
|
277
242
|
ctx.state.currentSession = matchingSession;
|
|
243
|
+
if (matchingSession.user?.documentId) {
|
|
244
|
+
ctx.state.sessionUserId = matchingSession.user.documentId;
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
if (ctx.state.user && ctx.state.user.documentId) {
|
|
248
|
+
strapi2.log.info(`[magic-sessionmanager] [BLOCKED] Session terminated for user ${ctx.state.user.documentId}`);
|
|
249
|
+
return ctx.unauthorized("This session has been terminated. Please login again.");
|
|
250
|
+
}
|
|
278
251
|
}
|
|
279
252
|
} catch (err) {
|
|
280
253
|
strapi2.log.debug("[magic-sessionmanager] Error checking session:", err.message);
|
|
@@ -301,7 +274,7 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
301
274
|
};
|
|
302
275
|
};
|
|
303
276
|
const getClientIp = getClientIp_1;
|
|
304
|
-
const { encryptToken: encryptToken$1, decryptToken: decryptToken$2 } = encryption;
|
|
277
|
+
const { encryptToken: encryptToken$1, decryptToken: decryptToken$2, hashToken: hashToken$1 } = encryption;
|
|
305
278
|
const { createLogger: createLogger$3 } = logger;
|
|
306
279
|
const SESSION_UID$2 = "plugin::magic-sessionmanager.session";
|
|
307
280
|
const USER_UID$2 = "plugin::users-permissions.user";
|
|
@@ -309,6 +282,7 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
309
282
|
const log = createLogger$3(strapi2);
|
|
310
283
|
log.info("[START] Bootstrap starting...");
|
|
311
284
|
try {
|
|
285
|
+
await ensureTokenHashIndex(strapi2, log);
|
|
312
286
|
const licenseGuardService = strapi2.plugin("magic-sessionmanager").service("license-guard");
|
|
313
287
|
setTimeout(async () => {
|
|
314
288
|
const licenseStatus = await licenseGuardService.initialize();
|
|
@@ -585,11 +559,15 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
585
559
|
if (matchingSession) {
|
|
586
560
|
const encryptedToken = newAccessToken ? encryptToken$1(newAccessToken) : matchingSession.token;
|
|
587
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;
|
|
588
564
|
await strapi2.documents(SESSION_UID$2).update({
|
|
589
565
|
documentId: matchingSession.documentId,
|
|
590
566
|
data: {
|
|
591
567
|
token: encryptedToken,
|
|
568
|
+
tokenHash: newTokenHash,
|
|
592
569
|
refreshToken: encryptedRefreshToken,
|
|
570
|
+
refreshTokenHash: newRefreshTokenHash,
|
|
593
571
|
lastActive: /* @__PURE__ */ new Date()
|
|
594
572
|
}
|
|
595
573
|
});
|
|
@@ -603,7 +581,7 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
603
581
|
});
|
|
604
582
|
log.info("[SUCCESS] Refresh Token interceptor middleware mounted");
|
|
605
583
|
strapi2.server.use(
|
|
606
|
-
lastSeen({ strapi: strapi2
|
|
584
|
+
lastSeen({ strapi: strapi2 })
|
|
607
585
|
);
|
|
608
586
|
log.info("[SUCCESS] LastSeen middleware mounted");
|
|
609
587
|
await ensureContentApiPermissions(strapi2, log);
|
|
@@ -661,6 +639,46 @@ async function ensureContentApiPermissions(strapi2, log) {
|
|
|
661
639
|
log.warn("Please manually enable plugin permissions in Settings > Users & Permissions > Roles > Authenticated");
|
|
662
640
|
}
|
|
663
641
|
}
|
|
642
|
+
async function ensureTokenHashIndex(strapi2, log) {
|
|
643
|
+
try {
|
|
644
|
+
const knex = strapi2.db.connection;
|
|
645
|
+
const tableName = "magic_sessions";
|
|
646
|
+
const indexName = "idx_magic_sessions_token_hash";
|
|
647
|
+
const hasIndex = await knex.schema.hasTable(tableName).then(async (exists) => {
|
|
648
|
+
if (!exists) return false;
|
|
649
|
+
const dialect = strapi2.db.dialect.client;
|
|
650
|
+
if (dialect === "postgres") {
|
|
651
|
+
const result = await knex.raw(`
|
|
652
|
+
SELECT indexname FROM pg_indexes
|
|
653
|
+
WHERE tablename = ? AND indexname = ?
|
|
654
|
+
`, [tableName, indexName]);
|
|
655
|
+
return result.rows.length > 0;
|
|
656
|
+
} else if (dialect === "mysql" || dialect === "mysql2") {
|
|
657
|
+
const result = await knex.raw(`
|
|
658
|
+
SHOW INDEX FROM ${tableName} WHERE Key_name = ?
|
|
659
|
+
`, [indexName]);
|
|
660
|
+
return result[0].length > 0;
|
|
661
|
+
} else if (dialect === "sqlite" || dialect === "better-sqlite3") {
|
|
662
|
+
const result = await knex.raw(`
|
|
663
|
+
SELECT name FROM sqlite_master
|
|
664
|
+
WHERE type='index' AND name = ?
|
|
665
|
+
`, [indexName]);
|
|
666
|
+
return result.length > 0;
|
|
667
|
+
}
|
|
668
|
+
return false;
|
|
669
|
+
});
|
|
670
|
+
if (hasIndex) {
|
|
671
|
+
log.debug("[INDEX] tokenHash index already exists");
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
await knex.schema.alterTable(tableName, (table) => {
|
|
675
|
+
table.index(["token_hash", "is_active"], indexName);
|
|
676
|
+
});
|
|
677
|
+
log.info("[INDEX] Created tokenHash index for O(1) session lookup");
|
|
678
|
+
} catch (err) {
|
|
679
|
+
log.debug("[INDEX] Could not create tokenHash index (will retry on next startup):", err.message);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
664
682
|
const { createLogger: createLogger$2 } = logger;
|
|
665
683
|
var destroy$1 = async ({ strapi: strapi2 }) => {
|
|
666
684
|
const log = createLogger$2(strapi2);
|
|
@@ -739,10 +757,19 @@ const attributes = {
|
|
|
739
757
|
type: "text",
|
|
740
758
|
"private": true
|
|
741
759
|
},
|
|
760
|
+
tokenHash: {
|
|
761
|
+
type: "string",
|
|
762
|
+
configurable: false,
|
|
763
|
+
unique: false
|
|
764
|
+
},
|
|
742
765
|
refreshToken: {
|
|
743
766
|
type: "text",
|
|
744
767
|
"private": true
|
|
745
768
|
},
|
|
769
|
+
refreshTokenHash: {
|
|
770
|
+
type: "string",
|
|
771
|
+
configurable: false
|
|
772
|
+
},
|
|
746
773
|
loginTime: {
|
|
747
774
|
type: "datetime",
|
|
748
775
|
required: true
|
|
@@ -1774,7 +1801,7 @@ var controllers$1 = {
|
|
|
1774
1801
|
license,
|
|
1775
1802
|
settings
|
|
1776
1803
|
};
|
|
1777
|
-
const { encryptToken, decryptToken, generateSessionId } = encryption;
|
|
1804
|
+
const { encryptToken, decryptToken, generateSessionId, hashToken } = encryption;
|
|
1778
1805
|
const { createLogger: createLogger$1 } = logger;
|
|
1779
1806
|
const SESSION_UID = "plugin::magic-sessionmanager.session";
|
|
1780
1807
|
const USER_UID = "plugin::users-permissions.user";
|
|
@@ -1792,6 +1819,8 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
1792
1819
|
const sessionId = generateSessionId(userId);
|
|
1793
1820
|
const encryptedToken = token ? encryptToken(token) : null;
|
|
1794
1821
|
const encryptedRefreshToken = refreshToken ? encryptToken(refreshToken) : null;
|
|
1822
|
+
const tokenHashValue = token ? hashToken(token) : null;
|
|
1823
|
+
const refreshTokenHashValue = refreshToken ? hashToken(refreshToken) : null;
|
|
1795
1824
|
const session2 = await strapi2.documents(SESSION_UID).create({
|
|
1796
1825
|
data: {
|
|
1797
1826
|
user: userId,
|
|
@@ -1802,11 +1831,15 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
1802
1831
|
lastActive: now,
|
|
1803
1832
|
isActive: true,
|
|
1804
1833
|
token: encryptedToken,
|
|
1805
|
-
//
|
|
1834
|
+
// Encrypted Access Token
|
|
1835
|
+
tokenHash: tokenHashValue,
|
|
1836
|
+
// SHA-256 hash for fast lookup
|
|
1806
1837
|
refreshToken: encryptedRefreshToken,
|
|
1807
|
-
//
|
|
1838
|
+
// Encrypted Refresh Token
|
|
1839
|
+
refreshTokenHash: refreshTokenHashValue,
|
|
1840
|
+
// SHA-256 hash for fast lookup
|
|
1808
1841
|
sessionId
|
|
1809
|
-
//
|
|
1842
|
+
// Unique identifier
|
|
1810
1843
|
}
|
|
1811
1844
|
});
|
|
1812
1845
|
log.info(`[SUCCESS] Session ${session2.documentId} (${sessionId}) created for user ${userId}`);
|
|
@@ -1884,9 +1917,9 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
1884
1917
|
const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
|
|
1885
1918
|
const timeSinceActive = now - lastActiveTime;
|
|
1886
1919
|
const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
|
|
1887
|
-
const { token, ...
|
|
1920
|
+
const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
|
|
1888
1921
|
return {
|
|
1889
|
-
...
|
|
1922
|
+
...safeSession,
|
|
1890
1923
|
isTrulyActive,
|
|
1891
1924
|
minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
|
|
1892
1925
|
};
|
|
@@ -1915,9 +1948,9 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
1915
1948
|
const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
|
|
1916
1949
|
const timeSinceActive = now - lastActiveTime;
|
|
1917
1950
|
const isTrulyActive = timeSinceActive < inactivityTimeout;
|
|
1918
|
-
const { token, ...
|
|
1951
|
+
const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
|
|
1919
1952
|
return {
|
|
1920
|
-
...
|
|
1953
|
+
...safeSession,
|
|
1921
1954
|
isTrulyActive,
|
|
1922
1955
|
minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
|
|
1923
1956
|
};
|
|
@@ -1954,9 +1987,9 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
1954
1987
|
const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
|
|
1955
1988
|
const timeSinceActive = now - lastActiveTime;
|
|
1956
1989
|
const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
|
|
1957
|
-
const { token, ...
|
|
1990
|
+
const { token, tokenHash, refreshToken, refreshTokenHash, ...safeSession } = session2;
|
|
1958
1991
|
return {
|
|
1959
|
-
...
|
|
1992
|
+
...safeSession,
|
|
1960
1993
|
isTrulyActive,
|
|
1961
1994
|
minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
|
|
1962
1995
|
};
|
|
@@ -2073,7 +2106,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
2073
2106
|
}
|
|
2074
2107
|
};
|
|
2075
2108
|
};
|
|
2076
|
-
const version = "4.2.
|
|
2109
|
+
const version = "4.2.9";
|
|
2077
2110
|
const require$$2 = {
|
|
2078
2111
|
version
|
|
2079
2112
|
};
|
package/package.json
CHANGED