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.
Files changed (52) hide show
  1. package/admin/jsconfig.json +10 -0
  2. package/admin/src/components/Initializer.jsx +11 -0
  3. package/admin/src/components/LicenseGuard.jsx +591 -0
  4. package/admin/src/components/OnlineUsersWidget.jsx +208 -0
  5. package/admin/src/components/PluginIcon.jsx +8 -0
  6. package/admin/src/components/SessionDetailModal.jsx +445 -0
  7. package/admin/src/components/SessionInfoCard.jsx +151 -0
  8. package/admin/src/components/SessionInfoPanel.jsx +375 -0
  9. package/admin/src/components/index.jsx +5 -0
  10. package/admin/src/hooks/useLicense.js +103 -0
  11. package/admin/src/index.js +137 -0
  12. package/admin/src/pages/ActiveSessions.jsx +12 -0
  13. package/admin/src/pages/Analytics.jsx +735 -0
  14. package/admin/src/pages/App.jsx +12 -0
  15. package/admin/src/pages/HomePage.jsx +1248 -0
  16. package/admin/src/pages/License.jsx +603 -0
  17. package/admin/src/pages/Settings.jsx +1497 -0
  18. package/admin/src/pages/SettingsNew.jsx +1204 -0
  19. package/admin/src/pages/index.jsx +3 -0
  20. package/admin/src/pluginId.js +3 -0
  21. package/admin/src/translations/de.json +20 -0
  22. package/admin/src/translations/en.json +20 -0
  23. package/admin/src/utils/getTranslation.js +5 -0
  24. package/admin/src/utils/index.js +2 -0
  25. package/admin/src/utils/parseUserAgent.js +79 -0
  26. package/package.json +3 -1
  27. package/server/jsconfig.json +10 -0
  28. package/server/src/bootstrap.js +297 -0
  29. package/server/src/config/index.js +20 -0
  30. package/server/src/content-types/index.js +9 -0
  31. package/server/src/content-types/session/schema.json +76 -0
  32. package/server/src/controllers/controller.js +11 -0
  33. package/server/src/controllers/index.js +11 -0
  34. package/server/src/controllers/license.js +266 -0
  35. package/server/src/controllers/session.js +362 -0
  36. package/server/src/controllers/settings.js +122 -0
  37. package/server/src/destroy.js +18 -0
  38. package/server/src/index.js +21 -0
  39. package/server/src/middlewares/index.js +5 -0
  40. package/server/src/middlewares/last-seen.js +56 -0
  41. package/server/src/policies/index.js +3 -0
  42. package/server/src/register.js +32 -0
  43. package/server/src/routes/admin.js +149 -0
  44. package/server/src/routes/content-api.js +51 -0
  45. package/server/src/routes/index.js +9 -0
  46. package/server/src/services/geolocation.js +180 -0
  47. package/server/src/services/index.js +13 -0
  48. package/server/src/services/license-guard.js +308 -0
  49. package/server/src/services/notifications.js +319 -0
  50. package/server/src/services/service.js +7 -0
  51. package/server/src/services/session.js +345 -0
  52. package/server/src/utils/getClientIp.js +118 -0
