strapi-plugin-magic-sessionmanager 2.0.0 → 2.0.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/admin/jsconfig.json +10 -0
- package/admin/src/components/Initializer.jsx +11 -0
- package/admin/src/components/LicenseGuard.jsx +591 -0
- package/admin/src/components/OnlineUsersWidget.jsx +208 -0
- package/admin/src/components/PluginIcon.jsx +8 -0
- package/admin/src/components/SessionDetailModal.jsx +445 -0
- package/admin/src/components/SessionInfoCard.jsx +151 -0
- package/admin/src/components/SessionInfoPanel.jsx +375 -0
- package/admin/src/components/index.jsx +5 -0
- package/admin/src/hooks/useLicense.js +103 -0
- package/admin/src/index.js +137 -0
- package/admin/src/pages/ActiveSessions.jsx +12 -0
- package/admin/src/pages/Analytics.jsx +735 -0
- package/admin/src/pages/App.jsx +12 -0
- package/admin/src/pages/HomePage.jsx +1248 -0
- package/admin/src/pages/License.jsx +603 -0
- package/admin/src/pages/Settings.jsx +1497 -0
- package/admin/src/pages/SettingsNew.jsx +1204 -0
- package/admin/src/pages/index.jsx +3 -0
- package/admin/src/pluginId.js +3 -0
- package/admin/src/translations/de.json +20 -0
- package/admin/src/translations/en.json +20 -0
- package/admin/src/utils/getTranslation.js +5 -0
- package/admin/src/utils/index.js +2 -0
- package/admin/src/utils/parseUserAgent.js +79 -0
- package/package.json +3 -1
- package/server/jsconfig.json +10 -0
- package/server/src/bootstrap.js +297 -0
- package/server/src/config/index.js +20 -0
- package/server/src/content-types/index.js +9 -0
- package/server/src/content-types/session/schema.json +76 -0
- package/server/src/controllers/controller.js +11 -0
- package/server/src/controllers/index.js +11 -0
- package/server/src/controllers/license.js +266 -0
- package/server/src/controllers/session.js +362 -0
- package/server/src/controllers/settings.js +122 -0
- package/server/src/destroy.js +18 -0
- package/server/src/index.js +21 -0
- package/server/src/middlewares/index.js +5 -0
- package/server/src/middlewares/last-seen.js +56 -0
- package/server/src/policies/index.js +3 -0
- package/server/src/register.js +32 -0
- package/server/src/routes/admin.js +149 -0
- package/server/src/routes/content-api.js +51 -0
- package/server/src/routes/index.js +9 -0
- package/server/src/services/geolocation.js +180 -0
- package/server/src/services/index.js +13 -0
- package/server/src/services/license-guard.js +308 -0
- package/server/src/services/notifications.js +319 -0
- package/server/src/services/service.js +7 -0
- package/server/src/services/session.js +345 -0
- package/server/src/utils/getClientIp.js +118 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License Controller for Magic Session Manager Plugin
|
|
3
|
+
* Manages licenses directly from the Admin Panel
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = ({ strapi }) => ({
|
|
7
|
+
/**
|
|
8
|
+
* Auto-create license with logged-in admin user data
|
|
9
|
+
*/
|
|
10
|
+
async autoCreate(ctx) {
|
|
11
|
+
try {
|
|
12
|
+
// Get the logged-in admin user
|
|
13
|
+
const adminUser = ctx.state.user;
|
|
14
|
+
|
|
15
|
+
if (!adminUser) {
|
|
16
|
+
return ctx.unauthorized('No admin user logged in');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const licenseGuard = strapi.plugin('magic-sessionmanager').service('license-guard');
|
|
20
|
+
|
|
21
|
+
// Use admin user data for license creation
|
|
22
|
+
const license = await licenseGuard.createLicense({
|
|
23
|
+
email: adminUser.email,
|
|
24
|
+
firstName: adminUser.firstname || 'Admin',
|
|
25
|
+
lastName: adminUser.lastname || 'User',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (!license) {
|
|
29
|
+
return ctx.badRequest('Failed to create license');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Store the license key
|
|
33
|
+
await licenseGuard.storeLicenseKey(license.licenseKey);
|
|
34
|
+
|
|
35
|
+
// Start pinging
|
|
36
|
+
const pingInterval = licenseGuard.startPinging(license.licenseKey, 15);
|
|
37
|
+
|
|
38
|
+
// Update global license guard
|
|
39
|
+
strapi.licenseGuard = {
|
|
40
|
+
licenseKey: license.licenseKey,
|
|
41
|
+
pingInterval,
|
|
42
|
+
data: license,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return ctx.send({
|
|
46
|
+
success: true,
|
|
47
|
+
message: 'License automatically created and activated',
|
|
48
|
+
data: license,
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
strapi.log.error('[magic-sessionmanager] Error auto-creating license:', error);
|
|
52
|
+
return ctx.badRequest('Error creating license');
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get current license status
|
|
58
|
+
*/
|
|
59
|
+
async getStatus(ctx) {
|
|
60
|
+
try {
|
|
61
|
+
const licenseGuard = strapi.plugin('magic-sessionmanager').service('license-guard');
|
|
62
|
+
const pluginStore = strapi.store({
|
|
63
|
+
type: 'plugin',
|
|
64
|
+
name: 'magic-sessionmanager'
|
|
65
|
+
});
|
|
66
|
+
const licenseKey = await pluginStore.get({ key: 'licenseKey' });
|
|
67
|
+
|
|
68
|
+
if (!licenseKey) {
|
|
69
|
+
strapi.log.debug('[magic-sessionmanager] No license key in store - demo mode');
|
|
70
|
+
return ctx.send({
|
|
71
|
+
success: false,
|
|
72
|
+
demo: true,
|
|
73
|
+
valid: false,
|
|
74
|
+
message: 'No license found. Running in demo mode.',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
strapi.log.info(`[magic-sessionmanager/license-controller] Checking stored license: ${licenseKey}`);
|
|
79
|
+
|
|
80
|
+
const verification = await licenseGuard.verifyLicense(licenseKey);
|
|
81
|
+
const license = await licenseGuard.getLicenseByKey(licenseKey);
|
|
82
|
+
|
|
83
|
+
strapi.log.info('[magic-sessionmanager/license-controller] License data from MagicAPI:', {
|
|
84
|
+
licenseKey: license?.licenseKey,
|
|
85
|
+
email: license?.email,
|
|
86
|
+
featurePremium: license?.featurePremium,
|
|
87
|
+
isActive: license?.isActive,
|
|
88
|
+
pluginName: license?.pluginName,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return ctx.send({
|
|
92
|
+
success: true,
|
|
93
|
+
valid: verification.valid,
|
|
94
|
+
demo: false,
|
|
95
|
+
data: {
|
|
96
|
+
licenseKey,
|
|
97
|
+
email: license?.email || null,
|
|
98
|
+
firstName: license?.firstName || null,
|
|
99
|
+
lastName: license?.lastName || null,
|
|
100
|
+
isActive: license?.isActive || false,
|
|
101
|
+
isExpired: license?.isExpired || false,
|
|
102
|
+
isOnline: license?.isOnline || false,
|
|
103
|
+
expiresAt: license?.expiresAt,
|
|
104
|
+
lastPingAt: license?.lastPingAt,
|
|
105
|
+
deviceName: license?.deviceName,
|
|
106
|
+
deviceId: license?.deviceId,
|
|
107
|
+
ipAddress: license?.ipAddress,
|
|
108
|
+
features: {
|
|
109
|
+
premium: license?.featurePremium || false,
|
|
110
|
+
advanced: license?.featureAdvanced || false,
|
|
111
|
+
enterprise: license?.featureEnterprise || false,
|
|
112
|
+
custom: license?.featureCustom || false,
|
|
113
|
+
},
|
|
114
|
+
maxDevices: license?.maxDevices || 1,
|
|
115
|
+
currentDevices: license?.currentDevices || 0,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
} catch (error) {
|
|
119
|
+
strapi.log.error('[magic-sessionmanager] Error getting license status:', error);
|
|
120
|
+
return ctx.badRequest('Error getting license status');
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create and activate a new license
|
|
126
|
+
*/
|
|
127
|
+
async createAndActivate(ctx) {
|
|
128
|
+
try {
|
|
129
|
+
const { email, firstName, lastName } = ctx.request.body;
|
|
130
|
+
|
|
131
|
+
if (!email || !firstName || !lastName) {
|
|
132
|
+
return ctx.badRequest('Email, firstName, and lastName are required');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const licenseGuard = strapi.plugin('magic-sessionmanager').service('license-guard');
|
|
136
|
+
const license = await licenseGuard.createLicense({ email, firstName, lastName });
|
|
137
|
+
|
|
138
|
+
if (!license) {
|
|
139
|
+
return ctx.badRequest('Failed to create license');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Store the license key
|
|
143
|
+
await licenseGuard.storeLicenseKey(license.licenseKey);
|
|
144
|
+
|
|
145
|
+
// Start pinging
|
|
146
|
+
const pingInterval = licenseGuard.startPinging(license.licenseKey, 15);
|
|
147
|
+
|
|
148
|
+
// Update global license guard
|
|
149
|
+
strapi.licenseGuard = {
|
|
150
|
+
licenseKey: license.licenseKey,
|
|
151
|
+
pingInterval,
|
|
152
|
+
data: license,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return ctx.send({
|
|
156
|
+
success: true,
|
|
157
|
+
message: 'License created and activated successfully',
|
|
158
|
+
data: license,
|
|
159
|
+
});
|
|
160
|
+
} catch (error) {
|
|
161
|
+
strapi.log.error('[magic-sessionmanager] Error creating license:', error);
|
|
162
|
+
return ctx.badRequest('Error creating license');
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Manually ping the current license
|
|
168
|
+
*/
|
|
169
|
+
async ping(ctx) {
|
|
170
|
+
try {
|
|
171
|
+
const pluginStore = strapi.store({
|
|
172
|
+
type: 'plugin',
|
|
173
|
+
name: 'magic-sessionmanager'
|
|
174
|
+
});
|
|
175
|
+
const licenseKey = await pluginStore.get({ key: 'licenseKey' });
|
|
176
|
+
|
|
177
|
+
if (!licenseKey) {
|
|
178
|
+
return ctx.badRequest('No license key found');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const licenseGuard = strapi.plugin('magic-sessionmanager').service('license-guard');
|
|
182
|
+
const pingResult = await licenseGuard.pingLicense(licenseKey);
|
|
183
|
+
|
|
184
|
+
if (!pingResult) {
|
|
185
|
+
return ctx.badRequest('Ping failed');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return ctx.send({
|
|
189
|
+
success: true,
|
|
190
|
+
message: 'License pinged successfully',
|
|
191
|
+
data: pingResult,
|
|
192
|
+
});
|
|
193
|
+
} catch (error) {
|
|
194
|
+
strapi.log.error('[magic-sessionmanager] Error pinging license:', error);
|
|
195
|
+
return ctx.badRequest('Error pinging license');
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Store and validate an existing license key
|
|
201
|
+
*/
|
|
202
|
+
async storeKey(ctx) {
|
|
203
|
+
try {
|
|
204
|
+
const { licenseKey, email } = ctx.request.body;
|
|
205
|
+
|
|
206
|
+
if (!licenseKey || !licenseKey.trim()) {
|
|
207
|
+
return ctx.badRequest('License key is required');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!email || !email.trim()) {
|
|
211
|
+
return ctx.badRequest('Email address is required');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const trimmedKey = licenseKey.trim();
|
|
215
|
+
const trimmedEmail = email.trim().toLowerCase();
|
|
216
|
+
const licenseGuard = strapi.plugin('magic-sessionmanager').service('license-guard');
|
|
217
|
+
|
|
218
|
+
// Verify the license key first
|
|
219
|
+
const verification = await licenseGuard.verifyLicense(trimmedKey);
|
|
220
|
+
|
|
221
|
+
if (!verification.valid) {
|
|
222
|
+
strapi.log.warn(`[magic-sessionmanager] ⚠️ Invalid license key attempted: ${trimmedKey.substring(0, 8)}...`);
|
|
223
|
+
return ctx.badRequest('Invalid or expired license key');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Get license details to verify email
|
|
227
|
+
const license = await licenseGuard.getLicenseByKey(trimmedKey);
|
|
228
|
+
|
|
229
|
+
if (!license) {
|
|
230
|
+
strapi.log.warn(`[magic-sessionmanager] ⚠️ License not found in database: ${trimmedKey.substring(0, 8)}...`);
|
|
231
|
+
return ctx.badRequest('License not found');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Verify email matches
|
|
235
|
+
if (license.email.toLowerCase() !== trimmedEmail) {
|
|
236
|
+
strapi.log.warn(`[magic-sessionmanager] ⚠️ Email mismatch for license key: ${trimmedKey.substring(0, 8)}... (Attempted: ${trimmedEmail})`);
|
|
237
|
+
return ctx.badRequest('Email address does not match this license key');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Store the license key
|
|
241
|
+
await licenseGuard.storeLicenseKey(trimmedKey);
|
|
242
|
+
|
|
243
|
+
// Start pinging
|
|
244
|
+
const pingInterval = licenseGuard.startPinging(trimmedKey, 15);
|
|
245
|
+
|
|
246
|
+
// Update global license guard
|
|
247
|
+
strapi.licenseGuard = {
|
|
248
|
+
licenseKey: trimmedKey,
|
|
249
|
+
pingInterval,
|
|
250
|
+
data: verification.data,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
strapi.log.info(`[magic-sessionmanager] ✅ Existing license key validated and stored: ${trimmedKey.substring(0, 8)}... (Email: ${trimmedEmail})`);
|
|
254
|
+
|
|
255
|
+
return ctx.send({
|
|
256
|
+
success: true,
|
|
257
|
+
message: 'License key validated and activated successfully',
|
|
258
|
+
data: verification.data,
|
|
259
|
+
});
|
|
260
|
+
} catch (error) {
|
|
261
|
+
strapi.log.error('[magic-sessionmanager] Error storing license key:', error);
|
|
262
|
+
return ctx.badRequest('Error validating license key');
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Session Controller
|
|
5
|
+
* Handles HTTP requests for session management
|
|
6
|
+
*/
|
|
7
|
+
module.exports = {
|
|
8
|
+
/**
|
|
9
|
+
* Get ALL sessions (active + inactive) - Admin only
|
|
10
|
+
* GET /magic-sessionmanager/sessions
|
|
11
|
+
*/
|
|
12
|
+
async getAllSessionsAdmin(ctx) {
|
|
13
|
+
try {
|
|
14
|
+
const sessionService = strapi
|
|
15
|
+
.plugin('magic-sessionmanager')
|
|
16
|
+
.service('session');
|
|
17
|
+
|
|
18
|
+
const sessions = await sessionService.getAllSessions();
|
|
19
|
+
|
|
20
|
+
ctx.body = {
|
|
21
|
+
data: sessions,
|
|
22
|
+
meta: {
|
|
23
|
+
count: sessions.length,
|
|
24
|
+
active: sessions.filter(s => s.isTrulyActive).length,
|
|
25
|
+
inactive: sessions.filter(s => !s.isTrulyActive).length,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
} catch (err) {
|
|
29
|
+
ctx.throw(500, 'Error fetching sessions');
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get active sessions only
|
|
35
|
+
* GET /magic-sessionmanager/sessions/active
|
|
36
|
+
*/
|
|
37
|
+
async getActiveSessions(ctx) {
|
|
38
|
+
try {
|
|
39
|
+
const sessionService = strapi
|
|
40
|
+
.plugin('magic-sessionmanager')
|
|
41
|
+
.service('session');
|
|
42
|
+
|
|
43
|
+
const sessions = await sessionService.getActiveSessions();
|
|
44
|
+
|
|
45
|
+
ctx.body = {
|
|
46
|
+
data: sessions,
|
|
47
|
+
meta: {
|
|
48
|
+
count: sessions.length,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
} catch (err) {
|
|
52
|
+
ctx.throw(500, 'Error fetching active sessions');
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get user's sessions
|
|
58
|
+
* GET /magic-sessionmanager/user/:userId/sessions
|
|
59
|
+
* SECURITY: User can only access their own sessions
|
|
60
|
+
*/
|
|
61
|
+
async getUserSessions(ctx) {
|
|
62
|
+
try {
|
|
63
|
+
const { userId } = ctx.params;
|
|
64
|
+
const requestingUserId = ctx.state.user?.id;
|
|
65
|
+
|
|
66
|
+
// SECURITY CHECK: User can only see their own sessions
|
|
67
|
+
if (requestingUserId && String(requestingUserId) !== String(userId)) {
|
|
68
|
+
strapi.log.warn(`[magic-sessionmanager] Security: User ${requestingUserId} tried to access sessions of user ${userId}`);
|
|
69
|
+
return ctx.forbidden('You can only access your own sessions');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const sessionService = strapi
|
|
73
|
+
.plugin('magic-sessionmanager')
|
|
74
|
+
.service('session');
|
|
75
|
+
|
|
76
|
+
const sessions = await sessionService.getUserSessions(userId);
|
|
77
|
+
|
|
78
|
+
ctx.body = {
|
|
79
|
+
data: sessions,
|
|
80
|
+
meta: {
|
|
81
|
+
count: sessions.length,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
} catch (err) {
|
|
85
|
+
ctx.throw(500, 'Error fetching user sessions');
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Logout handler - terminates current session
|
|
91
|
+
* POST /api/magic-sessionmanager/logout
|
|
92
|
+
*/
|
|
93
|
+
async logout(ctx) {
|
|
94
|
+
try {
|
|
95
|
+
const userId = ctx.state.user?.id;
|
|
96
|
+
const token = ctx.request.headers.authorization?.replace('Bearer ', '');
|
|
97
|
+
|
|
98
|
+
if (!userId) {
|
|
99
|
+
return ctx.throw(401, 'Unauthorized');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const sessionService = strapi
|
|
103
|
+
.plugin('magic-sessionmanager')
|
|
104
|
+
.service('session');
|
|
105
|
+
|
|
106
|
+
// Find current session by token
|
|
107
|
+
const sessions = await strapi.entityService.findMany('plugin::magic-sessionmanager.session', {
|
|
108
|
+
filters: {
|
|
109
|
+
user: { id: userId },
|
|
110
|
+
token: token,
|
|
111
|
+
isActive: true,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (sessions.length > 0) {
|
|
116
|
+
// Terminate only the current session
|
|
117
|
+
await sessionService.terminateSession({ sessionId: sessions[0].id });
|
|
118
|
+
strapi.log.info(`[magic-sessionmanager] User ${userId} logged out (session ${sessions[0].id})`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
ctx.body = {
|
|
122
|
+
message: 'Logged out successfully',
|
|
123
|
+
};
|
|
124
|
+
} catch (err) {
|
|
125
|
+
strapi.log.error('[magic-sessionmanager] Logout error:', err);
|
|
126
|
+
ctx.throw(500, 'Error during logout');
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Logout from all devices - terminates all sessions for current user
|
|
132
|
+
* POST /api/magic-sessionmanager/logout-all
|
|
133
|
+
*/
|
|
134
|
+
async logoutAll(ctx) {
|
|
135
|
+
try {
|
|
136
|
+
const userId = ctx.state.user?.id;
|
|
137
|
+
|
|
138
|
+
if (!userId) {
|
|
139
|
+
return ctx.throw(401, 'Unauthorized');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const sessionService = strapi
|
|
143
|
+
.plugin('magic-sessionmanager')
|
|
144
|
+
.service('session');
|
|
145
|
+
|
|
146
|
+
// Terminate all sessions for this user
|
|
147
|
+
await sessionService.terminateSession({ userId });
|
|
148
|
+
|
|
149
|
+
strapi.log.info(`[magic-sessionmanager] User ${userId} logged out from all devices`);
|
|
150
|
+
|
|
151
|
+
ctx.body = {
|
|
152
|
+
message: 'Logged out from all devices successfully',
|
|
153
|
+
};
|
|
154
|
+
} catch (err) {
|
|
155
|
+
strapi.log.error('[magic-sessionmanager] Logout-all error:', err);
|
|
156
|
+
ctx.throw(500, 'Error during logout');
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Terminate specific session
|
|
162
|
+
* DELETE /magic-sessionmanager/sessions/:sessionId
|
|
163
|
+
*/
|
|
164
|
+
async terminateSession(ctx) {
|
|
165
|
+
try {
|
|
166
|
+
const { sessionId } = ctx.params;
|
|
167
|
+
const sessionService = strapi
|
|
168
|
+
.plugin('magic-sessionmanager')
|
|
169
|
+
.service('session');
|
|
170
|
+
|
|
171
|
+
await sessionService.terminateSession({ sessionId });
|
|
172
|
+
|
|
173
|
+
ctx.body = {
|
|
174
|
+
message: `Session ${sessionId} terminated`,
|
|
175
|
+
};
|
|
176
|
+
} catch (err) {
|
|
177
|
+
ctx.throw(500, 'Error terminating session');
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Terminate a single session (Admin action)
|
|
183
|
+
* POST /magic-sessionmanager/sessions/:sessionId/terminate
|
|
184
|
+
*/
|
|
185
|
+
async terminateSingleSession(ctx) {
|
|
186
|
+
try {
|
|
187
|
+
const { sessionId } = ctx.params;
|
|
188
|
+
|
|
189
|
+
const sessionService = strapi
|
|
190
|
+
.plugin('magic-sessionmanager')
|
|
191
|
+
.service('session');
|
|
192
|
+
|
|
193
|
+
await sessionService.terminateSession({ sessionId });
|
|
194
|
+
|
|
195
|
+
ctx.body = {
|
|
196
|
+
message: `Session ${sessionId} terminated`,
|
|
197
|
+
success: true,
|
|
198
|
+
};
|
|
199
|
+
} catch (err) {
|
|
200
|
+
strapi.log.error('[magic-sessionmanager] Error terminating session:', err);
|
|
201
|
+
ctx.throw(500, 'Error terminating session');
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Terminate ALL sessions for a specific user (Admin action)
|
|
207
|
+
* POST /magic-sessionmanager/user/:userId/terminate-all
|
|
208
|
+
*/
|
|
209
|
+
async terminateAllUserSessions(ctx) {
|
|
210
|
+
try {
|
|
211
|
+
const { userId } = ctx.params;
|
|
212
|
+
|
|
213
|
+
const sessionService = strapi
|
|
214
|
+
.plugin('magic-sessionmanager')
|
|
215
|
+
.service('session');
|
|
216
|
+
|
|
217
|
+
await sessionService.terminateSession({ userId });
|
|
218
|
+
|
|
219
|
+
ctx.body = {
|
|
220
|
+
message: `All sessions terminated for user ${userId}`,
|
|
221
|
+
success: true,
|
|
222
|
+
};
|
|
223
|
+
} catch (err) {
|
|
224
|
+
strapi.log.error('[magic-sessionmanager] Error terminating all user sessions:', err);
|
|
225
|
+
ctx.throw(500, 'Error terminating all user sessions');
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get IP Geolocation data (Premium feature)
|
|
231
|
+
* GET /magic-sessionmanager/geolocation/:ipAddress
|
|
232
|
+
*/
|
|
233
|
+
async getIpGeolocation(ctx) {
|
|
234
|
+
try {
|
|
235
|
+
const { ipAddress } = ctx.params;
|
|
236
|
+
|
|
237
|
+
if (!ipAddress) {
|
|
238
|
+
return ctx.badRequest('IP address is required');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Check if user has premium license
|
|
242
|
+
const licenseGuard = strapi.plugin('magic-sessionmanager').service('license-guard');
|
|
243
|
+
const pluginStore = strapi.store({
|
|
244
|
+
type: 'plugin',
|
|
245
|
+
name: 'magic-sessionmanager'
|
|
246
|
+
});
|
|
247
|
+
const licenseKey = await pluginStore.get({ key: 'licenseKey' });
|
|
248
|
+
|
|
249
|
+
if (!licenseKey) {
|
|
250
|
+
return ctx.forbidden('Premium license required for geolocation features');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const license = await licenseGuard.getLicenseByKey(licenseKey);
|
|
254
|
+
|
|
255
|
+
if (!license || !license.featurePremium) {
|
|
256
|
+
return ctx.forbidden('Premium license required for geolocation features');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Get geolocation data
|
|
260
|
+
const geolocationService = strapi.plugin('magic-sessionmanager').service('geolocation');
|
|
261
|
+
const ipData = await geolocationService.getIpInfo(ipAddress);
|
|
262
|
+
|
|
263
|
+
ctx.body = {
|
|
264
|
+
success: true,
|
|
265
|
+
data: ipData,
|
|
266
|
+
};
|
|
267
|
+
} catch (err) {
|
|
268
|
+
strapi.log.error('[magic-sessionmanager] Error getting IP geolocation:', err);
|
|
269
|
+
ctx.throw(500, 'Error fetching IP geolocation data');
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Delete a single session permanently (Admin action)
|
|
275
|
+
* DELETE /magic-sessionmanager/sessions/:sessionId
|
|
276
|
+
*/
|
|
277
|
+
async deleteSession(ctx) {
|
|
278
|
+
try {
|
|
279
|
+
const { sessionId } = ctx.params;
|
|
280
|
+
|
|
281
|
+
const sessionService = strapi
|
|
282
|
+
.plugin('magic-sessionmanager')
|
|
283
|
+
.service('session');
|
|
284
|
+
|
|
285
|
+
await sessionService.deleteSession(sessionId);
|
|
286
|
+
|
|
287
|
+
ctx.body = {
|
|
288
|
+
message: `Session ${sessionId} permanently deleted`,
|
|
289
|
+
success: true,
|
|
290
|
+
};
|
|
291
|
+
} catch (err) {
|
|
292
|
+
strapi.log.error('[magic-sessionmanager] Error deleting session:', err);
|
|
293
|
+
ctx.throw(500, 'Error deleting session');
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Delete all inactive sessions (Admin action)
|
|
299
|
+
* POST /magic-sessionmanager/sessions/clean-inactive
|
|
300
|
+
*/
|
|
301
|
+
async cleanInactiveSessions(ctx) {
|
|
302
|
+
try {
|
|
303
|
+
const sessionService = strapi
|
|
304
|
+
.plugin('magic-sessionmanager')
|
|
305
|
+
.service('session');
|
|
306
|
+
|
|
307
|
+
const deletedCount = await sessionService.deleteInactiveSessions();
|
|
308
|
+
|
|
309
|
+
ctx.body = {
|
|
310
|
+
message: `Successfully deleted ${deletedCount} inactive sessions`,
|
|
311
|
+
success: true,
|
|
312
|
+
deletedCount,
|
|
313
|
+
};
|
|
314
|
+
} catch (err) {
|
|
315
|
+
strapi.log.error('[magic-sessionmanager] Error cleaning inactive sessions:', err);
|
|
316
|
+
ctx.throw(500, 'Error deleting inactive sessions');
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Toggle user blocked status
|
|
322
|
+
* POST /magic-sessionmanager/user/:userId/toggle-block
|
|
323
|
+
*/
|
|
324
|
+
async toggleUserBlock(ctx) {
|
|
325
|
+
try {
|
|
326
|
+
const { userId } = ctx.params;
|
|
327
|
+
|
|
328
|
+
// Get current user status
|
|
329
|
+
const user = await strapi.entityService.findOne('plugin::users-permissions.user', userId);
|
|
330
|
+
|
|
331
|
+
if (!user) {
|
|
332
|
+
return ctx.throw(404, 'User not found');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Toggle blocked status
|
|
336
|
+
const newBlockedStatus = !user.blocked;
|
|
337
|
+
|
|
338
|
+
await strapi.entityService.update('plugin::users-permissions.user', userId, {
|
|
339
|
+
data: {
|
|
340
|
+
blocked: newBlockedStatus,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// If blocking user, terminate all their sessions
|
|
345
|
+
if (newBlockedStatus) {
|
|
346
|
+
const sessionService = strapi
|
|
347
|
+
.plugin('magic-sessionmanager')
|
|
348
|
+
.service('session');
|
|
349
|
+
await sessionService.terminateSession({ userId });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
ctx.body = {
|
|
353
|
+
message: `User ${newBlockedStatus ? 'blocked' : 'unblocked'} successfully`,
|
|
354
|
+
blocked: newBlockedStatus,
|
|
355
|
+
success: true,
|
|
356
|
+
};
|
|
357
|
+
} catch (err) {
|
|
358
|
+
strapi.log.error('[magic-sessionmanager] Error toggling user block:', err);
|
|
359
|
+
ctx.throw(500, 'Error toggling user block status');
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
};
|