strapi-plugin-magic-sessionmanager 4.5.0 → 4.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{Analytics-DmlZwyVu.mjs → Analytics--qB2cKuD.mjs} +2 -2
- package/dist/_chunks/{Analytics-B7t0WvG7.js → Analytics-DM4_UffY.js} +2 -2
- package/dist/_chunks/{App-LfsjcXXp.mjs → App-CL5q29Mi.mjs} +2 -2
- package/dist/_chunks/{App-Bir8yK_r.js → App-DF-VsbDW.js} +2 -2
- package/dist/_chunks/{License-OToijT0s.mjs → License-D9piCp34.mjs} +1 -1
- package/dist/_chunks/{License-B56Xklj2.js → License-Dmm1d3fQ.js} +1 -1
- package/dist/_chunks/{OnlineUsersWidget-Fpc0aH2z.mjs → OnlineUsersWidget-DHsthkt0.mjs} +1 -1
- package/dist/_chunks/{OnlineUsersWidget-DTEzguhS.js → OnlineUsersWidget-KchZ_ScS.js} +1 -1
- package/dist/_chunks/{Settings-CO3-iggu.js → Settings-CUNaxDWk.js} +303 -3
- package/dist/_chunks/{Settings-Bz3lPBJ6.mjs → Settings-D6ILgR9X.mjs} +303 -3
- package/dist/_chunks/{UpgradePage-TBx1l2mQ.mjs → UpgradePage-D697BVWo.mjs} +1 -1
- package/dist/_chunks/{UpgradePage-KqUN7mDh.js → UpgradePage-Dwrv7g8L.js} +1 -1
- package/dist/_chunks/{index-CKrO7KSQ.js → index-CTxGMDHr.js} +6 -6
- package/dist/_chunks/{index-DuVZXuJh.mjs → index-CwxKazpc.mjs} +6 -6
- package/dist/_chunks/{useLicense-Xzo6nyh3.mjs → useLicense-B9WW9s_d.mjs} +1 -1
- package/dist/_chunks/{useLicense-DHAFqFSZ.js → useLicense-BEbtA_Zo.js} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +150 -24
- package/dist/server/index.mjs +150 -24
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -9584,6 +9584,9 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
9584
9584
|
}
|
|
9585
9585
|
}, 3e3);
|
|
9586
9586
|
const sessionService = strapi2.plugin("magic-sessionmanager").service("session");
|
|
9587
|
+
if (!strapi2.sessionManagerIntervals) {
|
|
9588
|
+
strapi2.sessionManagerIntervals = {};
|
|
9589
|
+
}
|
|
9587
9590
|
log.info("Running initial session cleanup...");
|
|
9588
9591
|
try {
|
|
9589
9592
|
const settings2 = await getPluginSettings$3(strapi2);
|
|
@@ -9593,23 +9596,50 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
9593
9596
|
} catch (cleanupErr) {
|
|
9594
9597
|
log.warn("Initial cleanup failed:", cleanupErr.message);
|
|
9595
9598
|
}
|
|
9596
|
-
const
|
|
9597
|
-
|
|
9599
|
+
const scheduleIdleCleanup = async () => {
|
|
9600
|
+
let intervalMs = 30 * 60 * 1e3;
|
|
9601
|
+
let useDbDirect = false;
|
|
9598
9602
|
try {
|
|
9599
|
-
const
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
});
|
|
9604
|
-
} catch (err) {
|
|
9605
|
-
log.error("Periodic cleanup error:", err);
|
|
9603
|
+
const settings2 = await getPluginSettings$3(strapi2);
|
|
9604
|
+
intervalMs = Math.max(5 * 60 * 1e3, settings2.cleanupInterval || intervalMs);
|
|
9605
|
+
useDbDirect = settings2.cleanupUseDbDirect === true;
|
|
9606
|
+
} catch {
|
|
9606
9607
|
}
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9608
|
+
const handle = setTimeout(async () => {
|
|
9609
|
+
try {
|
|
9610
|
+
const service = strapi2.plugin("magic-sessionmanager").service("session");
|
|
9611
|
+
await service.cleanupInactiveSessions({ useDbDirect });
|
|
9612
|
+
} catch (err) {
|
|
9613
|
+
log.error("Periodic cleanup error:", err);
|
|
9614
|
+
}
|
|
9615
|
+
scheduleIdleCleanup();
|
|
9616
|
+
}, intervalMs);
|
|
9617
|
+
strapi2.sessionManagerIntervals.cleanupTimeout = handle;
|
|
9618
|
+
};
|
|
9619
|
+
await scheduleIdleCleanup();
|
|
9620
|
+
const RETENTION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
9621
|
+
const scheduleRetention = async () => {
|
|
9622
|
+
let useDbDirect = false;
|
|
9623
|
+
try {
|
|
9624
|
+
const settings2 = await getPluginSettings$3(strapi2);
|
|
9625
|
+
useDbDirect = settings2.cleanupUseDbDirect === true;
|
|
9626
|
+
} catch {
|
|
9627
|
+
}
|
|
9628
|
+
const handle = setTimeout(async () => {
|
|
9629
|
+
try {
|
|
9630
|
+
const service = strapi2.plugin("magic-sessionmanager").service("session");
|
|
9631
|
+
await service.deleteOldSessions({ useDbDirect });
|
|
9632
|
+
} catch (err) {
|
|
9633
|
+
log.error("Retention cleanup error:", err);
|
|
9634
|
+
}
|
|
9635
|
+
scheduleRetention();
|
|
9636
|
+
}, RETENTION_INTERVAL_MS);
|
|
9637
|
+
strapi2.sessionManagerIntervals.retentionTimeout = handle;
|
|
9638
|
+
};
|
|
9639
|
+
strapi2.sessionManagerIntervals.retentionStartup = setTimeout(() => {
|
|
9640
|
+
scheduleRetention();
|
|
9641
|
+
}, 5 * 60 * 1e3);
|
|
9642
|
+
log.info("[TIME] Dynamic cleanup + retention scheduled");
|
|
9613
9643
|
mountPreLoginGeoGuard({ strapi: strapi2, log });
|
|
9614
9644
|
mountFailedLoginLockout({ strapi: strapi2, log });
|
|
9615
9645
|
mountLogoutRoute({ strapi: strapi2, log, sessionService });
|
|
@@ -10341,9 +10371,10 @@ var destroy$1 = async ({ strapi: strapi2 }) => {
|
|
|
10341
10371
|
if (!handle) continue;
|
|
10342
10372
|
try {
|
|
10343
10373
|
clearInterval(handle);
|
|
10344
|
-
|
|
10374
|
+
clearTimeout(handle);
|
|
10375
|
+
log.info(`[STOP] ${name} timer stopped`);
|
|
10345
10376
|
} catch (err) {
|
|
10346
|
-
log.warn(`Failed to stop ${name}
|
|
10377
|
+
log.warn(`Failed to stop ${name} timer:`, err.message);
|
|
10347
10378
|
}
|
|
10348
10379
|
}
|
|
10349
10380
|
strapi2.sessionManagerIntervals = {};
|
|
@@ -11732,15 +11763,27 @@ var settings$1 = {
|
|
|
11732
11763
|
let settings2 = await pluginStore.get({ key: "settings" });
|
|
11733
11764
|
if (!settings2) {
|
|
11734
11765
|
settings2 = {
|
|
11766
|
+
// Timing & cleanup
|
|
11735
11767
|
inactivityTimeout: 15,
|
|
11736
11768
|
cleanupInterval: 30,
|
|
11737
11769
|
lastSeenRateLimit: 30,
|
|
11738
11770
|
retentionDays: 90,
|
|
11739
11771
|
maxSessionAgeDays: 30,
|
|
11772
|
+
// Grace window (ms) during which a freshly-issued JWT is accepted
|
|
11773
|
+
// without a matching session row — see JWT-verify wrapper in bootstrap.
|
|
11774
|
+
sessionCreationGraceMs: 5e3,
|
|
11775
|
+
// Opt-in: use a single SQL UPDATE for idle-session cleanup
|
|
11776
|
+
// (bypasses Document Service lifecycle, required for very large installs).
|
|
11777
|
+
cleanupUseDbDirect: false,
|
|
11778
|
+
// Geo / security
|
|
11740
11779
|
enableGeolocation: true,
|
|
11741
11780
|
enableSecurityScoring: true,
|
|
11742
11781
|
blockSuspiciousSessions: false,
|
|
11743
11782
|
maxFailedLogins: 5,
|
|
11783
|
+
enableGeofencing: false,
|
|
11784
|
+
allowedCountries: [],
|
|
11785
|
+
blockedCountries: [],
|
|
11786
|
+
// Notifications
|
|
11744
11787
|
enableEmailAlerts: false,
|
|
11745
11788
|
alertOnSuspiciousLogin: true,
|
|
11746
11789
|
alertOnNewLocation: true,
|
|
@@ -11748,11 +11791,10 @@ var settings$1 = {
|
|
|
11748
11791
|
enableWebhooks: false,
|
|
11749
11792
|
discordWebhookUrl: "",
|
|
11750
11793
|
slackWebhookUrl: "",
|
|
11751
|
-
|
|
11752
|
-
allowedCountries: [],
|
|
11753
|
-
blockedCountries: [],
|
|
11794
|
+
// Session policy
|
|
11754
11795
|
strictSessionEnforcement: false,
|
|
11755
11796
|
trustedProxies: false,
|
|
11797
|
+
// Email templates
|
|
11756
11798
|
emailTemplates: {
|
|
11757
11799
|
suspiciousLogin: { subject: "", html: "", text: "" },
|
|
11758
11800
|
newLocation: { subject: "", html: "", text: "" },
|
|
@@ -11785,16 +11827,26 @@ var settings$1 = {
|
|
|
11785
11827
|
type: "plugin",
|
|
11786
11828
|
name: "magic-sessionmanager"
|
|
11787
11829
|
});
|
|
11830
|
+
const graceRaw = body.sessionCreationGraceMs;
|
|
11831
|
+
const grace = Number.isFinite(parseInt(graceRaw)) ? Math.max(0, Math.min(parseInt(graceRaw), 3e4)) : 5e3;
|
|
11788
11832
|
const sanitizedSettings = {
|
|
11833
|
+
// Timing & cleanup
|
|
11789
11834
|
inactivityTimeout: Math.max(1, Math.min(parseInt(body.inactivityTimeout) || 15, 1440)),
|
|
11790
11835
|
cleanupInterval: Math.max(5, Math.min(parseInt(body.cleanupInterval) || 30, 1440)),
|
|
11791
11836
|
lastSeenRateLimit: Math.max(5, Math.min(parseInt(body.lastSeenRateLimit) || 30, 300)),
|
|
11792
11837
|
retentionDays: Math.max(1, Math.min(parseInt(body.retentionDays) || 90, 365)),
|
|
11793
11838
|
maxSessionAgeDays: Math.max(1, Math.min(parseInt(body.maxSessionAgeDays) || 30, 365)),
|
|
11839
|
+
sessionCreationGraceMs: grace,
|
|
11840
|
+
cleanupUseDbDirect: !!body.cleanupUseDbDirect,
|
|
11841
|
+
// Geo / security
|
|
11794
11842
|
enableGeolocation: !!body.enableGeolocation,
|
|
11795
11843
|
enableSecurityScoring: !!body.enableSecurityScoring,
|
|
11796
11844
|
blockSuspiciousSessions: !!body.blockSuspiciousSessions,
|
|
11797
11845
|
maxFailedLogins: Math.max(1, Math.min(parseInt(body.maxFailedLogins) || 5, 100)),
|
|
11846
|
+
enableGeofencing: !!body.enableGeofencing,
|
|
11847
|
+
allowedCountries: sanitizeCountryList(body.allowedCountries),
|
|
11848
|
+
blockedCountries: sanitizeCountryList(body.blockedCountries),
|
|
11849
|
+
// Notifications
|
|
11798
11850
|
enableEmailAlerts: !!body.enableEmailAlerts,
|
|
11799
11851
|
alertOnSuspiciousLogin: !!body.alertOnSuspiciousLogin,
|
|
11800
11852
|
alertOnNewLocation: !!body.alertOnNewLocation,
|
|
@@ -11802,11 +11854,10 @@ var settings$1 = {
|
|
|
11802
11854
|
enableWebhooks: !!body.enableWebhooks,
|
|
11803
11855
|
discordWebhookUrl: sanitizeWebhookUrl(body.discordWebhookUrl, "discord"),
|
|
11804
11856
|
slackWebhookUrl: sanitizeWebhookUrl(body.slackWebhookUrl, "slack"),
|
|
11805
|
-
|
|
11806
|
-
allowedCountries: sanitizeCountryList(body.allowedCountries),
|
|
11807
|
-
blockedCountries: sanitizeCountryList(body.blockedCountries),
|
|
11857
|
+
// Session policy
|
|
11808
11858
|
strictSessionEnforcement: !!body.strictSessionEnforcement,
|
|
11809
11859
|
trustedProxies: !!body.trustedProxies,
|
|
11860
|
+
// Email templates
|
|
11810
11861
|
emailTemplates: sanitizeEmailTemplates(body.emailTemplates)
|
|
11811
11862
|
};
|
|
11812
11863
|
await pluginStore.set({
|
|
@@ -12190,6 +12241,81 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
12190
12241
|
throw err;
|
|
12191
12242
|
}
|
|
12192
12243
|
},
|
|
12244
|
+
/**
|
|
12245
|
+
* Permanently deletes sessions that have been inactive past the
|
|
12246
|
+
* configured retention window. Distinct from `deleteInactiveSessions`
|
|
12247
|
+
* (which deletes ALL inactive sessions) — this only drops rows older
|
|
12248
|
+
* than `retentionDays`, so recently-terminated sessions stay queryable
|
|
12249
|
+
* for audits.
|
|
12250
|
+
*
|
|
12251
|
+
* @param {Object} [options]
|
|
12252
|
+
* @param {number} [options.retentionDays] Overrides the stored setting.
|
|
12253
|
+
* @param {boolean} [options.useDbDirect] Fast-path via single SQL
|
|
12254
|
+
* DELETE. Bypasses lifecycle hooks; use only when necessary.
|
|
12255
|
+
* @returns {Promise<number>} Number of sessions deleted
|
|
12256
|
+
*/
|
|
12257
|
+
async deleteOldSessions({ retentionDays, useDbDirect } = {}) {
|
|
12258
|
+
try {
|
|
12259
|
+
const settings2 = await getPluginSettings$1(strapi2);
|
|
12260
|
+
const effectiveDays = Number.isFinite(retentionDays) ? retentionDays : settings2.retentionDays || 90;
|
|
12261
|
+
if (effectiveDays === -1) {
|
|
12262
|
+
log.debug("[RETENTION] retentionDays=-1 (forever) — skipping");
|
|
12263
|
+
return 0;
|
|
12264
|
+
}
|
|
12265
|
+
const cutoffDate = new Date(Date.now() - effectiveDays * 24 * 60 * 60 * 1e3);
|
|
12266
|
+
const wantDbDirect = useDbDirect ?? settings2.cleanupUseDbDirect === true;
|
|
12267
|
+
log.info(`[RETENTION] Deleting inactive sessions older than ${effectiveDays} days (before ${cutoffDate.toISOString()})`);
|
|
12268
|
+
if (wantDbDirect) {
|
|
12269
|
+
try {
|
|
12270
|
+
const deleted = await strapi2.db.connection("magic_sessions").where("is_active", false).andWhere(function whereOldEnough() {
|
|
12271
|
+
this.where("logout_time", "<", cutoffDate).orWhere(function whereNullLogout() {
|
|
12272
|
+
this.whereNull("logout_time").andWhere(function whereOldByActivity() {
|
|
12273
|
+
this.where("last_active", "<", cutoffDate).orWhere(function whereNullActivity() {
|
|
12274
|
+
this.whereNull("last_active").andWhere("login_time", "<", cutoffDate);
|
|
12275
|
+
});
|
|
12276
|
+
});
|
|
12277
|
+
});
|
|
12278
|
+
}).del();
|
|
12279
|
+
log.info(`[SUCCESS] Retention (db-direct) deleted ${deleted} old session(s)`);
|
|
12280
|
+
return deleted;
|
|
12281
|
+
} catch (err) {
|
|
12282
|
+
log.warn("[RETENTION] DB-direct delete failed, falling back to Document Service:", err.message);
|
|
12283
|
+
}
|
|
12284
|
+
}
|
|
12285
|
+
let deletedCount = 0;
|
|
12286
|
+
const BATCH = 200;
|
|
12287
|
+
while (true) {
|
|
12288
|
+
const batch = await strapi2.documents(SESSION_UID$1).findMany({
|
|
12289
|
+
filters: {
|
|
12290
|
+
isActive: false,
|
|
12291
|
+
$or: [
|
|
12292
|
+
{ logoutTime: { $lt: cutoffDate } },
|
|
12293
|
+
{ logoutTime: { $null: true }, lastActive: { $lt: cutoffDate } },
|
|
12294
|
+
{ logoutTime: { $null: true }, lastActive: { $null: true }, loginTime: { $lt: cutoffDate } }
|
|
12295
|
+
]
|
|
12296
|
+
},
|
|
12297
|
+
fields: ["documentId"],
|
|
12298
|
+
sort: { loginTime: "asc" },
|
|
12299
|
+
limit: BATCH
|
|
12300
|
+
});
|
|
12301
|
+
if (!batch || batch.length === 0) break;
|
|
12302
|
+
for (const session2 of batch) {
|
|
12303
|
+
try {
|
|
12304
|
+
await strapi2.documents(SESSION_UID$1).delete({ documentId: session2.documentId });
|
|
12305
|
+
deletedCount++;
|
|
12306
|
+
} catch (err) {
|
|
12307
|
+
log.debug(`[RETENTION] Failed to delete session ${session2.documentId}:`, err.message);
|
|
12308
|
+
}
|
|
12309
|
+
}
|
|
12310
|
+
if (batch.length < BATCH) break;
|
|
12311
|
+
}
|
|
12312
|
+
log.info(`[SUCCESS] Retention deleted ${deletedCount} old session(s)`);
|
|
12313
|
+
return deletedCount;
|
|
12314
|
+
} catch (err) {
|
|
12315
|
+
log.error("Error in retention cleanup:", err);
|
|
12316
|
+
return 0;
|
|
12317
|
+
}
|
|
12318
|
+
},
|
|
12193
12319
|
/**
|
|
12194
12320
|
* Permanently deletes all inactive sessions.
|
|
12195
12321
|
* Uses an inner scan loop that tolerates partial failures.
|
|
@@ -12235,7 +12361,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
12235
12361
|
}
|
|
12236
12362
|
};
|
|
12237
12363
|
};
|
|
12238
|
-
const version$1 = "4.5.
|
|
12364
|
+
const version$1 = "4.5.1";
|
|
12239
12365
|
const require$$2 = {
|
|
12240
12366
|
version: version$1
|
|
12241
12367
|
};
|
package/dist/server/index.mjs
CHANGED
|
@@ -9571,6 +9571,9 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
9571
9571
|
}
|
|
9572
9572
|
}, 3e3);
|
|
9573
9573
|
const sessionService = strapi2.plugin("magic-sessionmanager").service("session");
|
|
9574
|
+
if (!strapi2.sessionManagerIntervals) {
|
|
9575
|
+
strapi2.sessionManagerIntervals = {};
|
|
9576
|
+
}
|
|
9574
9577
|
log.info("Running initial session cleanup...");
|
|
9575
9578
|
try {
|
|
9576
9579
|
const settings2 = await getPluginSettings$3(strapi2);
|
|
@@ -9580,23 +9583,50 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
9580
9583
|
} catch (cleanupErr) {
|
|
9581
9584
|
log.warn("Initial cleanup failed:", cleanupErr.message);
|
|
9582
9585
|
}
|
|
9583
|
-
const
|
|
9584
|
-
|
|
9586
|
+
const scheduleIdleCleanup = async () => {
|
|
9587
|
+
let intervalMs = 30 * 60 * 1e3;
|
|
9588
|
+
let useDbDirect = false;
|
|
9585
9589
|
try {
|
|
9586
|
-
const
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
});
|
|
9591
|
-
} catch (err) {
|
|
9592
|
-
log.error("Periodic cleanup error:", err);
|
|
9590
|
+
const settings2 = await getPluginSettings$3(strapi2);
|
|
9591
|
+
intervalMs = Math.max(5 * 60 * 1e3, settings2.cleanupInterval || intervalMs);
|
|
9592
|
+
useDbDirect = settings2.cleanupUseDbDirect === true;
|
|
9593
|
+
} catch {
|
|
9593
9594
|
}
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9595
|
+
const handle = setTimeout(async () => {
|
|
9596
|
+
try {
|
|
9597
|
+
const service = strapi2.plugin("magic-sessionmanager").service("session");
|
|
9598
|
+
await service.cleanupInactiveSessions({ useDbDirect });
|
|
9599
|
+
} catch (err) {
|
|
9600
|
+
log.error("Periodic cleanup error:", err);
|
|
9601
|
+
}
|
|
9602
|
+
scheduleIdleCleanup();
|
|
9603
|
+
}, intervalMs);
|
|
9604
|
+
strapi2.sessionManagerIntervals.cleanupTimeout = handle;
|
|
9605
|
+
};
|
|
9606
|
+
await scheduleIdleCleanup();
|
|
9607
|
+
const RETENTION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
9608
|
+
const scheduleRetention = async () => {
|
|
9609
|
+
let useDbDirect = false;
|
|
9610
|
+
try {
|
|
9611
|
+
const settings2 = await getPluginSettings$3(strapi2);
|
|
9612
|
+
useDbDirect = settings2.cleanupUseDbDirect === true;
|
|
9613
|
+
} catch {
|
|
9614
|
+
}
|
|
9615
|
+
const handle = setTimeout(async () => {
|
|
9616
|
+
try {
|
|
9617
|
+
const service = strapi2.plugin("magic-sessionmanager").service("session");
|
|
9618
|
+
await service.deleteOldSessions({ useDbDirect });
|
|
9619
|
+
} catch (err) {
|
|
9620
|
+
log.error("Retention cleanup error:", err);
|
|
9621
|
+
}
|
|
9622
|
+
scheduleRetention();
|
|
9623
|
+
}, RETENTION_INTERVAL_MS);
|
|
9624
|
+
strapi2.sessionManagerIntervals.retentionTimeout = handle;
|
|
9625
|
+
};
|
|
9626
|
+
strapi2.sessionManagerIntervals.retentionStartup = setTimeout(() => {
|
|
9627
|
+
scheduleRetention();
|
|
9628
|
+
}, 5 * 60 * 1e3);
|
|
9629
|
+
log.info("[TIME] Dynamic cleanup + retention scheduled");
|
|
9600
9630
|
mountPreLoginGeoGuard({ strapi: strapi2, log });
|
|
9601
9631
|
mountFailedLoginLockout({ strapi: strapi2, log });
|
|
9602
9632
|
mountLogoutRoute({ strapi: strapi2, log, sessionService });
|
|
@@ -10328,9 +10358,10 @@ var destroy$1 = async ({ strapi: strapi2 }) => {
|
|
|
10328
10358
|
if (!handle) continue;
|
|
10329
10359
|
try {
|
|
10330
10360
|
clearInterval(handle);
|
|
10331
|
-
|
|
10361
|
+
clearTimeout(handle);
|
|
10362
|
+
log.info(`[STOP] ${name} timer stopped`);
|
|
10332
10363
|
} catch (err) {
|
|
10333
|
-
log.warn(`Failed to stop ${name}
|
|
10364
|
+
log.warn(`Failed to stop ${name} timer:`, err.message);
|
|
10334
10365
|
}
|
|
10335
10366
|
}
|
|
10336
10367
|
strapi2.sessionManagerIntervals = {};
|
|
@@ -11719,15 +11750,27 @@ var settings$1 = {
|
|
|
11719
11750
|
let settings2 = await pluginStore.get({ key: "settings" });
|
|
11720
11751
|
if (!settings2) {
|
|
11721
11752
|
settings2 = {
|
|
11753
|
+
// Timing & cleanup
|
|
11722
11754
|
inactivityTimeout: 15,
|
|
11723
11755
|
cleanupInterval: 30,
|
|
11724
11756
|
lastSeenRateLimit: 30,
|
|
11725
11757
|
retentionDays: 90,
|
|
11726
11758
|
maxSessionAgeDays: 30,
|
|
11759
|
+
// Grace window (ms) during which a freshly-issued JWT is accepted
|
|
11760
|
+
// without a matching session row — see JWT-verify wrapper in bootstrap.
|
|
11761
|
+
sessionCreationGraceMs: 5e3,
|
|
11762
|
+
// Opt-in: use a single SQL UPDATE for idle-session cleanup
|
|
11763
|
+
// (bypasses Document Service lifecycle, required for very large installs).
|
|
11764
|
+
cleanupUseDbDirect: false,
|
|
11765
|
+
// Geo / security
|
|
11727
11766
|
enableGeolocation: true,
|
|
11728
11767
|
enableSecurityScoring: true,
|
|
11729
11768
|
blockSuspiciousSessions: false,
|
|
11730
11769
|
maxFailedLogins: 5,
|
|
11770
|
+
enableGeofencing: false,
|
|
11771
|
+
allowedCountries: [],
|
|
11772
|
+
blockedCountries: [],
|
|
11773
|
+
// Notifications
|
|
11731
11774
|
enableEmailAlerts: false,
|
|
11732
11775
|
alertOnSuspiciousLogin: true,
|
|
11733
11776
|
alertOnNewLocation: true,
|
|
@@ -11735,11 +11778,10 @@ var settings$1 = {
|
|
|
11735
11778
|
enableWebhooks: false,
|
|
11736
11779
|
discordWebhookUrl: "",
|
|
11737
11780
|
slackWebhookUrl: "",
|
|
11738
|
-
|
|
11739
|
-
allowedCountries: [],
|
|
11740
|
-
blockedCountries: [],
|
|
11781
|
+
// Session policy
|
|
11741
11782
|
strictSessionEnforcement: false,
|
|
11742
11783
|
trustedProxies: false,
|
|
11784
|
+
// Email templates
|
|
11743
11785
|
emailTemplates: {
|
|
11744
11786
|
suspiciousLogin: { subject: "", html: "", text: "" },
|
|
11745
11787
|
newLocation: { subject: "", html: "", text: "" },
|
|
@@ -11772,16 +11814,26 @@ var settings$1 = {
|
|
|
11772
11814
|
type: "plugin",
|
|
11773
11815
|
name: "magic-sessionmanager"
|
|
11774
11816
|
});
|
|
11817
|
+
const graceRaw = body.sessionCreationGraceMs;
|
|
11818
|
+
const grace = Number.isFinite(parseInt(graceRaw)) ? Math.max(0, Math.min(parseInt(graceRaw), 3e4)) : 5e3;
|
|
11775
11819
|
const sanitizedSettings = {
|
|
11820
|
+
// Timing & cleanup
|
|
11776
11821
|
inactivityTimeout: Math.max(1, Math.min(parseInt(body.inactivityTimeout) || 15, 1440)),
|
|
11777
11822
|
cleanupInterval: Math.max(5, Math.min(parseInt(body.cleanupInterval) || 30, 1440)),
|
|
11778
11823
|
lastSeenRateLimit: Math.max(5, Math.min(parseInt(body.lastSeenRateLimit) || 30, 300)),
|
|
11779
11824
|
retentionDays: Math.max(1, Math.min(parseInt(body.retentionDays) || 90, 365)),
|
|
11780
11825
|
maxSessionAgeDays: Math.max(1, Math.min(parseInt(body.maxSessionAgeDays) || 30, 365)),
|
|
11826
|
+
sessionCreationGraceMs: grace,
|
|
11827
|
+
cleanupUseDbDirect: !!body.cleanupUseDbDirect,
|
|
11828
|
+
// Geo / security
|
|
11781
11829
|
enableGeolocation: !!body.enableGeolocation,
|
|
11782
11830
|
enableSecurityScoring: !!body.enableSecurityScoring,
|
|
11783
11831
|
blockSuspiciousSessions: !!body.blockSuspiciousSessions,
|
|
11784
11832
|
maxFailedLogins: Math.max(1, Math.min(parseInt(body.maxFailedLogins) || 5, 100)),
|
|
11833
|
+
enableGeofencing: !!body.enableGeofencing,
|
|
11834
|
+
allowedCountries: sanitizeCountryList(body.allowedCountries),
|
|
11835
|
+
blockedCountries: sanitizeCountryList(body.blockedCountries),
|
|
11836
|
+
// Notifications
|
|
11785
11837
|
enableEmailAlerts: !!body.enableEmailAlerts,
|
|
11786
11838
|
alertOnSuspiciousLogin: !!body.alertOnSuspiciousLogin,
|
|
11787
11839
|
alertOnNewLocation: !!body.alertOnNewLocation,
|
|
@@ -11789,11 +11841,10 @@ var settings$1 = {
|
|
|
11789
11841
|
enableWebhooks: !!body.enableWebhooks,
|
|
11790
11842
|
discordWebhookUrl: sanitizeWebhookUrl(body.discordWebhookUrl, "discord"),
|
|
11791
11843
|
slackWebhookUrl: sanitizeWebhookUrl(body.slackWebhookUrl, "slack"),
|
|
11792
|
-
|
|
11793
|
-
allowedCountries: sanitizeCountryList(body.allowedCountries),
|
|
11794
|
-
blockedCountries: sanitizeCountryList(body.blockedCountries),
|
|
11844
|
+
// Session policy
|
|
11795
11845
|
strictSessionEnforcement: !!body.strictSessionEnforcement,
|
|
11796
11846
|
trustedProxies: !!body.trustedProxies,
|
|
11847
|
+
// Email templates
|
|
11797
11848
|
emailTemplates: sanitizeEmailTemplates(body.emailTemplates)
|
|
11798
11849
|
};
|
|
11799
11850
|
await pluginStore.set({
|
|
@@ -12177,6 +12228,81 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
12177
12228
|
throw err;
|
|
12178
12229
|
}
|
|
12179
12230
|
},
|
|
12231
|
+
/**
|
|
12232
|
+
* Permanently deletes sessions that have been inactive past the
|
|
12233
|
+
* configured retention window. Distinct from `deleteInactiveSessions`
|
|
12234
|
+
* (which deletes ALL inactive sessions) — this only drops rows older
|
|
12235
|
+
* than `retentionDays`, so recently-terminated sessions stay queryable
|
|
12236
|
+
* for audits.
|
|
12237
|
+
*
|
|
12238
|
+
* @param {Object} [options]
|
|
12239
|
+
* @param {number} [options.retentionDays] Overrides the stored setting.
|
|
12240
|
+
* @param {boolean} [options.useDbDirect] Fast-path via single SQL
|
|
12241
|
+
* DELETE. Bypasses lifecycle hooks; use only when necessary.
|
|
12242
|
+
* @returns {Promise<number>} Number of sessions deleted
|
|
12243
|
+
*/
|
|
12244
|
+
async deleteOldSessions({ retentionDays, useDbDirect } = {}) {
|
|
12245
|
+
try {
|
|
12246
|
+
const settings2 = await getPluginSettings$1(strapi2);
|
|
12247
|
+
const effectiveDays = Number.isFinite(retentionDays) ? retentionDays : settings2.retentionDays || 90;
|
|
12248
|
+
if (effectiveDays === -1) {
|
|
12249
|
+
log.debug("[RETENTION] retentionDays=-1 (forever) — skipping");
|
|
12250
|
+
return 0;
|
|
12251
|
+
}
|
|
12252
|
+
const cutoffDate = new Date(Date.now() - effectiveDays * 24 * 60 * 60 * 1e3);
|
|
12253
|
+
const wantDbDirect = useDbDirect ?? settings2.cleanupUseDbDirect === true;
|
|
12254
|
+
log.info(`[RETENTION] Deleting inactive sessions older than ${effectiveDays} days (before ${cutoffDate.toISOString()})`);
|
|
12255
|
+
if (wantDbDirect) {
|
|
12256
|
+
try {
|
|
12257
|
+
const deleted = await strapi2.db.connection("magic_sessions").where("is_active", false).andWhere(function whereOldEnough() {
|
|
12258
|
+
this.where("logout_time", "<", cutoffDate).orWhere(function whereNullLogout() {
|
|
12259
|
+
this.whereNull("logout_time").andWhere(function whereOldByActivity() {
|
|
12260
|
+
this.where("last_active", "<", cutoffDate).orWhere(function whereNullActivity() {
|
|
12261
|
+
this.whereNull("last_active").andWhere("login_time", "<", cutoffDate);
|
|
12262
|
+
});
|
|
12263
|
+
});
|
|
12264
|
+
});
|
|
12265
|
+
}).del();
|
|
12266
|
+
log.info(`[SUCCESS] Retention (db-direct) deleted ${deleted} old session(s)`);
|
|
12267
|
+
return deleted;
|
|
12268
|
+
} catch (err) {
|
|
12269
|
+
log.warn("[RETENTION] DB-direct delete failed, falling back to Document Service:", err.message);
|
|
12270
|
+
}
|
|
12271
|
+
}
|
|
12272
|
+
let deletedCount = 0;
|
|
12273
|
+
const BATCH = 200;
|
|
12274
|
+
while (true) {
|
|
12275
|
+
const batch = await strapi2.documents(SESSION_UID$1).findMany({
|
|
12276
|
+
filters: {
|
|
12277
|
+
isActive: false,
|
|
12278
|
+
$or: [
|
|
12279
|
+
{ logoutTime: { $lt: cutoffDate } },
|
|
12280
|
+
{ logoutTime: { $null: true }, lastActive: { $lt: cutoffDate } },
|
|
12281
|
+
{ logoutTime: { $null: true }, lastActive: { $null: true }, loginTime: { $lt: cutoffDate } }
|
|
12282
|
+
]
|
|
12283
|
+
},
|
|
12284
|
+
fields: ["documentId"],
|
|
12285
|
+
sort: { loginTime: "asc" },
|
|
12286
|
+
limit: BATCH
|
|
12287
|
+
});
|
|
12288
|
+
if (!batch || batch.length === 0) break;
|
|
12289
|
+
for (const session2 of batch) {
|
|
12290
|
+
try {
|
|
12291
|
+
await strapi2.documents(SESSION_UID$1).delete({ documentId: session2.documentId });
|
|
12292
|
+
deletedCount++;
|
|
12293
|
+
} catch (err) {
|
|
12294
|
+
log.debug(`[RETENTION] Failed to delete session ${session2.documentId}:`, err.message);
|
|
12295
|
+
}
|
|
12296
|
+
}
|
|
12297
|
+
if (batch.length < BATCH) break;
|
|
12298
|
+
}
|
|
12299
|
+
log.info(`[SUCCESS] Retention deleted ${deletedCount} old session(s)`);
|
|
12300
|
+
return deletedCount;
|
|
12301
|
+
} catch (err) {
|
|
12302
|
+
log.error("Error in retention cleanup:", err);
|
|
12303
|
+
return 0;
|
|
12304
|
+
}
|
|
12305
|
+
},
|
|
12180
12306
|
/**
|
|
12181
12307
|
* Permanently deletes all inactive sessions.
|
|
12182
12308
|
* Uses an inner scan loop that tolerates partial failures.
|
|
@@ -12222,7 +12348,7 @@ var session$1 = ({ strapi: strapi2 }) => {
|
|
|
12222
12348
|
}
|
|
12223
12349
|
};
|
|
12224
12350
|
};
|
|
12225
|
-
const version$1 = "4.5.
|
|
12351
|
+
const version$1 = "4.5.1";
|
|
12226
12352
|
const require$$2 = {
|
|
12227
12353
|
version: version$1
|
|
12228
12354
|
};
|
package/package.json
CHANGED