@@ -0,0 +1,3 @@
1
+ export { default as HomePage } from './HomePage';
2
+ export { default as ActiveSessions } from './ActiveSessions';
3
+ export { default as App } from './App';
@@ -0,0 +1,3 @@
1
+ const pluginId = 'magic-sessionmanager';
2
+
3
+ export default pluginId;
@@ -0,0 +1,20 @@
1
+ {
2
+ "plugin.name": "Session Manager",
3
+ "plugin.description": "Verfolgung von Benutzer-Login/Logout und Sitzungsaktivität",
4
+ "settings.section": "Session Manager",
5
+ "settings.sessions": "Aktive Sitzungen",
6
+ "settings.emailTemplates.title": "E-Mail Templates",
7
+ "settings.emailTemplates.description": "Passe E-Mail-Benachrichtigungen mit dynamischen Variablen an",
8
+ "settings.emailTemplates.validate": "Validieren",
9
+ "settings.emailTemplates.loadDefault": "Standard-Template laden",
10
+ "settings.emailTemplates.subject": "E-Mail Betreff",
11
+ "settings.emailTemplates.htmlTemplate": "HTML Template",
12
+ "settings.emailTemplates.textTemplate": "Text Template (Fallback)",
13
+ "settings.emailTemplates.variables": "Verfügbare Variablen (klicken zum Kopieren)",
14
+ "settings.emailTemplates.validation.success": "Template gültig! {count} Variablen gefunden.",
15
+ "settings.emailTemplates.validation.warning": "Keine Variablen im Template gefunden. Füge mindestens eine Variable hinzu.",
16
+ "settings.emailTemplates.defaultLoaded": "Standard-Template geladen!",
17
+ "settings.emailTemplates.suspiciousLogin": "Verdächtiger Login",
18
+ "settings.emailTemplates.newLocation": "Neuer Standort",
19
+ "settings.emailTemplates.vpnProxy": "VPN/Proxy"
20
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "plugin.name": "Session Manager",
3
+ "plugin.description": "Track user login/logout and session activity",
4
+ "settings.section": "Session Manager",
5
+ "settings.sessions": "Active Sessions",
6
+ "settings.emailTemplates.title": "Email Templates",
7
+ "settings.emailTemplates.description": "Customize email notification templates with dynamic variables",
8
+ "settings.emailTemplates.validate": "Validate",
9
+ "settings.emailTemplates.loadDefault": "Load Default Template",
10
+ "settings.emailTemplates.subject": "Email Subject",
11
+ "settings.emailTemplates.htmlTemplate": "HTML Template",
12
+ "settings.emailTemplates.textTemplate": "Text Template (Fallback)",
13
+ "settings.emailTemplates.variables": "Available Variables (click to copy)",
14
+ "settings.emailTemplates.validation.success": "Template valid! Found {count} variables.",
15
+ "settings.emailTemplates.validation.warning": "No variables found in template. Add at least one variable.",
16
+ "settings.emailTemplates.defaultLoaded": "Default template loaded!",
17
+ "settings.emailTemplates.suspiciousLogin": "Suspicious Login",
18
+ "settings.emailTemplates.newLocation": "New Location",
19
+ "settings.emailTemplates.vpnProxy": "VPN/Proxy"
20
+ }
@@ -0,0 +1,5 @@
1
+ import { PLUGIN_ID } from '../pluginId';
2
+
3
+ const getTranslation = (id) => `${PLUGIN_ID}.${id}`;
4
+
5
+ export { getTranslation };
@@ -0,0 +1,2 @@
1
+ export { default as parseUserAgent } from './parseUserAgent';
2
+
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Parse User Agent to extract device and browser info
3
+ * Returns human-readable device type and browser name
4
+ */
5
+
6
+ export const parseUserAgent = (userAgent) => {
7
+ if (!userAgent) {
8
+ return {
9
+ device: 'Unknown',
10
+ deviceIcon: '❓',
11
+ browser: 'Unknown',
12
+ os: 'Unknown',
13
+ };
14
+ }
15
+
16
+ const ua = userAgent.toLowerCase();
17
+
18
+ // Device detection
19
+ let device = 'Desktop';
20
+ let deviceIcon = '💻';
21
+
22
+ if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(userAgent)) {
23
+ device = 'Tablet';
24
+ deviceIcon = '📱';
25
+ } else if (/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(userAgent)) {
26
+ device = 'Mobile';
27
+ deviceIcon = '📱';
28
+ }
29
+
30
+ // Browser detection
31
+ let browser = 'Unknown';
32
+ if (ua.includes('edg/')) {
33
+ browser = 'Edge';
34
+ } else if (ua.includes('chrome/') && !ua.includes('edg/')) {
35
+ browser = 'Chrome';
36
+ } else if (ua.includes('firefox/')) {
37
+ browser = 'Firefox';
38
+ } else if (ua.includes('safari/') && !ua.includes('chrome/')) {
39
+ browser = 'Safari';
40
+ } else if (ua.includes('opera/') || ua.includes('opr/')) {
41
+ browser = 'Opera';
42
+ } else if (ua.includes('curl/')) {
43
+ browser = 'cURL';
44
+ deviceIcon = '⚙️';
45
+ device = 'API Client';
46
+ } else if (ua.includes('postman')) {
47
+ browser = 'Postman';
48
+ deviceIcon = '📮';
49
+ device = 'API Client';
50
+ } else if (ua.includes('insomnia')) {
51
+ browser = 'Insomnia';
52
+ deviceIcon = '🌙';
53
+ device = 'API Client';
54
+ }
55
+
56
+ // OS detection
57
+ let os = 'Unknown';
58
+ if (ua.includes('windows')) {
59
+ os = 'Windows';
60
+ } else if (ua.includes('mac os x') || ua.includes('macintosh')) {
61
+ os = 'macOS';
62
+ } else if (ua.includes('linux')) {
63
+ os = 'Linux';
64
+ } else if (ua.includes('android')) {
65
+ os = 'Android';
66
+ } else if (ua.includes('iphone') || ua.includes('ipad')) {
67
+ os = 'iOS';
68
+ }
69
+
70
+ return {
71
+ device,
72
+ deviceIcon,
73
+ browser,
74
+ os,
75
+ };
76
+ };
77
+
78
+ export default parseUserAgent;
79
+
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.0.0",
2
+ "version": "2.0.2",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",
@@ -27,6 +27,8 @@
27
27
  },
