strapi-plugin-magic-sessionmanager 1.0.0
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/LICENSE +22 -0
- package/README.md +1085 -0
- package/dist/_chunks/Analytics-BSvvI-Hg.js +600 -0
- package/dist/_chunks/Analytics-ZQSBqi6k.mjs +598 -0
- package/dist/_chunks/App-DUVizmi_.js +1955 -0
- package/dist/_chunks/App-MPqlEJVm.mjs +1953 -0
- package/dist/_chunks/License-BI7jDjPV.js +403 -0
- package/dist/_chunks/License-WSbNrJuz.mjs +401 -0
- package/dist/_chunks/OnlineUsersWidget-ArMl0nen.mjs +174 -0
- package/dist/_chunks/OnlineUsersWidget-B8JS1xZu.js +174 -0
- package/dist/_chunks/Settings-Ds8IhBiX.mjs +1322 -0
- package/dist/_chunks/Settings-L1ryVQMS.js +1324 -0
- package/dist/_chunks/de-BxFx1pwE.js +23 -0
- package/dist/_chunks/de-CdO3s01z.mjs +23 -0
- package/dist/_chunks/en-CsPpPJL3.mjs +23 -0
- package/dist/_chunks/en-RqmpDHdS.js +23 -0
- package/dist/_chunks/index-BGCs2pNv.mjs +508 -0
- package/dist/_chunks/index-Dd_SkI79.js +507 -0
- package/dist/_chunks/useLicense-Bw66_4CW.js +85 -0
- package/dist/_chunks/useLicense-Dw_-SJHP.mjs +86 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/server/index.js +2127 -0
- package/dist/server/index.mjs +2125 -0
- package/package.json +89 -0
- package/strapi-admin.js +4 -0
- package/strapi-server.js +4 -0
|
@@ -0,0 +1,2125 @@
|
|
|
1
|
+
import require$$0 from "crypto";
|
|
2
|
+
import require$$1 from "os";
|
|
3
|
+
function getDefaultExportFromCjs(x) {
|
|
4
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
5
|
+
}
|
|
6
|
+
var register$1 = async ({ strapi: strapi2 }) => {
|
|
7
|
+
strapi2.log.info("[magic-sessionmanager] 🚀 Plugin registration starting...");
|
|
8
|
+
try {
|
|
9
|
+
const userCT = strapi2.contentType("plugin::users-permissions.user");
|
|
10
|
+
if (!userCT) {
|
|
11
|
+
strapi2.log.error("[magic-sessionmanager] User content type not found");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (userCT.attributes && userCT.attributes.sessions) {
|
|
15
|
+
delete userCT.attributes.sessions;
|
|
16
|
+
strapi2.log.info("[magic-sessionmanager] ✅ Removed sessions field from User content type");
|
|
17
|
+
}
|
|
18
|
+
strapi2.log.info("[magic-sessionmanager] ✅ Plugin registered successfully");
|
|
19
|
+
} catch (err) {
|
|
20
|
+
strapi2.log.error("[magic-sessionmanager] ❌ Registration error:", err);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const getClientIp$1 = (ctx) => {
|
|
24
|
+
try {
|
|
25
|
+
const headers = ctx.request.headers || ctx.request.header || {};
|
|
26
|
+
if (headers["cf-connecting-ip"]) {
|
|
27
|
+
return cleanIp(headers["cf-connecting-ip"]);
|
|
28
|
+
}
|
|
29
|
+
if (headers["true-client-ip"]) {
|
|
30
|
+
return cleanIp(headers["true-client-ip"]);
|
|
31
|
+
}
|
|
32
|
+
if (headers["x-real-ip"]) {
|
|
33
|
+
return cleanIp(headers["x-real-ip"]);
|
|
34
|
+
}
|
|
35
|
+
if (headers["x-forwarded-for"]) {
|
|
36
|
+
const forwardedIps = headers["x-forwarded-for"].split(",");
|
|
37
|
+
const clientIp = forwardedIps[0].trim();
|
|
38
|
+
if (clientIp && !isPrivateIp(clientIp)) {
|
|
39
|
+
return cleanIp(clientIp);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (headers["x-client-ip"]) {
|
|
43
|
+
return cleanIp(headers["x-client-ip"]);
|
|
44
|
+
}
|
|
45
|
+
if (headers["x-cluster-client-ip"]) {
|
|
46
|
+
return cleanIp(headers["x-cluster-client-ip"]);
|
|
47
|
+
}
|
|
48
|
+
if (headers["forwarded"]) {
|
|
49
|
+
const match = headers["forwarded"].match(/for=([^;,\s]+)/);
|
|
50
|
+
if (match && match[1]) {
|
|
51
|
+
return cleanIp(match[1].replace(/"/g, ""));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (ctx.request.ip) {
|
|
55
|
+
return cleanIp(ctx.request.ip);
|
|
56
|
+
}
|
|
57
|
+
return "unknown";
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error("[getClientIp] Error extracting IP:", error);
|
|
60
|
+
return "unknown";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const cleanIp = (ip) => {
|
|
64
|
+
if (!ip) return "unknown";
|
|
65
|
+
ip = ip.split(":")[0];
|
|
66
|
+
if (ip.startsWith("::ffff:")) {
|
|
67
|
+
ip = ip.substring(7);
|
|
68
|
+
}
|
|
69
|
+
ip = ip.trim();
|
|
70
|
+
return ip || "unknown";
|
|
71
|
+
};
|
|
72
|
+
const isPrivateIp = (ip) => {
|
|
73
|
+
if (!ip) return true;
|
|
74
|
+
if (ip === "127.0.0.1" || ip === "localhost" || ip === "::1") return true;
|
|
75
|
+
if (ip.startsWith("192.168.")) return true;
|
|
76
|
+
if (ip.startsWith("10.")) return true;
|
|
77
|
+
if (ip.startsWith("172.")) {
|
|
78
|
+
const second = parseInt(ip.split(".")[1]);
|
|
79
|
+
if (second >= 16 && second <= 31) return true;
|
|
80
|
+
}
|
|
81
|
+
if (ip.startsWith("fc00:") || ip.startsWith("fd00:")) return true;
|
|
82
|
+
if (ip.startsWith("fe80:")) return true;
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
var getClientIp_1 = getClientIp$1;
|
|
86
|
+
var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
87
|
+
return async (ctx, next) => {
|
|
88
|
+
if (ctx.state.user && ctx.state.user.id) {
|
|
89
|
+
try {
|
|
90
|
+
const userId = ctx.state.user.id;
|
|
91
|
+
const activeSessions = await strapi2.entityService.findMany("api::session.session", {
|
|
92
|
+
filters: {
|
|
93
|
+
user: { id: userId },
|
|
94
|
+
isActive: true
|
|
95
|
+
},
|
|
96
|
+
limit: 1
|
|
97
|
+
});
|
|
98
|
+
if (!activeSessions || activeSessions.length === 0) {
|
|
99
|
+
strapi2.log.info(`[magic-sessionmanager] 🚫 Blocked request - User ${userId} has no active sessions`);
|
|
100
|
+
return ctx.unauthorized("All sessions have been terminated. Please login again.");
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
strapi2.log.debug("[magic-sessionmanager] Error checking active sessions:", err.message);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
await next();
|
|
107
|
+
if (ctx.state.user && ctx.state.user.id) {
|
|
108
|
+
try {
|
|
109
|
+
const userId = ctx.state.user.id;
|
|
110
|
+
const sessionId = ctx.state.sessionId;
|
|
111
|
+
await sessionService.touch({
|
|
112
|
+
userId,
|
|
113
|
+
sessionId
|
|
114
|
+
});
|
|
115
|
+
} catch (err) {
|
|
116
|
+
strapi2.log.debug("[magic-sessionmanager] Error updating lastSeen:", err.message);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
const getClientIp = getClientIp_1;
|
|
122
|
+
var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
123
|
+
strapi2.log.info("[magic-sessionmanager] 🚀 Bootstrap starting...");
|
|
124
|
+
try {
|
|
125
|
+
const licenseGuardService = strapi2.plugin("magic-sessionmanager").service("license-guard");
|
|
126
|
+
setTimeout(async () => {
|
|
127
|
+
const licenseStatus = await licenseGuardService.initialize();
|
|
128
|
+
if (!licenseStatus.valid) {
|
|
129
|
+
strapi2.log.error("╔════════════════════════════════════════════════════════════════╗");
|
|
130
|
+
strapi2.log.error("║ ❌ SESSION MANAGER - NO VALID LICENSE ║");
|
|
131
|
+
strapi2.log.error("║ ║");
|
|
132
|
+
strapi2.log.error("║ This plugin requires a valid license to operate. ║");
|
|
133
|
+
strapi2.log.error("║ Please activate your license via Admin UI: ║");
|
|
134
|
+
strapi2.log.error("║ Go to Settings → Sessions → License ║");
|
|
135
|
+
strapi2.log.error("║ ║");
|
|
136
|
+
strapi2.log.error("║ The plugin will run with limited functionality until ║");
|
|
137
|
+
strapi2.log.error("║ a valid license is activated. ║");
|
|
138
|
+
strapi2.log.error("╚════════════════════════════════════════════════════════════════╝");
|
|
139
|
+
} else if (licenseStatus.valid) {
|
|
140
|
+
const pluginStore = strapi2.store({
|
|
141
|
+
type: "plugin",
|
|
142
|
+
name: "magic-sessionmanager"
|
|
143
|
+
});
|
|
144
|
+
const storedKey = await pluginStore.get({ key: "licenseKey" });
|
|
145
|
+
strapi2.log.info("╔════════════════════════════════════════════════════════════════╗");
|
|
146
|
+
strapi2.log.info("║ ✅ SESSION MANAGER LICENSE ACTIVE ║");
|
|
147
|
+
strapi2.log.info("║ ║");
|
|
148
|
+
if (licenseStatus.data) {
|
|
149
|
+
strapi2.log.info(`║ License: ${licenseStatus.data.licenseKey}`.padEnd(66) + "║");
|
|
150
|
+
strapi2.log.info(`║ User: ${licenseStatus.data.firstName} ${licenseStatus.data.lastName}`.padEnd(66) + "║");
|
|
151
|
+
strapi2.log.info(`║ Email: ${licenseStatus.data.email}`.padEnd(66) + "║");
|
|
152
|
+
} else if (storedKey) {
|
|
153
|
+
strapi2.log.info(`║ License: ${storedKey} (Offline Mode)`.padEnd(66) + "║");
|
|
154
|
+
strapi2.log.info(`║ Status: Grace Period Active`.padEnd(66) + "║");
|
|
155
|
+
}
|
|
156
|
+
strapi2.log.info("║ ║");
|
|
157
|
+
strapi2.log.info("║ 🔄 Auto-pinging every 15 minutes ║");
|
|
158
|
+
strapi2.log.info("╚════════════════════════════════════════════════════════════════╝");
|
|
159
|
+
}
|
|
160
|
+
}, 3e3);
|
|
161
|
+
const sessionService = strapi2.plugin("magic-sessionmanager").service("session");
|
|
162
|
+
strapi2.log.info("[magic-sessionmanager] Running initial session cleanup...");
|
|
163
|
+
await sessionService.cleanupInactiveSessions();
|
|
164
|
+
const cleanupInterval = 30 * 60 * 1e3;
|
|
165
|
+
const cleanupIntervalHandle = setInterval(async () => {
|
|
166
|
+
try {
|
|
167
|
+
const service = strapi2.plugin("magic-sessionmanager").service("session");
|
|
168
|
+
await service.cleanupInactiveSessions();
|
|
169
|
+
} catch (err) {
|
|
170
|
+
strapi2.log.error("[magic-sessionmanager] Periodic cleanup error:", err);
|
|
171
|
+
}
|
|
172
|
+
}, cleanupInterval);
|
|
173
|
+
strapi2.log.info("[magic-sessionmanager] ⏰ Periodic cleanup scheduled (every 30 minutes)");
|
|
174
|
+
if (!strapi2.sessionManagerIntervals) {
|
|
175
|
+
strapi2.sessionManagerIntervals = {};
|
|
176
|
+
}
|
|
177
|
+
strapi2.sessionManagerIntervals.cleanup = cleanupIntervalHandle;
|
|
178
|
+
strapi2.server.routes([{
|
|
179
|
+
method: "POST",
|
|
180
|
+
path: "/api/auth/logout",
|
|
181
|
+
handler: async (ctx) => {
|
|
182
|
+
try {
|
|
183
|
+
const token = ctx.request.headers?.authorization?.replace("Bearer ", "");
|
|
184
|
+
if (!token) {
|
|
185
|
+
ctx.status = 200;
|
|
186
|
+
ctx.body = { message: "Logged out successfully" };
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const sessions = await strapi2.entityService.findMany("api::session.session", {
|
|
190
|
+
filters: {
|
|
191
|
+
token,
|
|
192
|
+
isActive: true
|
|
193
|
+
},
|
|
194
|
+
limit: 1
|
|
195
|
+
});
|
|
196
|
+
if (sessions.length > 0) {
|
|
197
|
+
await sessionService.terminateSession({ sessionId: sessions[0].id });
|
|
198
|
+
strapi2.log.info(`[magic-sessionmanager] 🚪 Logout via /api/auth/logout - Session ${sessions[0].id} terminated`);
|
|
199
|
+
}
|
|
200
|
+
ctx.status = 200;
|
|
201
|
+
ctx.body = { message: "Logged out successfully" };
|
|
202
|
+
} catch (err) {
|
|
203
|
+
strapi2.log.error("[magic-sessionmanager] Logout error:", err);
|
|
204
|
+
ctx.status = 200;
|
|
205
|
+
ctx.body = { message: "Logged out successfully" };
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
config: {
|
|
209
|
+
auth: false
|
|
210
|
+
}
|
|
211
|
+
}]);
|
|
212
|
+
strapi2.log.info("[magic-sessionmanager] ✅ /api/auth/logout route registered");
|
|
213
|
+
strapi2.server.use(async (ctx, next) => {
|
|
214
|
+
await next();
|
|
215
|
+
const isAuthLocal = ctx.path === "/api/auth/local" && ctx.method === "POST";
|
|
216
|
+
const isMagicLink = ctx.path.includes("/magic-link/login") && ctx.method === "POST";
|
|
217
|
+
if ((isAuthLocal || isMagicLink) && ctx.status === 200 && ctx.body && ctx.body.user) {
|
|
218
|
+
try {
|
|
219
|
+
const user = ctx.body.user;
|
|
220
|
+
const ip = getClientIp(ctx);
|
|
221
|
+
const userAgent = ctx.request.headers?.["user-agent"] || ctx.request.header?.["user-agent"] || "unknown";
|
|
222
|
+
strapi2.log.info(`[magic-sessionmanager] 🔍 Login detected! User: ${user.id} (${user.email || user.username}) from IP: ${ip}`);
|
|
223
|
+
const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
224
|
+
let shouldBlock = false;
|
|
225
|
+
let blockReason = "";
|
|
226
|
+
let geoData = null;
|
|
227
|
+
if (config2.enableGeolocation || config2.enableSecurityScoring) {
|
|
228
|
+
try {
|
|
229
|
+
const geolocationService = strapi2.plugin("magic-sessionmanager").service("geolocation");
|
|
230
|
+
geoData = await geolocationService.getIpInfo(ip);
|
|
231
|
+
if (config2.blockSuspiciousSessions && geoData) {
|
|
232
|
+
if (geoData.isThreat) {
|
|
233
|
+
shouldBlock = true;
|
|
234
|
+
blockReason = "Known threat IP detected";
|
|
235
|
+
} else if (geoData.isVpn && config2.alertOnVpnProxy) {
|
|
236
|
+
shouldBlock = true;
|
|
237
|
+
blockReason = "VPN detected";
|
|
238
|
+
} else if (geoData.isProxy && config2.alertOnVpnProxy) {
|
|
239
|
+
shouldBlock = true;
|
|
240
|
+
blockReason = "Proxy detected";
|
|
241
|
+
} else if (geoData.securityScore < 50) {
|
|
242
|
+
shouldBlock = true;
|
|
243
|
+
blockReason = `Low security score: ${geoData.securityScore}/100`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (config2.enableGeofencing && geoData && geoData.country_code) {
|
|
247
|
+
const countryCode = geoData.country_code;
|
|
248
|
+
if (config2.blockedCountries && config2.blockedCountries.includes(countryCode)) {
|
|
249
|
+
shouldBlock = true;
|
|
250
|
+
blockReason = `Country ${countryCode} is blocked`;
|
|
251
|
+
}
|
|
252
|
+
if (config2.allowedCountries && config2.allowedCountries.length > 0) {
|
|
253
|
+
if (!config2.allowedCountries.includes(countryCode)) {
|
|
254
|
+
shouldBlock = true;
|
|
255
|
+
blockReason = `Country ${countryCode} is not in allowlist`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} catch (geoErr) {
|
|
260
|
+
strapi2.log.warn("[magic-sessionmanager] Geolocation check failed:", geoErr.message);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (shouldBlock) {
|
|
264
|
+
strapi2.log.warn(`[magic-sessionmanager] 🚫 Blocking login: ${blockReason}`);
|
|
265
|
+
ctx.status = 403;
|
|
266
|
+
ctx.body = {
|
|
267
|
+
error: {
|
|
268
|
+
status: 403,
|
|
269
|
+
message: "Login blocked for security reasons",
|
|
270
|
+
details: { reason: blockReason }
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const newSession = await sessionService.createSession({
|
|
276
|
+
userId: user.id,
|
|
277
|
+
ip,
|
|
278
|
+
userAgent,
|
|
279
|
+
token: ctx.body.jwt
|
|
280
|
+
// Store JWT token reference
|
|
281
|
+
});
|
|
282
|
+
strapi2.log.info(`[magic-sessionmanager] ✅ Session created for user ${user.id} (IP: ${ip})`);
|
|
283
|
+
if (geoData && (config2.enableEmailAlerts || config2.enableWebhooks)) {
|
|
284
|
+
try {
|
|
285
|
+
const notificationService = strapi2.plugin("magic-sessionmanager").service("notifications");
|
|
286
|
+
const isSuspicious = geoData.isVpn || geoData.isProxy || geoData.isThreat || geoData.securityScore < 70;
|
|
287
|
+
if (config2.enableEmailAlerts && config2.alertOnSuspiciousLogin && isSuspicious) {
|
|
288
|
+
await notificationService.sendSuspiciousLoginAlert({
|
|
289
|
+
user,
|
|
290
|
+
session: newSession,
|
|
291
|
+
reason: {
|
|
292
|
+
isVpn: geoData.isVpn,
|
|
293
|
+
isProxy: geoData.isProxy,
|
|
294
|
+
isThreat: geoData.isThreat,
|
|
295
|
+
securityScore: geoData.securityScore
|
|
296
|
+
},
|
|
297
|
+
geoData
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
if (config2.enableWebhooks) {
|
|
301
|
+
const webhookData = notificationService.formatDiscordWebhook({
|
|
302
|
+
event: isSuspicious ? "login.suspicious" : "login.success",
|
|
303
|
+
session: newSession,
|
|
304
|
+
user,
|
|
305
|
+
geoData
|
|
306
|
+
});
|
|
307
|
+
if (config2.discordWebhookUrl) {
|
|
308
|
+
await notificationService.sendWebhook({
|
|
309
|
+
event: "session.login",
|
|
310
|
+
data: webhookData,
|
|
311
|
+
webhookUrl: config2.discordWebhookUrl
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} catch (notifErr) {
|
|
316
|
+
strapi2.log.warn("[magic-sessionmanager] Notification failed:", notifErr.message);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
} catch (err) {
|
|
320
|
+
strapi2.log.error("[magic-sessionmanager] ❌ Error creating session:", err);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
strapi2.log.info("[magic-sessionmanager] ✅ Login/Logout interceptor middleware mounted");
|
|
325
|
+
strapi2.server.use(
|
|
326
|
+
lastSeen({ strapi: strapi2, sessionService })
|
|
327
|
+
);
|
|
328
|
+
strapi2.log.info("[magic-sessionmanager] ✅ LastSeen middleware mounted");
|
|
329
|
+
strapi2.log.info("[magic-sessionmanager] ✅ Bootstrap complete");
|
|
330
|
+
strapi2.log.info("[magic-sessionmanager] 🎉 Session Manager ready! Sessions stored in api::session.session");
|
|
331
|
+
} catch (err) {
|
|
332
|
+
strapi2.log.error("[magic-sessionmanager] ❌ Bootstrap error:", err);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
var destroy$1 = async ({ strapi: strapi2 }) => {
|
|
336
|
+
if (strapi2.licenseGuard && strapi2.licenseGuard.pingInterval) {
|
|
337
|
+
clearInterval(strapi2.licenseGuard.pingInterval);
|
|
338
|
+
strapi2.log.info("[magic-sessionmanager] 🛑 License pinging stopped");
|
|
339
|
+
}
|
|
340
|
+
if (strapi2.sessionManagerIntervals && strapi2.sessionManagerIntervals.cleanup) {
|
|
341
|
+
clearInterval(strapi2.sessionManagerIntervals.cleanup);
|
|
342
|
+
strapi2.log.info("[magic-sessionmanager] 🛑 Session cleanup interval stopped");
|
|
343
|
+
}
|
|
344
|
+
strapi2.log.info("[magic-sessionmanager] ✅ Plugin cleanup completed");
|
|
345
|
+
};
|
|
346
|
+
var config$1 = {
|
|
347
|
+
default: {
|
|
348
|
+
// Rate limit for lastSeen updates (in milliseconds)
|
|
349
|
+
lastSeenRateLimit: 3e4,
|
|
350
|
+
// 30 seconds
|
|
351
|
+
// Session inactivity timeout (in milliseconds)
|
|
352
|
+
// After this time without activity, a session is considered inactive
|
|
353
|
+
inactivityTimeout: 15 * 60 * 1e3
|
|
354
|
+
// 15 minutes
|
|
355
|
+
},
|
|
356
|
+
validator: (config2) => {
|
|
357
|
+
if (config2.lastSeenRateLimit && typeof config2.lastSeenRateLimit !== "number") {
|
|
358
|
+
throw new Error("lastSeenRateLimit must be a number (milliseconds)");
|
|
359
|
+
}
|
|
360
|
+
if (config2.inactivityTimeout && typeof config2.inactivityTimeout !== "number") {
|
|
361
|
+
throw new Error("inactivityTimeout must be a number (milliseconds)");
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
var contentApi$1 = {
|
|
366
|
+
type: "content-api",
|
|
367
|
+
routes: [
|
|
368
|
+
// ============================================================
|
|
369
|
+
// LOGOUT ENDPOINTS
|
|
370
|
+
// ============================================================
|
|
371
|
+
{
|
|
372
|
+
method: "POST",
|
|
373
|
+
path: "/logout",
|
|
374
|
+
handler: "session.logout",
|
|
375
|
+
config: {
|
|
376
|
+
auth: { strategies: ["users-permissions"] },
|
|
377
|
+
description: "Logout current session (requires JWT)"
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
method: "POST",
|
|
382
|
+
path: "/logout-all",
|
|
383
|
+
handler: "session.logoutAll",
|
|
384
|
+
config: {
|
|
385
|
+
auth: { strategies: ["users-permissions"] },
|
|
386
|
+
description: "Logout from all devices (requires JWT)"
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
// ============================================================
|
|
390
|
+
// SESSION QUERIES
|
|
391
|
+
// ============================================================
|
|
392
|
+
{
|
|
393
|
+
method: "GET",
|
|
394
|
+
path: "/user/:userId/sessions",
|
|
395
|
+
handler: "session.getUserSessions",
|
|
396
|
+
config: {
|
|
397
|
+
auth: { strategies: ["users-permissions"] },
|
|
398
|
+
description: "Get own sessions (controller validates user can only see own sessions)"
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
]
|
|
402
|
+
};
|
|
403
|
+
var admin$1 = {
|
|
404
|
+
type: "admin",
|
|
405
|
+
routes: [
|
|
406
|
+
{
|
|
407
|
+
method: "GET",
|
|
408
|
+
path: "/sessions",
|
|
409
|
+
handler: "session.getAllSessionsAdmin",
|
|
410
|
+
config: {
|
|
411
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
412
|
+
description: "Get all sessions - active and inactive (admin)"
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
method: "GET",
|
|
417
|
+
path: "/sessions/active",
|
|
418
|
+
handler: "session.getActiveSessions",
|
|
419
|
+
config: {
|
|
420
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
421
|
+
description: "Get only active sessions (admin)"
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
method: "GET",
|
|
426
|
+
path: "/user/:userId/sessions",
|
|
427
|
+
handler: "session.getUserSessions",
|
|
428
|
+
config: {
|
|
429
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
430
|
+
description: "Get user sessions (admin)"
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
method: "POST",
|
|
435
|
+
path: "/sessions/:sessionId/terminate",
|
|
436
|
+
handler: "session.terminateSingleSession",
|
|
437
|
+
config: {
|
|
438
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
439
|
+
description: "Terminate a specific session (admin)"
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
method: "DELETE",
|
|
444
|
+
path: "/sessions/:sessionId",
|
|
445
|
+
handler: "session.deleteSession",
|
|
446
|
+
config: {
|
|
447
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
448
|
+
description: "Delete a single session permanently (admin)"
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
method: "POST",
|
|
453
|
+
path: "/sessions/clean-inactive",
|
|
454
|
+
handler: "session.cleanInactiveSessions",
|
|
455
|
+
config: {
|
|
456
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
457
|
+
description: "Delete all inactive sessions from database (admin)"
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
method: "POST",
|
|
462
|
+
path: "/user/:userId/terminate-all",
|
|
463
|
+
handler: "session.terminateAllUserSessions",
|
|
464
|
+
config: {
|
|
465
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
466
|
+
description: "Terminate all sessions for a user (admin)"
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
method: "POST",
|
|
471
|
+
path: "/user/:userId/toggle-block",
|
|
472
|
+
handler: "session.toggleUserBlock",
|
|
473
|
+
config: {
|
|
474
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
475
|
+
description: "Toggle user blocked status (admin)"
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
// License Management
|
|
479
|
+
{
|
|
480
|
+
method: "GET",
|
|
481
|
+
path: "/license/status",
|
|
482
|
+
handler: "license.getStatus",
|
|
483
|
+
config: {
|
|
484
|
+
policies: []
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
method: "POST",
|
|
489
|
+
path: "/license/auto-create",
|
|
490
|
+
handler: "license.autoCreate",
|
|
491
|
+
config: {
|
|
492
|
+
policies: []
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
method: "POST",
|
|
497
|
+
path: "/license/create",
|
|
498
|
+
handler: "license.createAndActivate",
|
|
499
|
+
config: {
|
|
500
|
+
policies: []
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
method: "POST",
|
|
505
|
+
path: "/license/ping",
|
|
506
|
+
handler: "license.ping",
|
|
507
|
+
config: {
|
|
508
|
+
policies: []
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
method: "POST",
|
|
513
|
+
path: "/license/store-key",
|
|
514
|
+
handler: "license.storeKey",
|
|
515
|
+
config: {
|
|
516
|
+
policies: []
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
// Geolocation (Premium Feature)
|
|
520
|
+
{
|
|
521
|
+
method: "GET",
|
|
522
|
+
path: "/geolocation/:ipAddress",
|
|
523
|
+
handler: "session.getIpGeolocation",
|
|
524
|
+
config: {
|
|
525
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
526
|
+
description: "Get IP geolocation data (Premium feature)"
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
// Settings Management
|
|
530
|
+
{
|
|
531
|
+
method: "GET",
|
|
532
|
+
path: "/settings",
|
|
533
|
+
handler: "settings.getSettings",
|
|
534
|
+
config: {
|
|
535
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
536
|
+
description: "Get plugin settings"
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
method: "PUT",
|
|
541
|
+
path: "/settings",
|
|
542
|
+
handler: "settings.updateSettings",
|
|
543
|
+
config: {
|
|
544
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
545
|
+
description: "Update plugin settings"
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
]
|
|
549
|
+
};
|
|
550
|
+
const contentApi = contentApi$1;
|
|
551
|
+
const admin = admin$1;
|
|
552
|
+
var routes$1 = {
|
|
553
|
+
admin,
|
|
554
|
+
"content-api": contentApi
|
|
555
|
+
};
|
|
556
|
+
var session$3 = {
|
|
557
|
+
/**
|
|
558
|
+
* Get ALL sessions (active + inactive) - Admin only
|
|
559
|
+
* GET /magic-sessionmanager/sessions
|
|
560
|
+
*/
|
|
561
|
+
async getAllSessionsAdmin(ctx) {
|
|
562
|
+
try {
|
|
563
|
+
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
564
|
+
const sessions = await sessionService.getAllSessions();
|
|
565
|
+
ctx.body = {
|
|
566
|
+
data: sessions,
|
|
567
|
+
meta: {
|
|
568
|
+
count: sessions.length,
|
|
569
|
+
active: sessions.filter((s) => s.isTrulyActive).length,
|
|
570
|
+
inactive: sessions.filter((s) => !s.isTrulyActive).length
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
} catch (err) {
|
|
574
|
+
ctx.throw(500, "Error fetching sessions");
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
/**
|
|
578
|
+
* Get active sessions only
|
|
579
|
+
* GET /magic-sessionmanager/sessions/active
|
|
580
|
+
*/
|
|
581
|
+
async getActiveSessions(ctx) {
|
|
582
|
+
try {
|
|
583
|
+
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
584
|
+
const sessions = await sessionService.getActiveSessions();
|
|
585
|
+
ctx.body = {
|
|
586
|
+
data: sessions,
|
|
587
|
+
meta: {
|
|
588
|
+
count: sessions.length
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
} catch (err) {
|
|
592
|
+
ctx.throw(500, "Error fetching active sessions");
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
/**
|
|
596
|
+
* Get user's sessions
|
|
597
|
+
* GET /magic-sessionmanager/user/:userId/sessions
|
|
598
|
+
* SECURITY: User can only access their own sessions
|
|
599
|
+
*/
|
|
600
|
+
async getUserSessions(ctx) {
|
|
601
|
+
try {
|
|
602
|
+
const { userId } = ctx.params;
|
|
603
|
+
const requestingUserId = ctx.state.user?.id;
|
|
604
|
+
if (requestingUserId && String(requestingUserId) !== String(userId)) {
|
|
605
|
+
strapi.log.warn(`[magic-sessionmanager] Security: User ${requestingUserId} tried to access sessions of user ${userId}`);
|
|
606
|
+
return ctx.forbidden("You can only access your own sessions");
|
|
607
|
+
}
|
|
608
|
+
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
609
|
+
const sessions = await sessionService.getUserSessions(userId);
|
|
610
|
+
ctx.body = {
|
|
611
|
+
data: sessions,
|
|
612
|
+
meta: {
|
|
613
|
+
count: sessions.length
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
} catch (err) {
|
|
617
|
+
ctx.throw(500, "Error fetching user sessions");
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
/**
|
|
621
|
+
* Logout handler - terminates current session
|
|
622
|
+
* POST /api/magic-sessionmanager/logout
|
|
623
|
+
*/
|
|
624
|
+
async logout(ctx) {
|
|
625
|
+
try {
|
|
626
|
+
const userId = ctx.state.user?.id;
|
|
627
|
+
const token = ctx.request.headers.authorization?.replace("Bearer ", "");
|
|
628
|
+
if (!userId) {
|
|
629
|
+
return ctx.throw(401, "Unauthorized");
|
|
630
|
+
}
|
|
631
|
+
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
632
|
+
const sessions = await strapi.entityService.findMany("api::session.session", {
|
|
633
|
+
filters: {
|
|
634
|
+
user: { id: userId },
|
|
635
|
+
token,
|
|
636
|
+
isActive: true
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
if (sessions.length > 0) {
|
|
640
|
+
await sessionService.terminateSession({ sessionId: sessions[0].id });
|
|
641
|
+
strapi.log.info(`[magic-sessionmanager] User ${userId} logged out (session ${sessions[0].id})`);
|
|
642
|
+
}
|
|
643
|
+
ctx.body = {
|
|
644
|
+
message: "Logged out successfully"
|
|
645
|
+
};
|
|
646
|
+
} catch (err) {
|
|
647
|
+
strapi.log.error("[magic-sessionmanager] Logout error:", err);
|
|
648
|
+
ctx.throw(500, "Error during logout");
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
/**
|
|
652
|
+
* Logout from all devices - terminates all sessions for current user
|
|
653
|
+
* POST /api/magic-sessionmanager/logout-all
|
|
654
|
+
*/
|
|
655
|
+
async logoutAll(ctx) {
|
|
656
|
+
try {
|
|
657
|
+
const userId = ctx.state.user?.id;
|
|
658
|
+
if (!userId) {
|
|
659
|
+
return ctx.throw(401, "Unauthorized");
|
|
660
|
+
}
|
|
661
|
+
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
662
|
+
await sessionService.terminateSession({ userId });
|
|
663
|
+
strapi.log.info(`[magic-sessionmanager] User ${userId} logged out from all devices`);
|
|
664
|
+
ctx.body = {
|
|
665
|
+
message: "Logged out from all devices successfully"
|
|
666
|
+
};
|
|
667
|
+
} catch (err) {
|
|
668
|
+
strapi.log.error("[magic-sessionmanager] Logout-all error:", err);
|
|
669
|
+
ctx.throw(500, "Error during logout");
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
/**
|
|
673
|
+
* Terminate specific session
|
|
674
|
+
* DELETE /magic-sessionmanager/sessions/:sessionId
|
|
675
|
+
*/
|
|
676
|
+
async terminateSession(ctx) {
|
|
677
|
+
try {
|
|
678
|
+
const { sessionId } = ctx.params;
|
|
679
|
+
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
680
|
+
await sessionService.terminateSession({ sessionId });
|
|
681
|
+
ctx.body = {
|
|
682
|
+
message: `Session ${sessionId} terminated`
|
|
683
|
+
};
|
|
684
|
+
} catch (err) {
|
|
685
|
+
ctx.throw(500, "Error terminating session");
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
/**
|
|
689
|
+
* Terminate a single session (Admin action)
|
|
690
|
+
* POST /magic-sessionmanager/sessions/:sessionId/terminate
|
|
691
|
+
*/
|
|
692
|
+
async terminateSingleSession(ctx) {
|
|
693
|
+
try {
|
|
694
|
+
const { sessionId } = ctx.params;
|
|
695
|
+
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
696
|
+
await sessionService.terminateSession({ sessionId });
|
|
697
|
+
ctx.body = {
|
|
698
|
+
message: `Session ${sessionId} terminated`,
|
|
699
|
+
success: true
|
|
700
|
+
};
|
|
701
|
+
} catch (err) {
|
|
702
|
+
strapi.log.error("[magic-sessionmanager] Error terminating session:", err);
|
|
703
|
+
ctx.throw(500, "Error terminating session");
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
/**
|
|
707
|
+
* Terminate ALL sessions for a specific user (Admin action)
|
|
708
|
+
* POST /magic-sessionmanager/user/:userId/terminate-all
|
|
709
|
+
*/
|
|
710
|
+
async terminateAllUserSessions(ctx) {
|
|
711
|
+
try {
|
|
712
|
+
const { userId } = ctx.params;
|
|
713
|
+
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
714
|
+
await sessionService.terminateSession({ userId });
|
|
715
|
+
ctx.body = {
|
|
716
|
+
message: `All sessions terminated for user ${userId}`,
|
|
717
|
+
success: true
|
|
718
|
+
};
|
|
719
|
+
} catch (err) {
|
|
720
|
+
strapi.log.error("[magic-sessionmanager] Error terminating all user sessions:", err);
|
|
721
|
+
ctx.throw(500, "Error terminating all user sessions");
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
/**
|
|
725
|
+
* Get IP Geolocation data (Premium feature)
|
|
726
|
+
* GET /magic-sessionmanager/geolocation/:ipAddress
|
|
727
|
+
*/
|
|
728
|
+
async getIpGeolocation(ctx) {
|
|
729
|
+
try {
|
|
730
|
+
const { ipAddress } = ctx.params;
|
|
731
|
+
if (!ipAddress) {
|
|
732
|
+
return ctx.badRequest("IP address is required");
|
|
733
|
+
}
|
|
734
|
+
const licenseGuard2 = strapi.plugin("magic-sessionmanager").service("license-guard");
|
|
735
|
+
const pluginStore = strapi.store({
|
|
736
|
+
type: "plugin",
|
|
737
|
+
name: "magic-sessionmanager"
|
|
738
|
+
});
|
|
739
|
+
const licenseKey = await pluginStore.get({ key: "licenseKey" });
|
|
740
|
+
if (!licenseKey) {
|
|
741
|
+
return ctx.forbidden("Premium license required for geolocation features");
|
|
742
|
+
}
|
|
743
|
+
const license2 = await licenseGuard2.getLicenseByKey(licenseKey);
|
|
744
|
+
if (!license2 || !license2.featurePremium) {
|
|
745
|
+
return ctx.forbidden("Premium license required for geolocation features");
|
|
746
|
+
}
|
|
747
|
+
const geolocationService = strapi.plugin("magic-sessionmanager").service("geolocation");
|
|
748
|
+
const ipData = await geolocationService.getIpInfo(ipAddress);
|
|
749
|
+
ctx.body = {
|
|
750
|
+
success: true,
|
|
751
|
+
data: ipData
|
|
752
|
+
};
|
|
753
|
+
} catch (err) {
|
|
754
|
+
strapi.log.error("[magic-sessionmanager] Error getting IP geolocation:", err);
|
|
755
|
+
ctx.throw(500, "Error fetching IP geolocation data");
|
|
756
|
+
}
|
|
757
|
+
},
|
|
758
|
+
/**
|
|
759
|
+
* Delete a single session permanently (Admin action)
|
|
760
|
+
* DELETE /magic-sessionmanager/sessions/:sessionId
|
|
761
|
+
*/
|
|
762
|
+
async deleteSession(ctx) {
|
|
763
|
+
try {
|
|
764
|
+
const { sessionId } = ctx.params;
|
|
765
|
+
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
766
|
+
await sessionService.deleteSession(sessionId);
|
|
767
|
+
ctx.body = {
|
|
768
|
+
message: `Session ${sessionId} permanently deleted`,
|
|
769
|
+
success: true
|
|
770
|
+
};
|
|
771
|
+
} catch (err) {
|
|
772
|
+
strapi.log.error("[magic-sessionmanager] Error deleting session:", err);
|
|
773
|
+
ctx.throw(500, "Error deleting session");
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
/**
|
|
777
|
+
* Delete all inactive sessions (Admin action)
|
|
778
|
+
* POST /magic-sessionmanager/sessions/clean-inactive
|
|
779
|
+
*/
|
|
780
|
+
async cleanInactiveSessions(ctx) {
|
|
781
|
+
try {
|
|
782
|
+
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
783
|
+
const deletedCount = await sessionService.deleteInactiveSessions();
|
|
784
|
+
ctx.body = {
|
|
785
|
+
message: `Successfully deleted ${deletedCount} inactive sessions`,
|
|
786
|
+
success: true,
|
|
787
|
+
deletedCount
|
|
788
|
+
};
|
|
789
|
+
} catch (err) {
|
|
790
|
+
strapi.log.error("[magic-sessionmanager] Error cleaning inactive sessions:", err);
|
|
791
|
+
ctx.throw(500, "Error deleting inactive sessions");
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
/**
|
|
795
|
+
* Toggle user blocked status
|
|
796
|
+
* POST /magic-sessionmanager/user/:userId/toggle-block
|
|
797
|
+
*/
|
|
798
|
+
async toggleUserBlock(ctx) {
|
|
799
|
+
try {
|
|
800
|
+
const { userId } = ctx.params;
|
|
801
|
+
const user = await strapi.entityService.findOne("plugin::users-permissions.user", userId);
|
|
802
|
+
if (!user) {
|
|
803
|
+
return ctx.throw(404, "User not found");
|
|
804
|
+
}
|
|
805
|
+
const newBlockedStatus = !user.blocked;
|
|
806
|
+
await strapi.entityService.update("plugin::users-permissions.user", userId, {
|
|
807
|
+
data: {
|
|
808
|
+
blocked: newBlockedStatus
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
if (newBlockedStatus) {
|
|
812
|
+
const sessionService = strapi.plugin("magic-sessionmanager").service("session");
|
|
813
|
+
await sessionService.terminateSession({ userId });
|
|
814
|
+
}
|
|
815
|
+
ctx.body = {
|
|
816
|
+
message: `User ${newBlockedStatus ? "blocked" : "unblocked"} successfully`,
|
|
817
|
+
blocked: newBlockedStatus,
|
|
818
|
+
success: true
|
|
819
|
+
};
|
|
820
|
+
} catch (err) {
|
|
821
|
+
strapi.log.error("[magic-sessionmanager] Error toggling user block:", err);
|
|
822
|
+
ctx.throw(500, "Error toggling user block status");
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
var license$1 = ({ strapi: strapi2 }) => ({
|
|
827
|
+
/**
|
|
828
|
+
* Auto-create license with logged-in admin user data
|
|
829
|
+
*/
|
|
830
|
+
async autoCreate(ctx) {
|
|
831
|
+
try {
|
|
832
|
+
const adminUser = ctx.state.user;
|
|
833
|
+
if (!adminUser) {
|
|
834
|
+
return ctx.unauthorized("No admin user logged in");
|
|
835
|
+
}
|
|
836
|
+
const licenseGuard2 = strapi2.plugin("magic-sessionmanager").service("license-guard");
|
|
837
|
+
const license2 = await licenseGuard2.createLicense({
|
|
838
|
+
email: adminUser.email,
|
|
839
|
+
firstName: adminUser.firstname || "Admin",
|
|
840
|
+
lastName: adminUser.lastname || "User"
|
|
841
|
+
});
|
|
842
|
+
if (!license2) {
|
|
843
|
+
return ctx.badRequest("Failed to create license");
|
|
844
|
+
}
|
|
845
|
+
await licenseGuard2.storeLicenseKey(license2.licenseKey);
|
|
846
|
+
const pingInterval = licenseGuard2.startPinging(license2.licenseKey, 15);
|
|
847
|
+
strapi2.licenseGuard = {
|
|
848
|
+
licenseKey: license2.licenseKey,
|
|
849
|
+
pingInterval,
|
|
850
|
+
data: license2
|
|
851
|
+
};
|
|
852
|
+
return ctx.send({
|
|
853
|
+
success: true,
|
|
854
|
+
message: "License automatically created and activated",
|
|
855
|
+
data: license2
|
|
856
|
+
});
|
|
857
|
+
} catch (error) {
|
|
858
|
+
strapi2.log.error("[magic-sessionmanager] Error auto-creating license:", error);
|
|
859
|
+
return ctx.badRequest("Error creating license");
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
/**
|
|
863
|
+
* Get current license status
|
|
864
|
+
*/
|
|
865
|
+
async getStatus(ctx) {
|
|
866
|
+
try {
|
|
867
|
+
const licenseGuard2 = strapi2.plugin("magic-sessionmanager").service("license-guard");
|
|
868
|
+
const pluginStore = strapi2.store({
|
|
869
|
+
type: "plugin",
|
|
870
|
+
name: "magic-sessionmanager"
|
|
871
|
+
});
|
|
872
|
+
const licenseKey = await pluginStore.get({ key: "licenseKey" });
|
|
873
|
+
if (!licenseKey) {
|
|
874
|
+
strapi2.log.debug("[magic-sessionmanager] No license key in store - demo mode");
|
|
875
|
+
return ctx.send({
|
|
876
|
+
success: false,
|
|
877
|
+
demo: true,
|
|
878
|
+
valid: false,
|
|
879
|
+
message: "No license found. Running in demo mode."
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
strapi2.log.info(`[magic-sessionmanager/license-controller] Checking stored license: ${licenseKey}`);
|
|
883
|
+
const verification = await licenseGuard2.verifyLicense(licenseKey);
|
|
884
|
+
const license2 = await licenseGuard2.getLicenseByKey(licenseKey);
|
|
885
|
+
strapi2.log.info("[magic-sessionmanager/license-controller] License data from MagicAPI:", {
|
|
886
|
+
licenseKey: license2?.licenseKey,
|
|
887
|
+
email: license2?.email,
|
|
888
|
+
featurePremium: license2?.featurePremium,
|
|
889
|
+
isActive: license2?.isActive,
|
|
890
|
+
pluginName: license2?.pluginName
|
|
891
|
+
});
|
|
892
|
+
return ctx.send({
|
|
893
|
+
success: true,
|
|
894
|
+
valid: verification.valid,
|
|
895
|
+
demo: false,
|
|
896
|
+
data: {
|
|
897
|
+
licenseKey,
|
|
898
|
+
email: license2?.email || null,
|
|
899
|
+
firstName: license2?.firstName || null,
|
|
900
|
+
lastName: license2?.lastName || null,
|
|
901
|
+
isActive: license2?.isActive || false,
|
|
902
|
+
isExpired: license2?.isExpired || false,
|
|
903
|
+
isOnline: license2?.isOnline || false,
|
|
904
|
+
expiresAt: license2?.expiresAt,
|
|
905
|
+
lastPingAt: license2?.lastPingAt,
|
|
906
|
+
deviceName: license2?.deviceName,
|
|
907
|
+
deviceId: license2?.deviceId,
|
|
908
|
+
ipAddress: license2?.ipAddress,
|
|
909
|
+
features: {
|
|
910
|
+
premium: license2?.featurePremium || false,
|
|
911
|
+
advanced: license2?.featureAdvanced || false,
|
|
912
|
+
enterprise: license2?.featureEnterprise || false,
|
|
913
|
+
custom: license2?.featureCustom || false
|
|
914
|
+
},
|
|
915
|
+
maxDevices: license2?.maxDevices || 1,
|
|
916
|
+
currentDevices: license2?.currentDevices || 0
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
} catch (error) {
|
|
920
|
+
strapi2.log.error("[magic-sessionmanager] Error getting license status:", error);
|
|
921
|
+
return ctx.badRequest("Error getting license status");
|
|
922
|
+
}
|
|
923
|
+
},
|
|
924
|
+
/**
|
|
925
|
+
* Create and activate a new license
|
|
926
|
+
*/
|
|
927
|
+
async createAndActivate(ctx) {
|
|
928
|
+
try {
|
|
929
|
+
const { email, firstName, lastName } = ctx.request.body;
|
|
930
|
+
if (!email || !firstName || !lastName) {
|
|
931
|
+
return ctx.badRequest("Email, firstName, and lastName are required");
|
|
932
|
+
}
|
|
933
|
+
const licenseGuard2 = strapi2.plugin("magic-sessionmanager").service("license-guard");
|
|
934
|
+
const license2 = await licenseGuard2.createLicense({ email, firstName, lastName });
|
|
935
|
+
if (!license2) {
|
|
936
|
+
return ctx.badRequest("Failed to create license");
|
|
937
|
+
}
|
|
938
|
+
await licenseGuard2.storeLicenseKey(license2.licenseKey);
|
|
939
|
+
const pingInterval = licenseGuard2.startPinging(license2.licenseKey, 15);
|
|
940
|
+
strapi2.licenseGuard = {
|
|
941
|
+
licenseKey: license2.licenseKey,
|
|
942
|
+
pingInterval,
|
|
943
|
+
data: license2
|
|
944
|
+
};
|
|
945
|
+
return ctx.send({
|
|
946
|
+
success: true,
|
|
947
|
+
message: "License created and activated successfully",
|
|
948
|
+
data: license2
|
|
949
|
+
});
|
|
950
|
+
} catch (error) {
|
|
951
|
+
strapi2.log.error("[magic-sessionmanager] Error creating license:", error);
|
|
952
|
+
return ctx.badRequest("Error creating license");
|
|
953
|
+
}
|
|
954
|
+
},
|
|
955
|
+
/**
|
|
956
|
+
* Manually ping the current license
|
|
957
|
+
*/
|
|
958
|
+
async ping(ctx) {
|
|
959
|
+
try {
|
|
960
|
+
const pluginStore = strapi2.store({
|
|
961
|
+
type: "plugin",
|
|
962
|
+
name: "magic-sessionmanager"
|
|
963
|
+
});
|
|
964
|
+
const licenseKey = await pluginStore.get({ key: "licenseKey" });
|
|
965
|
+
if (!licenseKey) {
|
|
966
|
+
return ctx.badRequest("No license key found");
|
|
967
|
+
}
|
|
968
|
+
const licenseGuard2 = strapi2.plugin("magic-sessionmanager").service("license-guard");
|
|
969
|
+
const pingResult = await licenseGuard2.pingLicense(licenseKey);
|
|
970
|
+
if (!pingResult) {
|
|
971
|
+
return ctx.badRequest("Ping failed");
|
|
972
|
+
}
|
|
973
|
+
return ctx.send({
|
|
974
|
+
success: true,
|
|
975
|
+
message: "License pinged successfully",
|
|
976
|
+
data: pingResult
|
|
977
|
+
});
|
|
978
|
+
} catch (error) {
|
|
979
|
+
strapi2.log.error("[magic-sessionmanager] Error pinging license:", error);
|
|
980
|
+
return ctx.badRequest("Error pinging license");
|
|
981
|
+
}
|
|
982
|
+
},
|
|
983
|
+
/**
|
|
984
|
+
* Store and validate an existing license key
|
|
985
|
+
*/
|
|
986
|
+
async storeKey(ctx) {
|
|
987
|
+
try {
|
|
988
|
+
const { licenseKey, email } = ctx.request.body;
|
|
989
|
+
if (!licenseKey || !licenseKey.trim()) {
|
|
990
|
+
return ctx.badRequest("License key is required");
|
|
991
|
+
}
|
|
992
|
+
if (!email || !email.trim()) {
|
|
993
|
+
return ctx.badRequest("Email address is required");
|
|
994
|
+
}
|
|
995
|
+
const trimmedKey = licenseKey.trim();
|
|
996
|
+
const trimmedEmail = email.trim().toLowerCase();
|
|
997
|
+
const licenseGuard2 = strapi2.plugin("magic-sessionmanager").service("license-guard");
|
|
998
|
+
const verification = await licenseGuard2.verifyLicense(trimmedKey);
|
|
999
|
+
if (!verification.valid) {
|
|
1000
|
+
strapi2.log.warn(`[magic-sessionmanager] ⚠️ Invalid license key attempted: ${trimmedKey.substring(0, 8)}...`);
|
|
1001
|
+
return ctx.badRequest("Invalid or expired license key");
|
|
1002
|
+
}
|
|
1003
|
+
const license2 = await licenseGuard2.getLicenseByKey(trimmedKey);
|
|
1004
|
+
if (!license2) {
|
|
1005
|
+
strapi2.log.warn(`[magic-sessionmanager] ⚠️ License not found in database: ${trimmedKey.substring(0, 8)}...`);
|
|
1006
|
+
return ctx.badRequest("License not found");
|
|
1007
|
+
}
|
|
1008
|
+
if (license2.email.toLowerCase() !== trimmedEmail) {
|
|
1009
|
+
strapi2.log.warn(`[magic-sessionmanager] ⚠️ Email mismatch for license key: ${trimmedKey.substring(0, 8)}... (Attempted: ${trimmedEmail})`);
|
|
1010
|
+
return ctx.badRequest("Email address does not match this license key");
|
|
1011
|
+
}
|
|
1012
|
+
await licenseGuard2.storeLicenseKey(trimmedKey);
|
|
1013
|
+
const pingInterval = licenseGuard2.startPinging(trimmedKey, 15);
|
|
1014
|
+
strapi2.licenseGuard = {
|
|
1015
|
+
licenseKey: trimmedKey,
|
|
1016
|
+
pingInterval,
|
|
1017
|
+
data: verification.data
|
|
1018
|
+
};
|
|
1019
|
+
strapi2.log.info(`[magic-sessionmanager] ✅ Existing license key validated and stored: ${trimmedKey.substring(0, 8)}... (Email: ${trimmedEmail})`);
|
|
1020
|
+
return ctx.send({
|
|
1021
|
+
success: true,
|
|
1022
|
+
message: "License key validated and activated successfully",
|
|
1023
|
+
data: verification.data
|
|
1024
|
+
});
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
strapi2.log.error("[magic-sessionmanager] Error storing license key:", error);
|
|
1027
|
+
return ctx.badRequest("Error validating license key");
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
var settings$1 = {
|
|
1032
|
+
/**
|
|
1033
|
+
* Get plugin settings
|
|
1034
|
+
*/
|
|
1035
|
+
async getSettings(ctx) {
|
|
1036
|
+
try {
|
|
1037
|
+
const pluginStore = strapi.store({
|
|
1038
|
+
type: "plugin",
|
|
1039
|
+
name: "magic-sessionmanager"
|
|
1040
|
+
});
|
|
1041
|
+
let settings2 = await pluginStore.get({ key: "settings" });
|
|
1042
|
+
if (!settings2) {
|
|
1043
|
+
settings2 = {
|
|
1044
|
+
inactivityTimeout: 15,
|
|
1045
|
+
cleanupInterval: 30,
|
|
1046
|
+
lastSeenRateLimit: 30,
|
|
1047
|
+
retentionDays: 90,
|
|
1048
|
+
enableGeolocation: true,
|
|
1049
|
+
enableSecurityScoring: true,
|
|
1050
|
+
blockSuspiciousSessions: false,
|
|
1051
|
+
maxFailedLogins: 5,
|
|
1052
|
+
enableEmailAlerts: false,
|
|
1053
|
+
alertOnSuspiciousLogin: true,
|
|
1054
|
+
alertOnNewLocation: true,
|
|
1055
|
+
alertOnVpnProxy: true,
|
|
1056
|
+
enableWebhooks: false,
|
|
1057
|
+
discordWebhookUrl: "",
|
|
1058
|
+
slackWebhookUrl: "",
|
|
1059
|
+
enableGeofencing: false,
|
|
1060
|
+
allowedCountries: [],
|
|
1061
|
+
blockedCountries: [],
|
|
1062
|
+
emailTemplates: {
|
|
1063
|
+
suspiciousLogin: { subject: "", html: "", text: "" },
|
|
1064
|
+
newLocation: { subject: "", html: "", text: "" },
|
|
1065
|
+
vpnProxy: { subject: "", html: "", text: "" }
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
ctx.send({
|
|
1070
|
+
settings: settings2,
|
|
1071
|
+
success: true
|
|
1072
|
+
});
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
strapi.log.error("[magic-sessionmanager/settings] Error getting settings:", error);
|
|
1075
|
+
ctx.badRequest("Error loading settings");
|
|
1076
|
+
}
|
|
1077
|
+
},
|
|
1078
|
+
/**
|
|
1079
|
+
* Update plugin settings
|
|
1080
|
+
*/
|
|
1081
|
+
async updateSettings(ctx) {
|
|
1082
|
+
try {
|
|
1083
|
+
const { body } = ctx.request;
|
|
1084
|
+
if (!body) {
|
|
1085
|
+
return ctx.badRequest("Settings data is required");
|
|
1086
|
+
}
|
|
1087
|
+
const pluginStore = strapi.store({
|
|
1088
|
+
type: "plugin",
|
|
1089
|
+
name: "magic-sessionmanager"
|
|
1090
|
+
});
|
|
1091
|
+
const sanitizedSettings = {
|
|
1092
|
+
inactivityTimeout: parseInt(body.inactivityTimeout) || 15,
|
|
1093
|
+
cleanupInterval: parseInt(body.cleanupInterval) || 30,
|
|
1094
|
+
lastSeenRateLimit: parseInt(body.lastSeenRateLimit) || 30,
|
|
1095
|
+
retentionDays: parseInt(body.retentionDays) || 90,
|
|
1096
|
+
enableGeolocation: !!body.enableGeolocation,
|
|
1097
|
+
enableSecurityScoring: !!body.enableSecurityScoring,
|
|
1098
|
+
blockSuspiciousSessions: !!body.blockSuspiciousSessions,
|
|
1099
|
+
maxFailedLogins: parseInt(body.maxFailedLogins) || 5,
|
|
1100
|
+
enableEmailAlerts: !!body.enableEmailAlerts,
|
|
1101
|
+
alertOnSuspiciousLogin: !!body.alertOnSuspiciousLogin,
|
|
1102
|
+
alertOnNewLocation: !!body.alertOnNewLocation,
|
|
1103
|
+
alertOnVpnProxy: !!body.alertOnVpnProxy,
|
|
1104
|
+
enableWebhooks: !!body.enableWebhooks,
|
|
1105
|
+
discordWebhookUrl: String(body.discordWebhookUrl || ""),
|
|
1106
|
+
slackWebhookUrl: String(body.slackWebhookUrl || ""),
|
|
1107
|
+
enableGeofencing: !!body.enableGeofencing,
|
|
1108
|
+
allowedCountries: Array.isArray(body.allowedCountries) ? body.allowedCountries : [],
|
|
1109
|
+
blockedCountries: Array.isArray(body.blockedCountries) ? body.blockedCountries : [],
|
|
1110
|
+
emailTemplates: body.emailTemplates || {
|
|
1111
|
+
suspiciousLogin: { subject: "", html: "", text: "" },
|
|
1112
|
+
newLocation: { subject: "", html: "", text: "" },
|
|
1113
|
+
vpnProxy: { subject: "", html: "", text: "" }
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
await pluginStore.set({
|
|
1117
|
+
key: "settings",
|
|
1118
|
+
value: sanitizedSettings
|
|
1119
|
+
});
|
|
1120
|
+
strapi.log.info("[magic-sessionmanager/settings] Settings updated successfully");
|
|
1121
|
+
ctx.send({
|
|
1122
|
+
settings: sanitizedSettings,
|
|
1123
|
+
success: true,
|
|
1124
|
+
message: "Settings saved successfully!"
|
|
1125
|
+
});
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
strapi.log.error("[magic-sessionmanager/settings] Error updating settings:", error);
|
|
1128
|
+
ctx.badRequest("Error saving settings");
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
const session$2 = session$3;
|
|
1133
|
+
const license = license$1;
|
|
1134
|
+
const settings = settings$1;
|
|
1135
|
+
var controllers$1 = {
|
|
1136
|
+
session: session$2,
|
|
1137
|
+
license,
|
|
1138
|
+
settings
|
|
1139
|
+
};
|
|
1140
|
+
var session$1 = ({ strapi: strapi2 }) => ({
|
|
1141
|
+
/**
|
|
1142
|
+
* Create a new session record
|
|
1143
|
+
* @param {Object} params - { userId, ip, userAgent, token }
|
|
1144
|
+
* @returns {Promise<Object>} Created session
|
|
1145
|
+
*/
|
|
1146
|
+
async createSession({ userId, ip = "unknown", userAgent = "unknown", token }) {
|
|
1147
|
+
try {
|
|
1148
|
+
const now = /* @__PURE__ */ new Date();
|
|
1149
|
+
const session2 = await strapi2.entityService.create("api::session.session", {
|
|
1150
|
+
data: {
|
|
1151
|
+
user: userId,
|
|
1152
|
+
ipAddress: ip.substring(0, 45),
|
|
1153
|
+
userAgent: userAgent.substring(0, 500),
|
|
1154
|
+
loginTime: now,
|
|
1155
|
+
lastActive: now,
|
|
1156
|
+
isActive: true,
|
|
1157
|
+
token
|
|
1158
|
+
// Store JWT for logout matching
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
strapi2.log.info(`[magic-sessionmanager] ✅ Session ${session2.id} created for user ${userId}`);
|
|
1162
|
+
return session2;
|
|
1163
|
+
} catch (err) {
|
|
1164
|
+
strapi2.log.error("[magic-sessionmanager] Error creating session:", err);
|
|
1165
|
+
throw err;
|
|
1166
|
+
}
|
|
1167
|
+
},
|
|
1168
|
+
/**
|
|
1169
|
+
* Terminate a session or all sessions for a user
|
|
1170
|
+
* @param {Object} params - { sessionId | userId }
|
|
1171
|
+
* @returns {Promise<void>}
|
|
1172
|
+
*/
|
|
1173
|
+
async terminateSession({ sessionId, userId }) {
|
|
1174
|
+
try {
|
|
1175
|
+
const now = /* @__PURE__ */ new Date();
|
|
1176
|
+
if (sessionId) {
|
|
1177
|
+
await strapi2.entityService.update("api::session.session", sessionId, {
|
|
1178
|
+
data: {
|
|
1179
|
+
isActive: false,
|
|
1180
|
+
logoutTime: now
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
strapi2.log.info(`[magic-sessionmanager] Session ${sessionId} terminated`);
|
|
1184
|
+
} else if (userId) {
|
|
1185
|
+
const activeSessions = await strapi2.entityService.findMany("api::session.session", {
|
|
1186
|
+
filters: {
|
|
1187
|
+
user: { id: userId },
|
|
1188
|
+
isActive: true
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
for (const session2 of activeSessions) {
|
|
1192
|
+
await strapi2.entityService.update("api::session.session", session2.id, {
|
|
1193
|
+
data: {
|
|
1194
|
+
isActive: false,
|
|
1195
|
+
logoutTime: now
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
strapi2.log.info(`[magic-sessionmanager] All sessions terminated for user ${userId}`);
|
|
1200
|
+
}
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
strapi2.log.error("[magic-sessionmanager] Error terminating session:", err);
|
|
1203
|
+
throw err;
|
|
1204
|
+
}
|
|
1205
|
+
},
|
|
1206
|
+
/**
|
|
1207
|
+
* Get ALL sessions (active + inactive) with accurate online status
|
|
1208
|
+
* @returns {Promise<Array>} All sessions with enhanced data
|
|
1209
|
+
*/
|
|
1210
|
+
async getAllSessions() {
|
|
1211
|
+
try {
|
|
1212
|
+
const sessions = await strapi2.entityService.findMany("api::session.session", {
|
|
1213
|
+
populate: { user: { fields: ["id", "email", "username"] } },
|
|
1214
|
+
sort: { loginTime: "desc" },
|
|
1215
|
+
limit: 1e3
|
|
1216
|
+
// Reasonable limit
|
|
1217
|
+
});
|
|
1218
|
+
const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
1219
|
+
const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
|
|
1220
|
+
const now = /* @__PURE__ */ new Date();
|
|
1221
|
+
const enhancedSessions = sessions.map((session2) => {
|
|
1222
|
+
const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
|
|
1223
|
+
const timeSinceActive = now - lastActiveTime;
|
|
1224
|
+
const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
|
|
1225
|
+
const { token, ...sessionWithoutToken } = session2;
|
|
1226
|
+
return {
|
|
1227
|
+
...sessionWithoutToken,
|
|
1228
|
+
isTrulyActive,
|
|
1229
|
+
minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
|
|
1230
|
+
};
|
|
1231
|
+
});
|
|
1232
|
+
return enhancedSessions;
|
|
1233
|
+
} catch (err) {
|
|
1234
|
+
strapi2.log.error("[magic-sessionmanager] Error getting all sessions:", err);
|
|
1235
|
+
throw err;
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
1238
|
+
/**
|
|
1239
|
+
* Get all active sessions with accurate online status
|
|
1240
|
+
* @returns {Promise<Array>} Active sessions with user data and online status
|
|
1241
|
+
*/
|
|
1242
|
+
async getActiveSessions() {
|
|
1243
|
+
try {
|
|
1244
|
+
const sessions = await strapi2.entityService.findMany("api::session.session", {
|
|
1245
|
+
filters: { isActive: true },
|
|
1246
|
+
populate: { user: { fields: ["id", "email", "username"] } },
|
|
1247
|
+
sort: { loginTime: "desc" }
|
|
1248
|
+
});
|
|
1249
|
+
const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
1250
|
+
const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
|
|
1251
|
+
const now = /* @__PURE__ */ new Date();
|
|
1252
|
+
const enhancedSessions = sessions.map((session2) => {
|
|
1253
|
+
const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
|
|
1254
|
+
const timeSinceActive = now - lastActiveTime;
|
|
1255
|
+
const isTrulyActive = timeSinceActive < inactivityTimeout;
|
|
1256
|
+
const { token, ...sessionWithoutToken } = session2;
|
|
1257
|
+
return {
|
|
1258
|
+
...sessionWithoutToken,
|
|
1259
|
+
isTrulyActive,
|
|
1260
|
+
minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
|
|
1261
|
+
};
|
|
1262
|
+
});
|
|
1263
|
+
return enhancedSessions.filter((s) => s.isTrulyActive);
|
|
1264
|
+
} catch (err) {
|
|
1265
|
+
strapi2.log.error("[magic-sessionmanager] Error getting active sessions:", err);
|
|
1266
|
+
throw err;
|
|
1267
|
+
}
|
|
1268
|
+
},
|
|
1269
|
+
/**
|
|
1270
|
+
* Get all sessions for a specific user
|
|
1271
|
+
* @param {number} userId
|
|
1272
|
+
* @returns {Promise<Array>} User's sessions with accurate online status
|
|
1273
|
+
*/
|
|
1274
|
+
async getUserSessions(userId) {
|
|
1275
|
+
try {
|
|
1276
|
+
const sessions = await strapi2.entityService.findMany("api::session.session", {
|
|
1277
|
+
filters: { user: { id: userId } },
|
|
1278
|
+
sort: { loginTime: "desc" }
|
|
1279
|
+
});
|
|
1280
|
+
const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
1281
|
+
const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
|
|
1282
|
+
const now = /* @__PURE__ */ new Date();
|
|
1283
|
+
const enhancedSessions = sessions.map((session2) => {
|
|
1284
|
+
const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
|
|
1285
|
+
const timeSinceActive = now - lastActiveTime;
|
|
1286
|
+
const isTrulyActive = session2.isActive && timeSinceActive < inactivityTimeout;
|
|
1287
|
+
const { token, ...sessionWithoutToken } = session2;
|
|
1288
|
+
return {
|
|
1289
|
+
...sessionWithoutToken,
|
|
1290
|
+
isTrulyActive,
|
|
1291
|
+
minutesSinceActive: Math.floor(timeSinceActive / 1e3 / 60)
|
|
1292
|
+
};
|
|
1293
|
+
});
|
|
1294
|
+
return enhancedSessions;
|
|
1295
|
+
} catch (err) {
|
|
1296
|
+
strapi2.log.error("[magic-sessionmanager] Error getting user sessions:", err);
|
|
1297
|
+
throw err;
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
/**
|
|
1301
|
+
* Update lastActive timestamp on session (rate-limited to avoid DB noise)
|
|
1302
|
+
* @param {Object} params - { userId, sessionId }
|
|
1303
|
+
* @returns {Promise<void>}
|
|
1304
|
+
*/
|
|
1305
|
+
async touch({ userId, sessionId }) {
|
|
1306
|
+
try {
|
|
1307
|
+
const now = /* @__PURE__ */ new Date();
|
|
1308
|
+
const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
1309
|
+
const rateLimit = config2.lastSeenRateLimit || 3e4;
|
|
1310
|
+
if (sessionId) {
|
|
1311
|
+
const session2 = await strapi2.entityService.findOne("api::session.session", sessionId);
|
|
1312
|
+
if (session2 && session2.lastActive) {
|
|
1313
|
+
const lastActiveTime = new Date(session2.lastActive).getTime();
|
|
1314
|
+
const currentTime = now.getTime();
|
|
1315
|
+
if (currentTime - lastActiveTime > rateLimit) {
|
|
1316
|
+
await strapi2.entityService.update("api::session.session", sessionId, {
|
|
1317
|
+
data: { lastActive: now }
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
} else if (session2) {
|
|
1321
|
+
await strapi2.entityService.update("api::session.session", sessionId, {
|
|
1322
|
+
data: { lastActive: now }
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
} catch (err) {
|
|
1327
|
+
strapi2.log.debug("[magic-sessionmanager] Error touching session:", err.message);
|
|
1328
|
+
}
|
|
1329
|
+
},
|
|
1330
|
+
/**
|
|
1331
|
+
* Cleanup inactive sessions - set isActive to false for sessions older than inactivityTimeout
|
|
1332
|
+
* Should be called on bootstrap to clean up stale sessions
|
|
1333
|
+
*/
|
|
1334
|
+
async cleanupInactiveSessions() {
|
|
1335
|
+
try {
|
|
1336
|
+
const config2 = strapi2.config.get("plugin::magic-sessionmanager") || {};
|
|
1337
|
+
const inactivityTimeout = config2.inactivityTimeout || 15 * 60 * 1e3;
|
|
1338
|
+
const now = /* @__PURE__ */ new Date();
|
|
1339
|
+
const cutoffTime = new Date(now.getTime() - inactivityTimeout);
|
|
1340
|
+
strapi2.log.info(`[magic-sessionmanager] 🧹 Cleaning up sessions inactive since before ${cutoffTime.toISOString()}`);
|
|
1341
|
+
const activeSessions = await strapi2.entityService.findMany("api::session.session", {
|
|
1342
|
+
filters: { isActive: true },
|
|
1343
|
+
fields: ["id", "lastActive", "loginTime"]
|
|
1344
|
+
});
|
|
1345
|
+
let deactivatedCount = 0;
|
|
1346
|
+
for (const session2 of activeSessions) {
|
|
1347
|
+
const lastActiveTime = session2.lastActive ? new Date(session2.lastActive) : new Date(session2.loginTime);
|
|
1348
|
+
if (lastActiveTime < cutoffTime) {
|
|
1349
|
+
await strapi2.entityService.update("api::session.session", session2.id, {
|
|
1350
|
+
data: { isActive: false }
|
|
1351
|
+
});
|
|
1352
|
+
deactivatedCount++;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
strapi2.log.info(`[magic-sessionmanager] ✅ Cleanup complete: ${deactivatedCount} sessions deactivated`);
|
|
1356
|
+
return deactivatedCount;
|
|
1357
|
+
} catch (err) {
|
|
1358
|
+
strapi2.log.error("[magic-sessionmanager] Error cleaning up inactive sessions:", err);
|
|
1359
|
+
throw err;
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
/**
|
|
1363
|
+
* Delete a single session from database
|
|
1364
|
+
* WARNING: This permanently deletes the record!
|
|
1365
|
+
* @param {number} sessionId - Session ID to delete
|
|
1366
|
+
* @returns {Promise<boolean>} Success status
|
|
1367
|
+
*/
|
|
1368
|
+
async deleteSession(sessionId) {
|
|
1369
|
+
try {
|
|
1370
|
+
await strapi2.entityService.delete("api::session.session", sessionId);
|
|
1371
|
+
strapi2.log.info(`[magic-sessionmanager] 🗑️ Session ${sessionId} permanently deleted`);
|
|
1372
|
+
return true;
|
|
1373
|
+
} catch (err) {
|
|
1374
|
+
strapi2.log.error("[magic-sessionmanager] Error deleting session:", err);
|
|
1375
|
+
throw err;
|
|
1376
|
+
}
|
|
1377
|
+
},
|
|
1378
|
+
/**
|
|
1379
|
+
* Delete all inactive sessions from database
|
|
1380
|
+
* WARNING: This permanently deletes records!
|
|
1381
|
+
* @returns {Promise<number>} Number of deleted sessions
|
|
1382
|
+
*/
|
|
1383
|
+
async deleteInactiveSessions() {
|
|
1384
|
+
try {
|
|
1385
|
+
strapi2.log.info("[magic-sessionmanager] 🗑️ Deleting all inactive sessions...");
|
|
1386
|
+
const inactiveSessions = await strapi2.entityService.findMany("api::session.session", {
|
|
1387
|
+
filters: { isActive: false },
|
|
1388
|
+
fields: ["id"]
|
|
1389
|
+
});
|
|
1390
|
+
let deletedCount = 0;
|
|
1391
|
+
for (const session2 of inactiveSessions) {
|
|
1392
|
+
await strapi2.entityService.delete("api::session.session", session2.id);
|
|
1393
|
+
deletedCount++;
|
|
1394
|
+
}
|
|
1395
|
+
strapi2.log.info(`[magic-sessionmanager] ✅ Deleted ${deletedCount} inactive sessions`);
|
|
1396
|
+
return deletedCount;
|
|
1397
|
+
} catch (err) {
|
|
1398
|
+
strapi2.log.error("[magic-sessionmanager] Error deleting inactive sessions:", err);
|
|
1399
|
+
throw err;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
const crypto = require$$0;
|
|
1404
|
+
const os = require$$1;
|
|
1405
|
+
const LICENSE_SERVER_URL = "https://magicapi.fitlex.me";
|
|
1406
|
+
var licenseGuard$1 = ({ strapi: strapi2 }) => ({
|
|
1407
|
+
/**
|
|
1408
|
+
* Get license server URL
|
|
1409
|
+
*/
|
|
1410
|
+
getLicenseServerUrl() {
|
|
1411
|
+
return LICENSE_SERVER_URL;
|
|
1412
|
+
},
|
|
1413
|
+
/**
|
|
1414
|
+
* Generate device ID
|
|
1415
|
+
*/
|
|
1416
|
+
generateDeviceId() {
|
|
1417
|
+
try {
|
|
1418
|
+
const networkInterfaces = os.networkInterfaces();
|
|
1419
|
+
const macAddresses = [];
|
|
1420
|
+
Object.values(networkInterfaces).forEach((interfaces) => {
|
|
1421
|
+
interfaces?.forEach((iface) => {
|
|
1422
|
+
if (iface.mac && iface.mac !== "00:00:00:00:00:00") {
|
|
1423
|
+
macAddresses.push(iface.mac);
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
});
|
|
1427
|
+
const identifier = `${macAddresses.join("-")}-${os.hostname()}`;
|
|
1428
|
+
return crypto.createHash("sha256").update(identifier).digest("hex").substring(0, 32);
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
return crypto.randomBytes(16).toString("hex");
|
|
1431
|
+
}
|
|
1432
|
+
},
|
|
1433
|
+
getDeviceName() {
|
|
1434
|
+
try {
|
|
1435
|
+
return os.hostname() || "Unknown Device";
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
return "Unknown Device";
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
getIpAddress() {
|
|
1441
|
+
try {
|
|
1442
|
+
const networkInterfaces = os.networkInterfaces();
|
|
1443
|
+
for (const name of Object.keys(networkInterfaces)) {
|
|
1444
|
+
const interfaces = networkInterfaces[name];
|
|
1445
|
+
if (interfaces) {
|
|
1446
|
+
for (const iface of interfaces) {
|
|
1447
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
1448
|
+
return iface.address;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return "127.0.0.1";
|
|
1454
|
+
} catch (error) {
|
|
1455
|
+
return "127.0.0.1";
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
getUserAgent() {
|
|
1459
|
+
return `Strapi/${strapi2.config.get("info.strapi") || "5.0.0"} Node/${process.version} ${os.platform()}/${os.release()}`;
|
|
1460
|
+
},
|
|
1461
|
+
async createLicense({ email, firstName, lastName }) {
|
|
1462
|
+
try {
|
|
1463
|
+
const deviceId = this.generateDeviceId();
|
|
1464
|
+
const deviceName = this.getDeviceName();
|
|
1465
|
+
const ipAddress = this.getIpAddress();
|
|
1466
|
+
const userAgent = this.getUserAgent();
|
|
1467
|
+
const licenseServerUrl = this.getLicenseServerUrl();
|
|
1468
|
+
const response = await fetch(`${licenseServerUrl}/api/licenses/create`, {
|
|
1469
|
+
method: "POST",
|
|
1470
|
+
headers: { "Content-Type": "application/json" },
|
|
1471
|
+
body: JSON.stringify({
|
|
1472
|
+
email,
|
|
1473
|
+
firstName,
|
|
1474
|
+
lastName,
|
|
1475
|
+
deviceName,
|
|
1476
|
+
deviceId,
|
|
1477
|
+
ipAddress,
|
|
1478
|
+
userAgent,
|
|
1479
|
+
pluginName: "magic-sessionmanager",
|
|
1480
|
+
productName: "Magic Session Manager - Premium Session Tracking"
|
|
1481
|
+
})
|
|
1482
|
+
});
|
|
1483
|
+
const data = await response.json();
|
|
1484
|
+
if (data.success) {
|
|
1485
|
+
strapi2.log.info("[magic-sessionmanager] ✅ License created:", data.data.licenseKey);
|
|
1486
|
+
return data.data;
|
|
1487
|
+
} else {
|
|
1488
|
+
strapi2.log.error("[magic-sessionmanager] ❌ License creation failed:", data);
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
strapi2.log.error("[magic-sessionmanager] ❌ Error creating license:", error);
|
|
1493
|
+
return null;
|
|
1494
|
+
}
|
|
1495
|
+
},
|
|
1496
|
+
async verifyLicense(licenseKey, allowGracePeriod = false) {
|
|
1497
|
+
try {
|
|
1498
|
+
const controller = new AbortController();
|
|
1499
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
1500
|
+
const licenseServerUrl = this.getLicenseServerUrl();
|
|
1501
|
+
const response = await fetch(`${licenseServerUrl}/api/licenses/verify`, {
|
|
1502
|
+
method: "POST",
|
|
1503
|
+
headers: { "Content-Type": "application/json" },
|
|
1504
|
+
body: JSON.stringify({
|
|
1505
|
+
licenseKey,
|
|
1506
|
+
pluginName: "magic-sessionmanager",
|
|
1507
|
+
productName: "Magic Session Manager - Premium Session Tracking"
|
|
1508
|
+
}),
|
|
1509
|
+
signal: controller.signal
|
|
1510
|
+
});
|
|
1511
|
+
clearTimeout(timeoutId);
|
|
1512
|
+
const data = await response.json();
|
|
1513
|
+
if (data.success && data.data) {
|
|
1514
|
+
return { valid: true, data: data.data, gracePeriod: false };
|
|
1515
|
+
} else {
|
|
1516
|
+
return { valid: false, data: null };
|
|
1517
|
+
}
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
if (allowGracePeriod) {
|
|
1520
|
+
return { valid: true, data: null, gracePeriod: true };
|
|
1521
|
+
}
|
|
1522
|
+
return { valid: false, data: null };
|
|
1523
|
+
}
|
|
1524
|
+
},
|
|
1525
|
+
async getLicenseByKey(licenseKey) {
|
|
1526
|
+
try {
|
|
1527
|
+
const licenseServerUrl = this.getLicenseServerUrl();
|
|
1528
|
+
const url = `${licenseServerUrl}/api/licenses/key/${licenseKey}`;
|
|
1529
|
+
strapi2.log.debug(`[magic-sessionmanager/license-guard] Fetching license from: ${url}`);
|
|
1530
|
+
const response = await fetch(url, {
|
|
1531
|
+
method: "GET",
|
|
1532
|
+
headers: { "Content-Type": "application/json" }
|
|
1533
|
+
});
|
|
1534
|
+
const data = await response.json();
|
|
1535
|
+
if (data.success && data.data) {
|
|
1536
|
+
strapi2.log.debug(`[magic-sessionmanager/license-guard] License fetched: ${data.data.email}, featurePremium: ${data.data.featurePremium}`);
|
|
1537
|
+
return data.data;
|
|
1538
|
+
}
|
|
1539
|
+
strapi2.log.warn(`[magic-sessionmanager/license-guard] License API returned no data`);
|
|
1540
|
+
return null;
|
|
1541
|
+
} catch (error) {
|
|
1542
|
+
strapi2.log.error("[magic-sessionmanager/license-guard] Error fetching license by key:", error);
|
|
1543
|
+
return null;
|
|
1544
|
+
}
|
|
1545
|
+
},
|
|
1546
|
+
async pingLicense(licenseKey) {
|
|
1547
|
+
try {
|
|
1548
|
+
const deviceId = this.generateDeviceId();
|
|
1549
|
+
const deviceName = this.getDeviceName();
|
|
1550
|
+
const ipAddress = this.getIpAddress();
|
|
1551
|
+
const userAgent = this.getUserAgent();
|
|
1552
|
+
const licenseServerUrl = this.getLicenseServerUrl();
|
|
1553
|
+
const response = await fetch(`${licenseServerUrl}/api/licenses/ping`, {
|
|
1554
|
+
method: "POST",
|
|
1555
|
+
headers: { "Content-Type": "application/json" },
|
|
1556
|
+
body: JSON.stringify({
|
|
1557
|
+
licenseKey,
|
|
1558
|
+
deviceId,
|
|
1559
|
+
deviceName,
|
|
1560
|
+
ipAddress,
|
|
1561
|
+
userAgent,
|
|
1562
|
+
pluginName: "magic-sessionmanager"
|
|
1563
|
+
})
|
|
1564
|
+
});
|
|
1565
|
+
const data = await response.json();
|
|
1566
|
+
return data.success ? data.data : null;
|
|
1567
|
+
} catch (error) {
|
|
1568
|
+
return null;
|
|
1569
|
+
}
|
|
1570
|
+
},
|
|
1571
|
+
async storeLicenseKey(licenseKey) {
|
|
1572
|
+
const pluginStore = strapi2.store({
|
|
1573
|
+
type: "plugin",
|
|
1574
|
+
name: "magic-sessionmanager"
|
|
1575
|
+
});
|
|
1576
|
+
await pluginStore.set({ key: "licenseKey", value: licenseKey });
|
|
1577
|
+
strapi2.log.info(`[magic-sessionmanager] ✅ License key stored: ${licenseKey.substring(0, 8)}...`);
|
|
1578
|
+
},
|
|
1579
|
+
startPinging(licenseKey, intervalMinutes = 15) {
|
|
1580
|
+
strapi2.log.info(`[magic-sessionmanager] ⏰ Starting license pings every ${intervalMinutes} minutes`);
|
|
1581
|
+
this.pingLicense(licenseKey);
|
|
1582
|
+
const interval = setInterval(async () => {
|
|
1583
|
+
try {
|
|
1584
|
+
await this.pingLicense(licenseKey);
|
|
1585
|
+
} catch (error) {
|
|
1586
|
+
strapi2.log.error("[magic-sessionmanager] Ping error:", error);
|
|
1587
|
+
}
|
|
1588
|
+
}, intervalMinutes * 60 * 1e3);
|
|
1589
|
+
return interval;
|
|
1590
|
+
},
|
|
1591
|
+
/**
|
|
1592
|
+
* Initialize license guard
|
|
1593
|
+
* Checks for existing license and starts pinging
|
|
1594
|
+
*/
|
|
1595
|
+
async initialize() {
|
|
1596
|
+
try {
|
|
1597
|
+
strapi2.log.info("[magic-sessionmanager] 🔐 Initializing License Guard...");
|
|
1598
|
+
const pluginStore = strapi2.store({
|
|
1599
|
+
type: "plugin",
|
|
1600
|
+
name: "magic-sessionmanager"
|
|
1601
|
+
});
|
|
1602
|
+
const licenseKey = await pluginStore.get({ key: "licenseKey" });
|
|
1603
|
+
if (!licenseKey) {
|
|
1604
|
+
strapi2.log.info("[magic-sessionmanager] ℹ️ No license found - Running in demo mode");
|
|
1605
|
+
return {
|
|
1606
|
+
valid: false,
|
|
1607
|
+
demo: true,
|
|
1608
|
+
data: null
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
const lastValidated = await pluginStore.get({ key: "lastValidated" });
|
|
1612
|
+
const now = /* @__PURE__ */ new Date();
|
|
1613
|
+
const gracePeriodHours = 24;
|
|
1614
|
+
let withinGracePeriod = false;
|
|
1615
|
+
if (lastValidated) {
|
|
1616
|
+
const lastValidatedDate = new Date(lastValidated);
|
|
1617
|
+
const hoursSinceValidation = (now.getTime() - lastValidatedDate.getTime()) / (1e3 * 60 * 60);
|
|
1618
|
+
withinGracePeriod = hoursSinceValidation < gracePeriodHours;
|
|
1619
|
+
}
|
|
1620
|
+
const verification = await this.verifyLicense(licenseKey, withinGracePeriod);
|
|
1621
|
+
if (verification.valid) {
|
|
1622
|
+
await pluginStore.set({
|
|
1623
|
+
key: "lastValidated",
|
|
1624
|
+
value: now.toISOString()
|
|
1625
|
+
});
|
|
1626
|
+
const pingInterval = this.startPinging(licenseKey, 15);
|
|
1627
|
+
strapi2.licenseGuard = {
|
|
1628
|
+
licenseKey,
|
|
1629
|
+
pingInterval,
|
|
1630
|
+
data: verification.data
|
|
1631
|
+
};
|
|
1632
|
+
return {
|
|
1633
|
+
valid: true,
|
|
1634
|
+
demo: false,
|
|
1635
|
+
data: verification.data,
|
|
1636
|
+
gracePeriod: verification.gracePeriod || false
|
|
1637
|
+
};
|
|
1638
|
+
} else {
|
|
1639
|
+
strapi2.log.error("[magic-sessionmanager] ❌ License validation failed");
|
|
1640
|
+
return {
|
|
1641
|
+
valid: false,
|
|
1642
|
+
demo: true,
|
|
1643
|
+
error: "Invalid or expired license",
|
|
1644
|
+
data: null
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
strapi2.log.error("[magic-sessionmanager] ❌ Error initializing License Guard:", error);
|
|
1649
|
+
return {
|
|
1650
|
+
valid: false,
|
|
1651
|
+
demo: true,
|
|
1652
|
+
error: error.message,
|
|
1653
|
+
data: null
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
var geolocation$1 = ({ strapi: strapi2 }) => ({
|
|
1659
|
+
/**
|
|
1660
|
+
* Get IP information from ipapi.co
|
|
1661
|
+
* @param {string} ipAddress - IP to lookup
|
|
1662
|
+
* @returns {Promise<Object>} Geolocation data
|
|
1663
|
+
*/
|
|
1664
|
+
async getIpInfo(ipAddress) {
|
|
1665
|
+
try {
|
|
1666
|
+
if (!ipAddress || ipAddress === "127.0.0.1" || ipAddress === "::1" || ipAddress.startsWith("192.168.") || ipAddress.startsWith("10.")) {
|
|
1667
|
+
return {
|
|
1668
|
+
ip: ipAddress,
|
|
1669
|
+
country: "Local Network",
|
|
1670
|
+
country_code: "XX",
|
|
1671
|
+
country_flag: "🏠",
|
|
1672
|
+
city: "Localhost",
|
|
1673
|
+
region: "Private Network",
|
|
1674
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
1675
|
+
latitude: null,
|
|
1676
|
+
longitude: null,
|
|
1677
|
+
isVpn: false,
|
|
1678
|
+
isProxy: false,
|
|
1679
|
+
isThreat: false,
|
|
1680
|
+
securityScore: 100,
|
|
1681
|
+
riskLevel: "None"
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
const response = await fetch(`https://ipapi.co/${ipAddress}/json/`, {
|
|
1685
|
+
method: "GET",
|
|
1686
|
+
headers: {
|
|
1687
|
+
"User-Agent": "Strapi-Magic-SessionManager/1.0"
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1690
|
+
if (!response.ok) {
|
|
1691
|
+
throw new Error(`API returned ${response.status}`);
|
|
1692
|
+
}
|
|
1693
|
+
const data = await response.json();
|
|
1694
|
+
if (data.error) {
|
|
1695
|
+
strapi2.log.warn(`[magic-sessionmanager/geolocation] API Error: ${data.reason}`);
|
|
1696
|
+
return this.getFallbackData(ipAddress);
|
|
1697
|
+
}
|
|
1698
|
+
const result = {
|
|
1699
|
+
ip: data.ip,
|
|
1700
|
+
country: data.country_name,
|
|
1701
|
+
country_code: data.country_code,
|
|
1702
|
+
country_flag: this.getCountryFlag(data.country_code),
|
|
1703
|
+
city: data.city,
|
|
1704
|
+
region: data.region,
|
|
1705
|
+
timezone: data.timezone,
|
|
1706
|
+
latitude: data.latitude,
|
|
1707
|
+
longitude: data.longitude,
|
|
1708
|
+
postal: data.postal,
|
|
1709
|
+
org: data.org,
|
|
1710
|
+
asn: data.asn,
|
|
1711
|
+
// Security features (available in ipapi.co response)
|
|
1712
|
+
isVpn: data.threat?.is_vpn || false,
|
|
1713
|
+
isProxy: data.threat?.is_proxy || false,
|
|
1714
|
+
isThreat: data.threat?.is_threat || false,
|
|
1715
|
+
isAnonymous: data.threat?.is_anonymous || false,
|
|
1716
|
+
isTor: data.threat?.is_tor || false,
|
|
1717
|
+
// Calculate security score (0-100, higher is safer)
|
|
1718
|
+
securityScore: this.calculateSecurityScore({
|
|
1719
|
+
isVpn: data.threat?.is_vpn,
|
|
1720
|
+
isProxy: data.threat?.is_proxy,
|
|
1721
|
+
isThreat: data.threat?.is_threat,
|
|
1722
|
+
isAnonymous: data.threat?.is_anonymous,
|
|
1723
|
+
isTor: data.threat?.is_tor
|
|
1724
|
+
}),
|
|
1725
|
+
riskLevel: this.getRiskLevel({
|
|
1726
|
+
isVpn: data.threat?.is_vpn,
|
|
1727
|
+
isProxy: data.threat?.is_proxy,
|
|
1728
|
+
isThreat: data.threat?.is_threat
|
|
1729
|
+
})
|
|
1730
|
+
};
|
|
1731
|
+
strapi2.log.debug(`[magic-sessionmanager/geolocation] IP ${ipAddress}: ${result.city}, ${result.country} (Score: ${result.securityScore})`);
|
|
1732
|
+
return result;
|
|
1733
|
+
} catch (error) {
|
|
1734
|
+
strapi2.log.error(`[magic-sessionmanager/geolocation] Error fetching IP info for ${ipAddress}:`, error.message);
|
|
1735
|
+
return this.getFallbackData(ipAddress);
|
|
1736
|
+
}
|
|
1737
|
+
},
|
|
1738
|
+
/**
|
|
1739
|
+
* Calculate security score based on threat indicators
|
|
1740
|
+
*/
|
|
1741
|
+
calculateSecurityScore({ isVpn, isProxy, isThreat, isAnonymous, isTor }) {
|
|
1742
|
+
let score = 100;
|
|
1743
|
+
if (isTor) score -= 50;
|
|
1744
|
+
if (isThreat) score -= 40;
|
|
1745
|
+
if (isVpn) score -= 20;
|
|
1746
|
+
if (isProxy) score -= 15;
|
|
1747
|
+
if (isAnonymous) score -= 10;
|
|
1748
|
+
return Math.max(0, score);
|
|
1749
|
+
},
|
|
1750
|
+
/**
|
|
1751
|
+
* Get risk level based on indicators
|
|
1752
|
+
*/
|
|
1753
|
+
getRiskLevel({ isVpn, isProxy, isThreat }) {
|
|
1754
|
+
if (isThreat) return "High";
|
|
1755
|
+
if (isVpn && isProxy) return "Medium-High";
|
|
1756
|
+
if (isVpn || isProxy) return "Medium";
|
|
1757
|
+
return "Low";
|
|
1758
|
+
},
|
|
1759
|
+
/**
|
|
1760
|
+
* Get country flag emoji
|
|
1761
|
+
*/
|
|
1762
|
+
getCountryFlag(countryCode) {
|
|
1763
|
+
if (!countryCode) return "🏳️";
|
|
1764
|
+
const codePoints = countryCode.toUpperCase().split("").map((char) => 127397 + char.charCodeAt());
|
|
1765
|
+
return String.fromCodePoint(...codePoints);
|
|
1766
|
+
},
|
|
1767
|
+
/**
|
|
1768
|
+
* Fallback data when API fails
|
|
1769
|
+
*/
|
|
1770
|
+
getFallbackData(ipAddress) {
|
|
1771
|
+
return {
|
|
1772
|
+
ip: ipAddress,
|
|
1773
|
+
country: "Unknown",
|
|
1774
|
+
country_code: "XX",
|
|
1775
|
+
country_flag: "🌍",
|
|
1776
|
+
city: "Unknown",
|
|
1777
|
+
region: "Unknown",
|
|
1778
|
+
timezone: "Unknown",
|
|
1779
|
+
latitude: null,
|
|
1780
|
+
longitude: null,
|
|
1781
|
+
isVpn: false,
|
|
1782
|
+
isProxy: false,
|
|
1783
|
+
isThreat: false,
|
|
1784
|
+
securityScore: 50,
|
|
1785
|
+
riskLevel: "Unknown"
|
|
1786
|
+
};
|
|
1787
|
+
},
|
|
1788
|
+
/**
|
|
1789
|
+
* Batch lookup multiple IPs (for efficiency)
|
|
1790
|
+
*/
|
|
1791
|
+
async batchGetIpInfo(ipAddresses) {
|
|
1792
|
+
const results = await Promise.all(
|
|
1793
|
+
ipAddresses.map((ip) => this.getIpInfo(ip))
|
|
1794
|
+
);
|
|
1795
|
+
return results;
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
var notifications$1 = ({ strapi: strapi2 }) => ({
|
|
1799
|
+
/**
|
|
1800
|
+
* Get email templates from database settings
|
|
1801
|
+
* Falls back to default hardcoded templates if not found
|
|
1802
|
+
*/
|
|
1803
|
+
async getEmailTemplates() {
|
|
1804
|
+
try {
|
|
1805
|
+
const pluginStore = strapi2.store({
|
|
1806
|
+
type: "plugin",
|
|
1807
|
+
name: "magic-sessionmanager"
|
|
1808
|
+
});
|
|
1809
|
+
const settings2 = await pluginStore.get({ key: "settings" });
|
|
1810
|
+
if (settings2?.emailTemplates && Object.keys(settings2.emailTemplates).length > 0) {
|
|
1811
|
+
const hasContent = Object.values(settings2.emailTemplates).some(
|
|
1812
|
+
(template) => template.html || template.text
|
|
1813
|
+
);
|
|
1814
|
+
if (hasContent) {
|
|
1815
|
+
strapi2.log.debug("[magic-sessionmanager/notifications] Using templates from database");
|
|
1816
|
+
return settings2.emailTemplates;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
} catch (err) {
|
|
1820
|
+
strapi2.log.warn("[magic-sessionmanager/notifications] Could not load templates from DB, using defaults:", err.message);
|
|
1821
|
+
}
|
|
1822
|
+
strapi2.log.debug("[magic-sessionmanager/notifications] Using default fallback templates");
|
|
1823
|
+
return {
|
|
1824
|
+
suspiciousLogin: {
|
|
1825
|
+
subject: "🚨 Suspicious Login Alert - Session Manager",
|
|
1826
|
+
html: `
|
|
1827
|
+
<html>
|
|
1828
|
+
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
|
1829
|
+
<div style="max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f9fafb; border-radius: 10px;">
|
|
1830
|
+
<h2 style="color: #dc2626;">🚨 Suspicious Login Detected</h2>
|
|
1831
|
+
<p>A potentially suspicious login was detected for your account.</p>
|
|
1832
|
+
|
|
1833
|
+
<div style="background: white; padding: 15px; border-radius: 8px; margin: 20px 0;">
|
|
1834
|
+
<h3 style="margin-top: 0;">Account Information:</h3>
|
|
1835
|
+
<ul>
|
|
1836
|
+
<li><strong>Email:</strong> {{user.email}}</li>
|
|
1837
|
+
<li><strong>Username:</strong> {{user.username}}</li>
|
|
1838
|
+
</ul>
|
|
1839
|
+
|
|
1840
|
+
<h3>Login Details:</h3>
|
|
1841
|
+
<ul>
|
|
1842
|
+
<li><strong>Time:</strong> {{session.loginTime}}</li>
|
|
1843
|
+
<li><strong>IP Address:</strong> {{session.ipAddress}}</li>
|
|
1844
|
+
<li><strong>Location:</strong> {{geo.city}}, {{geo.country}}</li>
|
|
1845
|
+
<li><strong>Timezone:</strong> {{geo.timezone}}</li>
|
|
1846
|
+
<li><strong>Device:</strong> {{session.userAgent}}</li>
|
|
1847
|
+
</ul>
|
|
1848
|
+
|
|
1849
|
+
<h3 style="color: #dc2626;">Security Alert:</h3>
|
|
1850
|
+
<ul>
|
|
1851
|
+
<li>VPN Detected: {{reason.isVpn}}</li>
|
|
1852
|
+
<li>Proxy Detected: {{reason.isProxy}}</li>
|
|
1853
|
+
<li>Threat Detected: {{reason.isThreat}}</li>
|
|
1854
|
+
<li>Security Score: {{reason.securityScore}}/100</li>
|
|
1855
|
+
</ul>
|
|
1856
|
+
</div>
|
|
1857
|
+
|
|
1858
|
+
<p>If this was you, you can safely ignore this email. If you don't recognize this activity, please secure your account immediately.</p>
|
|
1859
|
+
|
|
1860
|
+
<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 20px 0;"/>
|
|
1861
|
+
<p style="color: #666; font-size: 12px;">This is an automated security notification from Magic Session Manager.</p>
|
|
1862
|
+
</div>
|
|
1863
|
+
</body>
|
|
1864
|
+
</html>`,
|
|
1865
|
+
text: `🚨 Suspicious Login Detected
|
|
1866
|
+
|
|
1867
|
+
A potentially suspicious login was detected for your account.
|
|
1868
|
+
|
|
1869
|
+
Account: {{user.email}}
|
|
1870
|
+
Username: {{user.username}}
|
|
1871
|
+
|
|
1872
|
+
Login Details:
|
|
1873
|
+
- Time: {{session.loginTime}}
|
|
1874
|
+
- IP: {{session.ipAddress}}
|
|
1875
|
+
- Location: {{geo.city}}, {{geo.country}}
|
|
1876
|
+
|
|
1877
|
+
Security: VPN={{reason.isVpn}}, Proxy={{reason.isProxy}}, Threat={{reason.isThreat}}, Score={{reason.securityScore}}/100`
|
|
1878
|
+
},
|
|
1879
|
+
newLocation: {
|
|
1880
|
+
subject: "📍 New Location Login Detected",
|
|
1881
|
+
html: `<h2>📍 New Location Login</h2><p>Account: {{user.email}}</p><p>Time: {{session.loginTime}}</p><p>Location: {{geo.city}}, {{geo.country}}</p><p>IP: {{session.ipAddress}}</p>`,
|
|
1882
|
+
text: `📍 New Location Login
|
|
1883
|
+
|
|
1884
|
+
Account: {{user.email}}
|
|
1885
|
+
Time: {{session.loginTime}}
|
|
1886
|
+
Location: {{geo.city}}, {{geo.country}}
|
|
1887
|
+
IP: {{session.ipAddress}}`
|
|
1888
|
+
},
|
|
1889
|
+
vpnProxy: {
|
|
1890
|
+
subject: "⚠️ VPN/Proxy Login Detected",
|
|
1891
|
+
html: `<h2>⚠️ VPN/Proxy Detected</h2><p>Account: {{user.email}}</p><p>Time: {{session.loginTime}}</p><p>IP: {{session.ipAddress}}</p><p>VPN: {{reason.isVpn}}, Proxy: {{reason.isProxy}}</p>`,
|
|
1892
|
+
text: `⚠️ VPN/Proxy Detected
|
|
1893
|
+
|
|
1894
|
+
Account: {{user.email}}
|
|
1895
|
+
Time: {{session.loginTime}}
|
|
1896
|
+
IP: {{session.ipAddress}}
|
|
1897
|
+
VPN: {{reason.isVpn}}, Proxy: {{reason.isProxy}}`
|
|
1898
|
+
}
|
|
1899
|
+
};
|
|
1900
|
+
},
|
|
1901
|
+
/**
|
|
1902
|
+
* Replace template variables with actual values
|
|
1903
|
+
*/
|
|
1904
|
+
replaceVariables(template, data) {
|
|
1905
|
+
let result = template;
|
|
1906
|
+
result = result.replace(/\{\{user\.email\}\}/g, data.user?.email || "N/A");
|
|
1907
|
+
result = result.replace(/\{\{user\.username\}\}/g, data.user?.username || "N/A");
|
|
1908
|
+
result = result.replace(
|
|
1909
|
+
/\{\{session\.loginTime\}\}/g,
|
|
1910
|
+
data.session?.loginTime ? new Date(data.session.loginTime).toLocaleString() : "N/A"
|
|
1911
|
+
);
|
|
1912
|
+
result = result.replace(/\{\{session\.ipAddress\}\}/g, data.session?.ipAddress || "N/A");
|
|
1913
|
+
result = result.replace(/\{\{session\.userAgent\}\}/g, data.session?.userAgent || "N/A");
|
|
1914
|
+
result = result.replace(/\{\{geo\.city\}\}/g, data.geoData?.city || "Unknown");
|
|
1915
|
+
result = result.replace(/\{\{geo\.country\}\}/g, data.geoData?.country || "Unknown");
|
|
1916
|
+
result = result.replace(/\{\{geo\.timezone\}\}/g, data.geoData?.timezone || "Unknown");
|
|
1917
|
+
result = result.replace(/\{\{reason\.isVpn\}\}/g, data.reason?.isVpn ? "Yes" : "No");
|
|
1918
|
+
result = result.replace(/\{\{reason\.isProxy\}\}/g, data.reason?.isProxy ? "Yes" : "No");
|
|
1919
|
+
result = result.replace(/\{\{reason\.isThreat\}\}/g, data.reason?.isThreat ? "Yes" : "No");
|
|
1920
|
+
result = result.replace(/\{\{reason\.securityScore\}\}/g, data.reason?.securityScore || "0");
|
|
1921
|
+
return result;
|
|
1922
|
+
},
|
|
1923
|
+
/**
|
|
1924
|
+
* Send suspicious login alert
|
|
1925
|
+
* @param {Object} params - { user, session, reason, geoData }
|
|
1926
|
+
*/
|
|
1927
|
+
async sendSuspiciousLoginAlert({ user, session: session2, reason, geoData }) {
|
|
1928
|
+
try {
|
|
1929
|
+
const templates = await this.getEmailTemplates();
|
|
1930
|
+
const template = templates.suspiciousLogin;
|
|
1931
|
+
const data = { user, session: session2, reason, geoData };
|
|
1932
|
+
const htmlContent = this.replaceVariables(template.html, data);
|
|
1933
|
+
const textContent = this.replaceVariables(template.text, data);
|
|
1934
|
+
await strapi2.plugins["email"].services.email.send({
|
|
1935
|
+
to: user.email,
|
|
1936
|
+
subject: template.subject,
|
|
1937
|
+
html: htmlContent,
|
|
1938
|
+
text: textContent
|
|
1939
|
+
});
|
|
1940
|
+
strapi2.log.info(`[magic-sessionmanager/notifications] Suspicious login alert sent to ${user.email}`);
|
|
1941
|
+
return true;
|
|
1942
|
+
} catch (err) {
|
|
1943
|
+
strapi2.log.error("[magic-sessionmanager/notifications] Error sending email:", err);
|
|
1944
|
+
return false;
|
|
1945
|
+
}
|
|
1946
|
+
},
|
|
1947
|
+
/**
|
|
1948
|
+
* Send new location login alert
|
|
1949
|
+
* @param {Object} params - { user, session, geoData }
|
|
1950
|
+
*/
|
|
1951
|
+
async sendNewLocationAlert({ user, session: session2, geoData }) {
|
|
1952
|
+
try {
|
|
1953
|
+
const templates = await this.getEmailTemplates();
|
|
1954
|
+
const template = templates.newLocation;
|
|
1955
|
+
const data = { user, session: session2, geoData, reason: {} };
|
|
1956
|
+
const htmlContent = this.replaceVariables(template.html, data);
|
|
1957
|
+
const textContent = this.replaceVariables(template.text, data);
|
|
1958
|
+
await strapi2.plugins["email"].services.email.send({
|
|
1959
|
+
to: user.email,
|
|
1960
|
+
subject: template.subject,
|
|
1961
|
+
html: htmlContent,
|
|
1962
|
+
text: textContent
|
|
1963
|
+
});
|
|
1964
|
+
strapi2.log.info(`[magic-sessionmanager/notifications] New location alert sent to ${user.email}`);
|
|
1965
|
+
return true;
|
|
1966
|
+
} catch (err) {
|
|
1967
|
+
strapi2.log.error("[magic-sessionmanager/notifications] Error sending new location email:", err);
|
|
1968
|
+
return false;
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
/**
|
|
1972
|
+
* Send VPN/Proxy login alert
|
|
1973
|
+
* @param {Object} params - { user, session, reason, geoData }
|
|
1974
|
+
*/
|
|
1975
|
+
async sendVpnProxyAlert({ user, session: session2, reason, geoData }) {
|
|
1976
|
+
try {
|
|
1977
|
+
const templates = await this.getEmailTemplates();
|
|
1978
|
+
const template = templates.vpnProxy;
|
|
1979
|
+
const data = { user, session: session2, reason, geoData };
|
|
1980
|
+
const htmlContent = this.replaceVariables(template.html, data);
|
|
1981
|
+
const textContent = this.replaceVariables(template.text, data);
|
|
1982
|
+
await strapi2.plugins["email"].services.email.send({
|
|
1983
|
+
to: user.email,
|
|
1984
|
+
subject: template.subject,
|
|
1985
|
+
html: htmlContent,
|
|
1986
|
+
text: textContent
|
|
1987
|
+
});
|
|
1988
|
+
strapi2.log.info(`[magic-sessionmanager/notifications] VPN/Proxy alert sent to ${user.email}`);
|
|
1989
|
+
return true;
|
|
1990
|
+
} catch (err) {
|
|
1991
|
+
strapi2.log.error("[magic-sessionmanager/notifications] Error sending VPN/Proxy email:", err);
|
|
1992
|
+
return false;
|
|
1993
|
+
}
|
|
1994
|
+
},
|
|
1995
|
+
/**
|
|
1996
|
+
* Send webhook notification
|
|
1997
|
+
* @param {Object} params - { event, data, webhookUrl }
|
|
1998
|
+
*/
|
|
1999
|
+
async sendWebhook({ event, data, webhookUrl }) {
|
|
2000
|
+
try {
|
|
2001
|
+
const payload = {
|
|
2002
|
+
event,
|
|
2003
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2004
|
+
data,
|
|
2005
|
+
source: "magic-sessionmanager"
|
|
2006
|
+
};
|
|
2007
|
+
const response = await fetch(webhookUrl, {
|
|
2008
|
+
method: "POST",
|
|
2009
|
+
headers: {
|
|
2010
|
+
"Content-Type": "application/json",
|
|
2011
|
+
"User-Agent": "Strapi-Magic-SessionManager-Webhook/1.0"
|
|
2012
|
+
},
|
|
2013
|
+
body: JSON.stringify(payload)
|
|
2014
|
+
});
|
|
2015
|
+
if (response.ok) {
|
|
2016
|
+
strapi2.log.info(`[magic-sessionmanager/notifications] Webhook sent: ${event}`);
|
|
2017
|
+
return true;
|
|
2018
|
+
} else {
|
|
2019
|
+
strapi2.log.warn(`[magic-sessionmanager/notifications] Webhook failed: ${response.status}`);
|
|
2020
|
+
return false;
|
|
2021
|
+
}
|
|
2022
|
+
} catch (err) {
|
|
2023
|
+
strapi2.log.error("[magic-sessionmanager/notifications] Webhook error:", err);
|
|
2024
|
+
return false;
|
|
2025
|
+
}
|
|
2026
|
+
},
|
|
2027
|
+
/**
|
|
2028
|
+
* Format webhook for Discord
|
|
2029
|
+
* @param {Object} params - { event, session, user, geoData }
|
|
2030
|
+
*/
|
|
2031
|
+
formatDiscordWebhook({ event, session: session2, user, geoData }) {
|
|
2032
|
+
const embed = {
|
|
2033
|
+
title: this.getEventTitle(event),
|
|
2034
|
+
color: this.getEventColor(event),
|
|
2035
|
+
fields: [
|
|
2036
|
+
{ name: "👤 User", value: `${user.email}
|
|
2037
|
+
${user.username || "N/A"}`, inline: true },
|
|
2038
|
+
{ name: "🌐 IP", value: session2.ipAddress, inline: true },
|
|
2039
|
+
{ name: "📅 Time", value: new Date(session2.loginTime).toLocaleString(), inline: false }
|
|
2040
|
+
],
|
|
2041
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2042
|
+
footer: { text: "Magic Session Manager" }
|
|
2043
|
+
};
|
|
2044
|
+
if (geoData) {
|
|
2045
|
+
embed.fields.push({
|
|
2046
|
+
name: "📍 Location",
|
|
2047
|
+
value: `${geoData.country_flag} ${geoData.city}, ${geoData.country}`,
|
|
2048
|
+
inline: true
|
|
2049
|
+
});
|
|
2050
|
+
if (geoData.isVpn || geoData.isProxy || geoData.isThreat) {
|
|
2051
|
+
const warnings = [];
|
|
2052
|
+
if (geoData.isVpn) warnings.push("VPN");
|
|
2053
|
+
if (geoData.isProxy) warnings.push("Proxy");
|
|
2054
|
+
if (geoData.isThreat) warnings.push("Threat");
|
|
2055
|
+
embed.fields.push({
|
|
2056
|
+
name: "⚠️ Security",
|
|
2057
|
+
value: `${warnings.join(", ")} detected
|
|
2058
|
+
Score: ${geoData.securityScore}/100`,
|
|
2059
|
+
inline: true
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
return { embeds: [embed] };
|
|
2064
|
+
},
|
|
2065
|
+
getEventTitle(event) {
|
|
2066
|
+
const titles = {
|
|
2067
|
+
"login.suspicious": "🚨 Suspicious Login",
|
|
2068
|
+
"login.new_location": "📍 New Location Login",
|
|
2069
|
+
"login.vpn": "🔴 VPN Login Detected",
|
|
2070
|
+
"login.threat": "⛔ Threat IP Login",
|
|
2071
|
+
"session.terminated": "🔴 Session Terminated"
|
|
2072
|
+
};
|
|
2073
|
+
return titles[event] || "📊 Session Event";
|
|
2074
|
+
},
|
|
2075
|
+
getEventColor(event) {
|
|
2076
|
+
const colors = {
|
|
2077
|
+
"login.suspicious": 16711680,
|
|
2078
|
+
// Red
|
|
2079
|
+
"login.new_location": 16753920,
|
|
2080
|
+
// Orange
|
|
2081
|
+
"login.vpn": 16739179,
|
|
2082
|
+
// Light Red
|
|
2083
|
+
"login.threat": 9109504,
|
|
2084
|
+
// Dark Red
|
|
2085
|
+
"session.terminated": 8421504
|
|
2086
|
+
// Gray
|
|
2087
|
+
};
|
|
2088
|
+
return colors[event] || 5793266;
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
const session = session$1;
|
|
2092
|
+
const licenseGuard = licenseGuard$1;
|
|
2093
|
+
const geolocation = geolocation$1;
|
|
2094
|
+
const notifications = notifications$1;
|
|
2095
|
+
var services$1 = {
|
|
2096
|
+
session,
|
|
2097
|
+
"license-guard": licenseGuard,
|
|
2098
|
+
geolocation,
|
|
2099
|
+
notifications
|
|
2100
|
+
};
|
|
2101
|
+
var middlewares$1 = {
|
|
2102
|
+
"last-seen": lastSeen
|
|
2103
|
+
};
|
|
2104
|
+
const register = register$1;
|
|
2105
|
+
const bootstrap = bootstrap$1;
|
|
2106
|
+
const destroy = destroy$1;
|
|
2107
|
+
const config = config$1;
|
|
2108
|
+
const routes = routes$1;
|
|
2109
|
+
const controllers = controllers$1;
|
|
2110
|
+
const services = services$1;
|
|
2111
|
+
const middlewares = middlewares$1;
|
|
2112
|
+
var src = {
|
|
2113
|
+
register,
|
|
2114
|
+
bootstrap,
|
|
2115
|
+
destroy,
|
|
2116
|
+
config,
|
|
2117
|
+
routes,
|
|
2118
|
+
controllers,
|
|
2119
|
+
services,
|
|
2120
|
+
middlewares
|
|
2121
|
+
};
|
|
2122
|
+
const index = /* @__PURE__ */ getDefaultExportFromCjs(src);
|
|
2123
|
+
export {
|
|
2124
|
+
index as default
|
|
2125
|
+
};
|