28
28
  "files": [
29
29
  "dist",
30
+ "server",
31
+ "admin",
30
32
  "strapi-admin.js",
31
33
  "strapi-server.js",
32
34
  "README.md",
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "module": "commonjs",
5
+ "allowSyntheticDefaultImports": true,
6
+ "esModuleInterop": true
7
+ },
8
+ "include": ["./src/**/*.js"],
9
+ "exclude": ["node_modules"]
10
+ }
@@ -0,0 +1,297 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Bootstrap: Mount middleware for session tracking
5
+ * Sessions are managed via plugin::magic-sessionmanager.session content type
6
+ *
7
+ * NOTE: For multi-instance deployments, consider Redis locks or session store
8
+ */
9
+
10
+ const getClientIp = require('./utils/getClientIp');
11
+
12
+ module.exports = async ({ strapi }) => {
13
+ strapi.log.info('[magic-sessionmanager] 🚀 Bootstrap starting...');
14
+
15
+ try {
16
+ // Initialize License Guard
17
+ const licenseGuardService = strapi.plugin('magic-sessionmanager').service('license-guard');
18
+
19
+ // Wait a bit for all services to be ready
20
+ setTimeout(async () => {
21
+ const licenseStatus = await licenseGuardService.initialize();
22
+
23
+ if (!licenseStatus.valid) {
24
+ strapi.log.error('╔════════════════════════════════════════════════════════════════╗');
25
+ strapi.log.error('║ ❌ SESSION MANAGER - NO VALID LICENSE ║');
26
+ strapi.log.error('║ ║');
27
+ strapi.log.error('║ This plugin requires a valid license to operate. ║');
28
+ strapi.log.error('║ Please activate your license via Admin UI: ║');
29
+ strapi.log.error('║ Go to Settings → Sessions → License ║');
30
+ strapi.log.error('║ ║');
31
+ strapi.log.error('║ The plugin will run with limited functionality until ║');
32
+ strapi.log.error('║ a valid license is activated. ║');
33
+ strapi.log.error('╚════════════════════════════════════════════════════════════════╝');
34
+ } else if (licenseStatus.valid) {
35
+ const pluginStore = strapi.store({
36
+ type: 'plugin',
37
+ name: 'magic-sessionmanager',
38
+ });
39
+ const storedKey = await pluginStore.get({ key: 'licenseKey' });
40
+
41
+ strapi.log.info('╔════════════════════════════════════════════════════════════════╗');
42
+ strapi.log.info('║ ✅ SESSION MANAGER LICENSE ACTIVE ║');
43
+ strapi.log.info('║ ║');
44
+
45
+ if (licenseStatus.data) {
46
+ strapi.log.info(`║ License: ${licenseStatus.data.licenseKey}`.padEnd(66) + '║');
47
+ strapi.log.info(`║ User: ${licenseStatus.data.firstName} ${licenseStatus.data.lastName}`.padEnd(66) + '║');
48
+ strapi.log.info(`║ Email: ${licenseStatus.data.email}`.padEnd(66) + '║');
49
+ } else if (storedKey) {
50
+ strapi.log.info(`║ License: ${storedKey} (Offline Mode)`.padEnd(66) + '║');
51
+ strapi.log.info(`║ Status: Grace Period Active`.padEnd(66) + '║');
52
+ }
53
+
54
+ strapi.log.info('║ ║');
55
+ strapi.log.info('║ 🔄 Auto-pinging every 15 minutes ║');
56
+ strapi.log.info('╚════════════════════════════════════════════════════════════════╝');
57
+ }
58
+ }, 3000); // Wait 3 seconds for API to be ready
59
+
60
+ // Get session service
61
+ const sessionService = strapi
62
+ .plugin('magic-sessionmanager')
63
+ .service('session');
64
+
65
+ // Cleanup inactive sessions on startup
66
+ strapi.log.info('[magic-sessionmanager] Running initial session cleanup...');
67
+ await sessionService.cleanupInactiveSessions();
68
+
69
+ // Schedule periodic cleanup every 30 minutes
70
+ const cleanupInterval = 30 * 60 * 1000; // 30 minutes
71
+
72
+ const cleanupIntervalHandle = setInterval(async () => {
73
+ try {
74
+ // Get fresh reference to service to avoid scope issues
75
+ const service = strapi.plugin('magic-sessionmanager').service('session');
76
+ await service.cleanupInactiveSessions();
77
+ } catch (err) {
78
+ strapi.log.error('[magic-sessionmanager] Periodic cleanup error:', err);
79
+ }
80
+ }, cleanupInterval);
81
+
82
+ strapi.log.info('[magic-sessionmanager] ⏰ Periodic cleanup scheduled (every 30 minutes)');
83
+
84
+ // Store interval handle for cleanup on shutdown
85
+ if (!strapi.sessionManagerIntervals) {
86
+ strapi.sessionManagerIntervals = {};
87
+ }
88
+ strapi.sessionManagerIntervals.cleanup = cleanupIntervalHandle;
89
+
90
+ // HIGH PRIORITY: Register /api/auth/logout route BEFORE other plugins
91
+ strapi.server.routes([{
92
+ method: 'POST',
93
+ path: '/api/auth/logout',
94
+ handler: async (ctx) => {
95
+ try {
96
+ const token = ctx.request.headers?.authorization?.replace('Bearer ', '');
97
+
98
+ if (!token) {
99
+ ctx.status = 200;
100
+ ctx.body = { message: 'Logged out successfully' };
101
+ return;
102
+ }
103
+
104
+ // Find and terminate session by token
105
+ const sessions = await strapi.entityService.findMany('plugin::magic-sessionmanager.session', {
106
+ filters: {
107
+ token: token,
108
+ isActive: true,
109
+ },
110
+ limit: 1,
111
+ });
112
+
113
+ if (sessions.length > 0) {
114
+ await sessionService.terminateSession({ sessionId: sessions[0].id });
115
+ strapi.log.info(`[magic-sessionmanager] 🚪 Logout via /api/auth/logout - Session ${sessions[0].id} terminated`);
116
+ }
117
+
118
+ ctx.status = 200;
119
+ ctx.body = { message: 'Logged out successfully' };
120
+ } catch (err) {
121
+ strapi.log.error('[magic-sessionmanager] Logout error:', err);
122
+ ctx.status = 200;
123
+ ctx.body = { message: 'Logged out successfully' };
124
+ }
125
+ },
126
+ config: {
127
+ auth: false,
128
+ },
129
+ }]);
130
+
131
+ strapi.log.info('[magic-sessionmanager] ✅ /api/auth/logout route registered');
132
+
133
+ // Middleware to intercept logins
134
+ strapi.server.use(async (ctx, next) => {
135
+ // Execute the actual request
136
+ await next();
137
+
138
+ // Check if this was a successful login request
139
+ const isAuthLocal = ctx.path === '/api/auth/local' && ctx.method === 'POST';
140
+ const isMagicLink = ctx.path.includes('/magic-link/login') && ctx.method === 'POST';
141
+
142
+ if ((isAuthLocal || isMagicLink) && ctx.status === 200 && ctx.body && ctx.body.user) {
143
+ try {
144
+ const user = ctx.body.user;
145
+
146
+ // Extract REAL client IP (handles proxies, load balancers, Cloudflare, etc.)
147
+ const ip = getClientIp(ctx);
148
+ const userAgent = ctx.request.headers?.['user-agent'] || ctx.request.header?.['user-agent'] || 'unknown';
149
+
150
+ strapi.log.info(`[magic-sessionmanager] 🔍 Login detected! User: ${user.id} (${user.email || user.username}) from IP: ${ip}`);
151
+
152
+ // Get config
153
+ const config = strapi.config.get('plugin::magic-sessionmanager') || {};
154
+
155
+ // Check if we should analyze this session (Premium/Advanced features)
156
+ let shouldBlock = false;
157
+ let blockReason = '';
158
+ let geoData = null;
159
+
160
+ // Premium: Get geolocation data
161
+ if (config.enableGeolocation || config.enableSecurityScoring) {
162
+ try {
163
+ const geolocationService = strapi.plugin('magic-sessionmanager').service('geolocation');
164
+ geoData = await geolocationService.getIpInfo(ip);
165
+
166
+ // Advanced: Auto-blocking
167
+ if (config.blockSuspiciousSessions && geoData) {
168
+ if (geoData.isThreat) {
169
+ shouldBlock = true;
170
+ blockReason = 'Known threat IP detected';
171
+ } else if (geoData.isVpn && config.alertOnVpnProxy) {
172
+ shouldBlock = true;
173
+ blockReason = 'VPN detected';
174
+ } else if (geoData.isProxy && config.alertOnVpnProxy) {
175
+ shouldBlock = true;
176
+ blockReason = 'Proxy detected';
177
+ } else if (geoData.securityScore < 50) {
178
+ shouldBlock = true;
179
+ blockReason = `Low security score: ${geoData.securityScore}/100`;
180
+ }
181
+ }
182
+
183
+ // Advanced: Geo-fencing
184
+ if (config.enableGeofencing && geoData && geoData.country_code) {
185
+ const countryCode = geoData.country_code;
186
+
187
+ // Check blocked countries
188
+ if (config.blockedCountries && config.blockedCountries.includes(countryCode)) {
189
+ shouldBlock = true;
190
+ blockReason = `Country ${countryCode} is blocked`;
191
+ }
192
+
193
+ // Check allowed countries (whitelist)
194
+ if (config.allowedCountries && config.allowedCountries.length > 0) {
195
+ if (!config.allowedCountries.includes(countryCode)) {
196
+ shouldBlock = true;
197
+ blockReason = `Country ${countryCode} is not in allowlist`;
198
+ }
199
+ }
200
+ }
201
+ } catch (geoErr) {
202
+ strapi.log.warn('[magic-sessionmanager] Geolocation check failed:', geoErr.message);
203
+ }
204
+ }
205
+
206
+ // Block if needed
207
+ if (shouldBlock) {
208
+ strapi.log.warn(`[magic-sessionmanager] 🚫 Blocking login: ${blockReason}`);
209
+
210
+ // Don't create session, return error
211
+ ctx.status = 403;
212
+ ctx.body = {
213
+ error: {
214
+ status: 403,
215
+ message: 'Login blocked for security reasons',
216
+ details: { reason: blockReason }
217
+ }
218
+ };
219
+ return; // Stop here
220
+ }
221
+
222
+ // Create a new session
223
+ const newSession = await sessionService.createSession({
224
+ userId: user.id,
225
+ ip,
226
+ userAgent,
227
+ token: ctx.body.jwt, // Store JWT token reference
228
+ });
229
+
230
+ strapi.log.info(`[magic-sessionmanager] ✅ Session created for user ${user.id} (IP: ${ip})`);
231
+
232
+ // Advanced: Send notifications
233
+ if (geoData && (config.enableEmailAlerts || config.enableWebhooks)) {
234
+ try {
235
+ const notificationService = strapi.plugin('magic-sessionmanager').service('notifications');
236
+
237
+ // Determine if suspicious
238
+ const isSuspicious = geoData.isVpn || geoData.isProxy || geoData.isThreat || geoData.securityScore < 70;
239
+
240
+ // Email alerts
241
+ if (config.enableEmailAlerts && config.alertOnSuspiciousLogin && isSuspicious) {
242
+ await notificationService.sendSuspiciousLoginAlert({
243
+ user,
244
+ session: newSession,
245
+ reason: {
246
+ isVpn: geoData.isVpn,
247
+ isProxy: geoData.isProxy,
248
+ isThreat: geoData.isThreat,
249
+ securityScore: geoData.securityScore,
250
+ },
251
+ geoData,
252
+ });
253
+ }
254
+
255
+ // Webhook notifications (Discord/Slack)
256
+ if (config.enableWebhooks) {
257
+ const webhookData = notificationService.formatDiscordWebhook({
258
+ event: isSuspicious ? 'login.suspicious' : 'login.success',
259
+ session: newSession,
260
+ user,
261
+ geoData,
262
+ });
263
+
264
+ if (config.discordWebhookUrl) {
265
+ await notificationService.sendWebhook({
266
+ event: 'session.login',
267
+ data: webhookData,
268
+ webhookUrl: config.discordWebhookUrl,
269
+ });
270
+ }
271
+ }
272
+ } catch (notifErr) {
273
+ strapi.log.warn('[magic-sessionmanager] Notification failed:', notifErr.message);
274
+ }
275
+ }
276
+ } catch (err) {
277
+ strapi.log.error('[magic-sessionmanager] ❌ Error creating session:', err);
278
+ // Don't throw - login should still succeed even if session creation fails
279
+ }
280
+ }
281
+ });
282
+
283
+ strapi.log.info('[magic-sessionmanager] ✅ Login/Logout interceptor middleware mounted');
284
+
285
+ // Mount lastSeen update middleware
286
+ strapi.server.use(
287
+ require('./middlewares/last-seen')({ strapi, sessionService })
288
+ );
289
+
290
+ strapi.log.info('[magic-sessionmanager] ✅ LastSeen middleware mounted');
291
+ strapi.log.info('[magic-sessionmanager] ✅ Bootstrap complete');
292
+ strapi.log.info('[magic-sessionmanager] 🎉 Session Manager ready! Sessions stored in plugin::magic-sessionmanager.session');
293
+
294
+ } catch (err) {
295
+ strapi.log.error('[magic-sessionmanager] ❌ Bootstrap error:', err);
296
+ }
297
+ };
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ default: {
5
+ // Rate limit for lastSeen updates (in milliseconds)
6
+ lastSeenRateLimit: 30000, // 30 seconds
7
+
8
+ // Session inactivity timeout (in milliseconds)
9
+ // After this time without activity, a session is considered inactive
10
+ inactivityTimeout: 15 * 60 * 1000, // 15 minutes
11
+ },
12
+ validator: (config) => {
13
+ if (config.lastSeenRateLimit && typeof config.lastSeenRateLimit !== 'number') {
14
+ throw new Error('lastSeenRateLimit must be a number (milliseconds)');
15
+ }
16
+ if (config.inactivityTimeout && typeof config.inactivityTimeout !== 'number') {
17
+ throw new Error('inactivityTimeout must be a number (milliseconds)');
18
+ }
19
+ },
20
+ };
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const session = require('./session/schema.json');
4
+
5
+ module.exports = {
6
+ 'plugin::magic-sessionmanager.session': {
7
+ schema: session,
8
+ },
9
+ };
@@ -0,0 +1,76 @@
1
+ {
2
+ "kind": "collectionType",
3
+ "collectionName": "sessions",
4
+ "info": {
5
+ "singularName": "session",
6
+ "pluralName": "sessions",
7
+ "displayName": "Session",
8
+ "description": "User session tracking with IP, device, and activity information"
9
+ },
10
+ "options": {
11
+ "draftAndPublish": false,
12
+ "comment": ""
13
+ },
14
+ "pluginOptions": {
15
+ "content-manager": {
16
+ "visible": true
17
+ },
18
+ "content-type-builder": {
19
+ "visible": false
20
+ }
21
+ },
22
+ "attributes": {
23
+ "user": {
24
+ "type": "relation",
25
+ "relation": "manyToOne",
26
+ "target": "plugin::users-permissions.user",
27
+ "inversedBy": "sessions"
28
+ },
29
+ "ipAddress": {
30
+ "type": "string",
31
+ "maxLength": 45,
32
+ "required": true
33
+ },
34
+ "userAgent": {
35
+ "type": "text",
36
+ "maxLength": 500
37
+ },
38
+ "token": {
39
+ "type": "text",
40
+ "private": true
41
+ },
42
+ "loginTime": {
43
+ "type": "datetime",
44
+ "required": true
45
+ },
46
+ "logoutTime": {
47
+ "type": "datetime"
48
+ },
49
+ "lastActive": {
50
+ "type": "datetime"
51
+ },
52
+ "isActive": {
53
+ "type": "boolean",
54
+ "default": true,
55
+ "required": true
56
+ },
57
+ "geoLocation": {
58
+ "type": "json"
59
+ },
60
+ "securityScore": {
61
+ "type": "integer",
62
+ "min": 0,
63
+ "max": 100
64
+ },
65
+ "deviceType": {
66
+ "type": "string"
67
+ },
68
+ "browserName": {
69
+ "type": "string"
70
+ },
71
+ "osName": {
72
+ "type": "string"
73
+ }
74
+ }
75
+ }
76
+
@@ -0,0 +1,11 @@
1
+ const controller = ({ strapi }) => ({
2
+ index(ctx) {
3
+ ctx.body = strapi
4
+ .plugin('magic-sessionmanager')
5
+ // the name of the service file & the method.
6
+ .service('service')
7
+ .getWelcomeMessage();
8
+ },
9
+ });
10
+
11
+ export default controller;
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ const session = require('./session');
4
+ const license = require('./license');
5
+ const settings = require('./settings');
6
+
7
+ module.exports = {
8
+ session,
9
+ license,
10
+ settings,
11
+ };