strapi-plugin-magic-sessionmanager 4.2.3 → 4.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/index.js +1 -1
- package/dist/server/index.mjs +1 -1
- package/package.json +1 -3
- package/admin/jsconfig.json +0 -10
- package/admin/src/components/Initializer.jsx +0 -11
- package/admin/src/components/LicenseGuard.jsx +0 -591
- package/admin/src/components/OnlineUsersWidget.jsx +0 -212
- package/admin/src/components/PluginIcon.jsx +0 -8
- package/admin/src/components/SessionDetailModal.jsx +0 -449
- package/admin/src/components/SessionInfoCard.jsx +0 -151
- package/admin/src/components/SessionInfoPanel.jsx +0 -385
- package/admin/src/components/index.jsx +0 -5
- package/admin/src/hooks/useLicense.js +0 -103
- package/admin/src/index.js +0 -149
- package/admin/src/pages/ActiveSessions.jsx +0 -12
- package/admin/src/pages/Analytics.jsx +0 -735
- package/admin/src/pages/App.jsx +0 -12
- package/admin/src/pages/HomePage.jsx +0 -1212
- package/admin/src/pages/License.jsx +0 -603
- package/admin/src/pages/Settings.jsx +0 -1646
- package/admin/src/pages/SettingsNew.jsx +0 -1204
- package/admin/src/pages/UpgradePage.jsx +0 -448
- package/admin/src/pages/index.jsx +0 -3
- package/admin/src/pluginId.js +0 -4
- package/admin/src/translations/de.json +0 -299
- package/admin/src/translations/en.json +0 -299
- package/admin/src/translations/es.json +0 -287
- package/admin/src/translations/fr.json +0 -287
- package/admin/src/translations/pt.json +0 -287
- package/admin/src/utils/getTranslation.js +0 -5
- package/admin/src/utils/index.js +0 -2
- package/admin/src/utils/parseUserAgent.js +0 -79
- package/admin/src/utils/theme.js +0 -85
- package/server/jsconfig.json +0 -10
- package/server/src/bootstrap.js +0 -492
- package/server/src/config/index.js +0 -23
- package/server/src/content-types/index.js +0 -9
- package/server/src/content-types/session/schema.json +0 -84
- package/server/src/controllers/controller.js +0 -11
- package/server/src/controllers/index.js +0 -11
- package/server/src/controllers/license.js +0 -266
- package/server/src/controllers/session.js +0 -433
- package/server/src/controllers/settings.js +0 -122
- package/server/src/destroy.js +0 -22
- package/server/src/index.js +0 -23
- package/server/src/middlewares/index.js +0 -5
- package/server/src/middlewares/last-seen.js +0 -62
- package/server/src/policies/index.js +0 -3
- package/server/src/register.js +0 -36
- package/server/src/routes/admin.js +0 -149
- package/server/src/routes/content-api.js +0 -60
- package/server/src/routes/index.js +0 -9
- package/server/src/services/geolocation.js +0 -182
- package/server/src/services/index.js +0 -13
- package/server/src/services/license-guard.js +0 -316
- package/server/src/services/notifications.js +0 -319
- package/server/src/services/service.js +0 -7
- package/server/src/services/session.js +0 -393
- package/server/src/utils/encryption.js +0 -121
- package/server/src/utils/getClientIp.js +0 -118
- package/server/src/utils/logger.js +0 -84
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Settings controller
|
|
5
|
-
* Manages plugin settings stored in Strapi database
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
module.exports = {
|
|
9
|
-
/**
|
|
10
|
-
* Get plugin settings
|
|
11
|
-
*/
|
|
12
|
-
async getSettings(ctx) {
|
|
13
|
-
try {
|
|
14
|
-
const pluginStore = strapi.store({
|
|
15
|
-
type: 'plugin',
|
|
16
|
-
name: 'magic-sessionmanager',
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
let settings = await pluginStore.get({ key: 'settings' });
|
|
20
|
-
|
|
21
|
-
// If no settings exist, return defaults
|
|
22
|
-
if (!settings) {
|
|
23
|
-
settings = {
|
|
24
|
-
inactivityTimeout: 15,
|
|
25
|
-
cleanupInterval: 30,
|
|
26
|
-
lastSeenRateLimit: 30,
|
|
27
|
-
retentionDays: 90,
|
|
28
|
-
enableGeolocation: true,
|
|
29
|
-
enableSecurityScoring: true,
|
|
30
|
-
blockSuspiciousSessions: false,
|
|
31
|
-
maxFailedLogins: 5,
|
|
32
|
-
enableEmailAlerts: false,
|
|
33
|
-
alertOnSuspiciousLogin: true,
|
|
34
|
-
alertOnNewLocation: true,
|
|
35
|
-
alertOnVpnProxy: true,
|
|
36
|
-
enableWebhooks: false,
|
|
37
|
-
discordWebhookUrl: '',
|
|
38
|
-
slackWebhookUrl: '',
|
|
39
|
-
enableGeofencing: false,
|
|
40
|
-
allowedCountries: [],
|
|
41
|
-
blockedCountries: [],
|
|
42
|
-
emailTemplates: {
|
|
43
|
-
suspiciousLogin: { subject: '', html: '', text: '' },
|
|
44
|
-
newLocation: { subject: '', html: '', text: '' },
|
|
45
|
-
vpnProxy: { subject: '', html: '', text: '' },
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
ctx.send({
|
|
51
|
-
settings,
|
|
52
|
-
success: true
|
|
53
|
-
});
|
|
54
|
-
} catch (error) {
|
|
55
|
-
strapi.log.error('[magic-sessionmanager/settings] Error getting settings:', error);
|
|
56
|
-
ctx.badRequest('Error loading settings');
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Update plugin settings
|
|
62
|
-
*/
|
|
63
|
-
async updateSettings(ctx) {
|
|
64
|
-
try {
|
|
65
|
-
const { body } = ctx.request;
|
|
66
|
-
|
|
67
|
-
if (!body) {
|
|
68
|
-
return ctx.badRequest('Settings data is required');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const pluginStore = strapi.store({
|
|
72
|
-
type: 'plugin',
|
|
73
|
-
name: 'magic-sessionmanager',
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Validate and sanitize settings
|
|
77
|
-
const sanitizedSettings = {
|
|
78
|
-
inactivityTimeout: parseInt(body.inactivityTimeout) || 15,
|
|
79
|
-
cleanupInterval: parseInt(body.cleanupInterval) || 30,
|
|
80
|
-
lastSeenRateLimit: parseInt(body.lastSeenRateLimit) || 30,
|
|
81
|
-
retentionDays: parseInt(body.retentionDays) || 90,
|
|
82
|
-
enableGeolocation: !!body.enableGeolocation,
|
|
83
|
-
enableSecurityScoring: !!body.enableSecurityScoring,
|
|
84
|
-
blockSuspiciousSessions: !!body.blockSuspiciousSessions,
|
|
85
|
-
maxFailedLogins: parseInt(body.maxFailedLogins) || 5,
|
|
86
|
-
enableEmailAlerts: !!body.enableEmailAlerts,
|
|
87
|
-
alertOnSuspiciousLogin: !!body.alertOnSuspiciousLogin,
|
|
88
|
-
alertOnNewLocation: !!body.alertOnNewLocation,
|
|
89
|
-
alertOnVpnProxy: !!body.alertOnVpnProxy,
|
|
90
|
-
enableWebhooks: !!body.enableWebhooks,
|
|
91
|
-
discordWebhookUrl: String(body.discordWebhookUrl || ''),
|
|
92
|
-
slackWebhookUrl: String(body.slackWebhookUrl || ''),
|
|
93
|
-
enableGeofencing: !!body.enableGeofencing,
|
|
94
|
-
allowedCountries: Array.isArray(body.allowedCountries) ? body.allowedCountries : [],
|
|
95
|
-
blockedCountries: Array.isArray(body.blockedCountries) ? body.blockedCountries : [],
|
|
96
|
-
emailTemplates: body.emailTemplates || {
|
|
97
|
-
suspiciousLogin: { subject: '', html: '', text: '' },
|
|
98
|
-
newLocation: { subject: '', html: '', text: '' },
|
|
99
|
-
vpnProxy: { subject: '', html: '', text: '' },
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
// Save to database
|
|
104
|
-
await pluginStore.set({
|
|
105
|
-
key: 'settings',
|
|
106
|
-
value: sanitizedSettings
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
strapi.log.info('[magic-sessionmanager/settings] Settings updated successfully');
|
|
110
|
-
|
|
111
|
-
ctx.send({
|
|
112
|
-
settings: sanitizedSettings,
|
|
113
|
-
success: true,
|
|
114
|
-
message: 'Settings saved successfully!'
|
|
115
|
-
});
|
|
116
|
-
} catch (error) {
|
|
117
|
-
strapi.log.error('[magic-sessionmanager/settings] Error updating settings:', error);
|
|
118
|
-
ctx.badRequest('Error saving settings');
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
|
package/server/src/destroy.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { createLogger } = require('./utils/logger');
|
|
4
|
-
|
|
5
|
-
module.exports = async ({ strapi }) => {
|
|
6
|
-
const log = createLogger(strapi);
|
|
7
|
-
|
|
8
|
-
// Stop license pinging
|
|
9
|
-
if (strapi.licenseGuard && strapi.licenseGuard.pingInterval) {
|
|
10
|
-
clearInterval(strapi.licenseGuard.pingInterval);
|
|
11
|
-
log.info('🛑 License pinging stopped');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Stop cleanup interval
|
|
15
|
-
if (strapi.sessionManagerIntervals && strapi.sessionManagerIntervals.cleanup) {
|
|
16
|
-
clearInterval(strapi.sessionManagerIntervals.cleanup);
|
|
17
|
-
log.info('🛑 Session cleanup interval stopped');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
log.info('[SUCCESS] Plugin cleanup completed');
|
|
21
|
-
};
|
|
22
|
-
|
package/server/src/index.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const register = require('./register');
|
|
4
|
-
const bootstrap = require('./bootstrap');
|
|
5
|
-
const destroy = require('./destroy');
|
|
6
|
-
const config = require('./config');
|
|
7
|
-
const contentTypes = require('./content-types');
|
|
8
|
-
const routes = require('./routes');
|
|
9
|
-
const controllers = require('./controllers');
|
|
10
|
-
const services = require('./services');
|
|
11
|
-
const middlewares = require('./middlewares');
|
|
12
|
-
|
|
13
|
-
module.exports = {
|
|
14
|
-
register,
|
|
15
|
-
bootstrap,
|
|
16
|
-
destroy,
|
|
17
|
-
config,
|
|
18
|
-
contentTypes,
|
|
19
|
-
routes,
|
|
20
|
-
controllers,
|
|
21
|
-
services,
|
|
22
|
-
middlewares,
|
|
23
|
-
};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* lastSeen Middleware
|
|
5
|
-
* Updates user lastSeen and session lastActive on each authenticated request
|
|
6
|
-
* Rate-limited to prevent DB write noise (default: 30 seconds)
|
|
7
|
-
*
|
|
8
|
-
* [SUCCESS] Migrated to strapi.documents() API (Strapi v5 Best Practice)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const SESSION_UID = 'plugin::magic-sessionmanager.session';
|
|
12
|
-
|
|
13
|
-
module.exports = ({ strapi, sessionService }) => {
|
|
14
|
-
return async (ctx, next) => {
|
|
15
|
-
// BEFORE processing request: Check if user's sessions are active
|
|
16
|
-
// Strapi v5: Use documentId instead of numeric id for Document Service API
|
|
17
|
-
if (ctx.state.user && ctx.state.user.documentId) {
|
|
18
|
-
try {
|
|
19
|
-
const userId = ctx.state.user.documentId;
|
|
20
|
-
|
|
21
|
-
// Check if user has ANY active sessions
|
|
22
|
-
const activeSessions = await strapi.documents(SESSION_UID).findMany( {
|
|
23
|
-
filters: {
|
|
24
|
-
user: { documentId: userId },
|
|
25
|
-
isActive: true,
|
|
26
|
-
},
|
|
27
|
-
limit: 1,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// If user has NO active sessions, reject the request
|
|
31
|
-
if (!activeSessions || activeSessions.length === 0) {
|
|
32
|
-
strapi.log.info(`[magic-sessionmanager] [BLOCKED] Blocked request - User ${userId} has no active sessions`);
|
|
33
|
-
return ctx.unauthorized('All sessions have been terminated. Please login again.');
|
|
34
|
-
}
|
|
35
|
-
} catch (err) {
|
|
36
|
-
strapi.log.debug('[magic-sessionmanager] Error checking active sessions:', err.message);
|
|
37
|
-
// On error, allow request to continue (fail-open for availability)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Process request
|
|
42
|
-
await next();
|
|
43
|
-
|
|
44
|
-
// AFTER response: Update activity timestamps if user is authenticated
|
|
45
|
-
if (ctx.state.user && ctx.state.user.documentId) {
|
|
46
|
-
try {
|
|
47
|
-
const userId = ctx.state.user.documentId;
|
|
48
|
-
|
|
49
|
-
// Try to find or extract sessionId from context
|
|
50
|
-
const sessionId = ctx.state.sessionId;
|
|
51
|
-
|
|
52
|
-
// Call touch with rate limiting
|
|
53
|
-
await sessionService.touch({
|
|
54
|
-
userId,
|
|
55
|
-
sessionId,
|
|
56
|
-
});
|
|
57
|
-
} catch (err) {
|
|
58
|
-
strapi.log.debug('[magic-sessionmanager] Error updating lastSeen:', err.message);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
};
|
package/server/src/register.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { createLogger } = require('./utils/logger');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Register hook
|
|
7
|
-
* Sessions relation is hidden from UI to keep User interface clean
|
|
8
|
-
* Sessions are accessed via the Session Manager plugin UI components
|
|
9
|
-
*/
|
|
10
|
-
module.exports = async ({ strapi }) => {
|
|
11
|
-
const log = createLogger(strapi);
|
|
12
|
-
|
|
13
|
-
log.info('[START] Plugin registration starting...');
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
// Get the user content type
|
|
17
|
-
const userCT = strapi.contentType('plugin::users-permissions.user');
|
|
18
|
-
|
|
19
|
-
if (!userCT) {
|
|
20
|
-
log.error('User content type not found');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// REMOVE sessions relation from User content type to keep UI clean
|
|
25
|
-
// Sessions are managed through SessionInfoPanel sidebar instead
|
|
26
|
-
if (userCT.attributes && userCT.attributes.sessions) {
|
|
27
|
-
delete userCT.attributes.sessions;
|
|
28
|
-
log.info('[SUCCESS] Removed sessions field from User content type');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
log.info('[SUCCESS] Plugin registered successfully');
|
|
32
|
-
|
|
33
|
-
} catch (err) {
|
|
34
|
-
log.error('[ERROR] Registration error:', err);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
type: 'admin',
|
|
5
|
-
routes: [
|
|
6
|
-
{
|
|
7
|
-
method: 'GET',
|
|
8
|
-
path: '/sessions',
|
|
9
|
-
handler: 'session.getAllSessionsAdmin',
|
|
10
|
-
config: {
|
|
11
|
-
policies: ['admin::isAuthenticatedAdmin'],
|
|
12
|
-
description: 'Get all sessions - active and inactive (admin)',
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
method: 'GET',
|
|
17
|
-
path: '/sessions/active',
|
|
18
|
-
handler: 'session.getActiveSessions',
|
|
19
|
-
config: {
|
|
20
|
-
policies: ['admin::isAuthenticatedAdmin'],
|
|
21
|
-
description: 'Get only active sessions (admin)',
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
method: 'GET',
|
|
26
|
-
path: '/user/:userId/sessions',
|
|
27
|
-
handler: 'session.getUserSessions',
|
|
28
|
-
config: {
|
|
29
|
-
policies: ['admin::isAuthenticatedAdmin'],
|
|
30
|
-
description: 'Get user sessions (admin)',
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
method: 'POST',
|
|
35
|
-
path: '/sessions/:sessionId/terminate',
|
|
36
|
-
handler: 'session.terminateSingleSession',
|
|
37
|
-
config: {
|
|
38
|
-
policies: ['admin::isAuthenticatedAdmin'],
|
|
39
|
-
description: 'Terminate a specific session (admin)',
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
method: 'DELETE',
|
|
44
|
-
path: '/sessions/:sessionId',
|
|
45
|
-
handler: 'session.deleteSession',
|
|
46
|
-
config: {
|
|
47
|
-
policies: ['admin::isAuthenticatedAdmin'],
|
|
48
|
-
description: 'Delete a single session permanently (admin)',
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
method: 'POST',
|
|
53
|
-
path: '/sessions/clean-inactive',
|
|
54
|
-
handler: 'session.cleanInactiveSessions',
|
|
55
|
-
config: {
|
|
56
|
-
policies: ['admin::isAuthenticatedAdmin'],
|
|
57
|
-
description: 'Delete all inactive sessions from database (admin)',
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
method: 'POST',
|
|
62
|
-
path: '/user/:userId/terminate-all',
|
|
63
|
-
handler: 'session.terminateAllUserSessions',
|
|
64
|
-
config: {
|
|
65
|
-
policies: ['admin::isAuthenticatedAdmin'],
|
|
66
|
-
description: 'Terminate all sessions for a user (admin)',
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
method: 'POST',
|
|
71
|
-
path: '/user/:userId/toggle-block',
|
|
72
|
-
handler: 'session.toggleUserBlock',
|
|
73
|
-
config: {
|
|
74
|
-
policies: ['admin::isAuthenticatedAdmin'],
|
|
75
|
-
description: 'Toggle user blocked status (admin)',
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
// License Management
|
|
79
|
-
{
|
|
80
|
-
method: 'GET',
|
|
81
|
-
path: '/license/status',
|
|
82
|
-
handler: 'license.getStatus',
|
|
83
|
-
config: {
|
|
84
|
-
policies: [],
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
method: 'POST',
|
|
89
|
-
path: '/license/auto-create',
|
|
90
|
-
handler: 'license.autoCreate',
|
|
91
|
-
config: {
|
|
92
|
-
policies: [],
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
method: 'POST',
|
|
97
|
-
path: '/license/create',
|
|
98
|
-
handler: 'license.createAndActivate',
|
|
99
|
-
config: {
|
|
100
|
-
policies: [],
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
method: 'POST',
|
|
105
|
-
path: '/license/ping',
|
|
106
|
-
handler: 'license.ping',
|
|
107
|
-
config: {
|
|
108
|
-
policies: [],
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
method: 'POST',
|
|
113
|
-
path: '/license/store-key',
|
|
114
|
-
handler: 'license.storeKey',
|
|
115
|
-
config: {
|
|
116
|
-
policies: [],
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
// Geolocation (Premium Feature)
|
|
120
|
-
{
|
|
121
|
-
method: 'GET',
|
|
122
|
-
path: '/geolocation/:ipAddress',
|
|
123
|
-
handler: 'session.getIpGeolocation',
|
|
124
|
-
config: {
|
|
125
|
-
policies: ['admin::isAuthenticatedAdmin'],
|
|
126
|
-
description: 'Get IP geolocation data (Premium feature)',
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
// Settings Management
|
|
130
|
-
{
|
|
131
|
-
method: 'GET',
|
|
132
|
-
path: '/settings',
|
|
133
|
-
handler: 'settings.getSettings',
|
|
134
|
-
config: {
|
|
135
|
-
policies: ['admin::isAuthenticatedAdmin'],
|
|
136
|
-
description: 'Get plugin settings',
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
method: 'PUT',
|
|
141
|
-
path: '/settings',
|
|
142
|
-
handler: 'settings.updateSettings',
|
|
143
|
-
config: {
|
|
144
|
-
policies: ['admin::isAuthenticatedAdmin'],
|
|
145
|
-
description: 'Update plugin settings',
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
],
|
|
149
|
-
};
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Content API Routes for Magic Session Manager
|
|
5
|
-
*
|
|
6
|
-
* SECURITY: All routes require authentication
|
|
7
|
-
* - User can only access their own sessions
|
|
8
|
-
* - Admin routes are in admin.js
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
module.exports = {
|
|
12
|
-
type: 'content-api',
|
|
13
|
-
routes: [
|
|
14
|
-
// ============================================================
|
|
15
|
-
// LOGOUT ENDPOINTS
|
|
16
|
-
// ============================================================
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
method: 'POST',
|
|
20
|
-
path: '/logout',
|
|
21
|
-
handler: 'session.logout',
|
|
22
|
-
config: {
|
|
23
|
-
auth: { strategies: ['users-permissions'] },
|
|
24
|
-
description: 'Logout current session (requires JWT)',
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
method: 'POST',
|
|
29
|
-
path: '/logout-all',
|
|
30
|
-
handler: 'session.logoutAll',
|
|
31
|
-
config: {
|
|
32
|
-
auth: { strategies: ['users-permissions'] },
|
|
33
|
-
description: 'Logout from all devices (requires JWT)',
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
// ============================================================
|
|
38
|
-
// SESSION QUERIES
|
|
39
|
-
// ============================================================
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
method: 'GET',
|
|
43
|
-
path: '/my-sessions',
|
|
44
|
-
handler: 'session.getOwnSessions',
|
|
45
|
-
config: {
|
|
46
|
-
auth: { strategies: ['users-permissions'] },
|
|
47
|
-
description: 'Get own sessions (automatically uses authenticated user)',
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
method: 'GET',
|
|
52
|
-
path: '/user/:userId/sessions',
|
|
53
|
-
handler: 'session.getUserSessions',
|
|
54
|
-
config: {
|
|
55
|
-
auth: { strategies: ['users-permissions'] },
|
|
56
|
-
description: 'Get sessions by userId (validates user can only see own sessions)',
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
};
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IP Geolocation Service
|
|
3
|
-
* Uses ipapi.co for accurate IP information
|
|
4
|
-
* Free tier: 30,000 requests/month
|
|
5
|
-
*
|
|
6
|
-
* Premium features:
|
|
7
|
-
* - Country, City, Region
|
|
8
|
-
* - Timezone
|
|
9
|
-
* - VPN/Proxy/Threat detection
|
|
10
|
-
* - Security scoring
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
module.exports = ({ strapi }) => ({
|
|
14
|
-
/**
|
|
15
|
-
* Get IP information from ipapi.co
|
|
16
|
-
* @param {string} ipAddress - IP to lookup
|
|
17
|
-
* @returns {Promise<Object>} Geolocation data
|
|
18
|
-
*/
|
|
19
|
-
async getIpInfo(ipAddress) {
|
|
20
|
-
try {
|
|
21
|
-
// Skip localhost/private IPs
|
|
22
|
-
if (!ipAddress || ipAddress === '127.0.0.1' || ipAddress === '::1' || ipAddress.startsWith('192.168.') || ipAddress.startsWith('10.')) {
|
|
23
|
-
return {
|
|
24
|
-
ip: ipAddress,
|
|
25
|
-
country: 'Local Network',
|
|
26
|
-
country_code: 'XX',
|
|
27
|
-
country_flag: '🏠',
|
|
28
|
-
city: 'Localhost',
|
|
29
|
-
region: 'Private Network',
|
|
30
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
31
|
-
latitude: null,
|
|
32
|
-
longitude: null,
|
|
33
|
-
isVpn: false,
|
|
34
|
-
isProxy: false,
|
|
35
|
-
isThreat: false,
|
|
36
|
-
securityScore: 100,
|
|
37
|
-
riskLevel: 'None',
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Call ipapi.co API
|
|
42
|
-
const response = await fetch(`https://ipapi.co/${ipAddress}/json/`, {
|
|
43
|
-
method: 'GET',
|
|
44
|
-
headers: {
|
|
45
|
-
'User-Agent': 'Strapi-Magic-SessionManager/1.0',
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (!response.ok) {
|
|
50
|
-
throw new Error(`API returned ${response.status}`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const data = await response.json();
|
|
54
|
-
|
|
55
|
-
// Check for rate limit
|
|
56
|
-
if (data.error) {
|
|
57
|
-
strapi.log.warn(`[magic-sessionmanager/geolocation] API Error: ${data.reason}`);
|
|
58
|
-
return this.getFallbackData(ipAddress);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Parse and return structured data
|
|
62
|
-
const result = {
|
|
63
|
-
ip: data.ip,
|
|
64
|
-
country: data.country_name,
|
|
65
|
-
country_code: data.country_code,
|
|
66
|
-
country_flag: this.getCountryFlag(data.country_code),
|
|
67
|
-
city: data.city,
|
|
68
|
-
region: data.region,
|
|
69
|
-
timezone: data.timezone,
|
|
70
|
-
latitude: data.latitude,
|
|
71
|
-
longitude: data.longitude,
|
|
72
|
-
postal: data.postal,
|
|
73
|
-
org: data.org,
|
|
74
|
-
asn: data.asn,
|
|
75
|
-
|
|
76
|
-
// Security features (available in ipapi.co response)
|
|
77
|
-
isVpn: data.threat?.is_vpn || false,
|
|
78
|
-
isProxy: data.threat?.is_proxy || false,
|
|
79
|
-
isThreat: data.threat?.is_threat || false,
|
|
80
|
-
isAnonymous: data.threat?.is_anonymous || false,
|
|
81
|
-
isTor: data.threat?.is_tor || false,
|
|
82
|
-
|
|
83
|
-
// Calculate security score (0-100, higher is safer)
|
|
84
|
-
securityScore: this.calculateSecurityScore({
|
|
85
|
-
isVpn: data.threat?.is_vpn,
|
|
86
|
-
isProxy: data.threat?.is_proxy,
|
|
87
|
-
isThreat: data.threat?.is_threat,
|
|
88
|
-
isAnonymous: data.threat?.is_anonymous,
|
|
89
|
-
isTor: data.threat?.is_tor,
|
|
90
|
-
}),
|
|
91
|
-
|
|
92
|
-
riskLevel: this.getRiskLevel({
|
|
93
|
-
isVpn: data.threat?.is_vpn,
|
|
94
|
-
isProxy: data.threat?.is_proxy,
|
|
95
|
-
isThreat: data.threat?.is_threat,
|
|
96
|
-
}),
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
strapi.log.debug(`[magic-sessionmanager/geolocation] IP ${ipAddress}: ${result.city}, ${result.country} (Score: ${result.securityScore})`);
|
|
100
|
-
|
|
101
|
-
return result;
|
|
102
|
-
} catch (error) {
|
|
103
|
-
strapi.log.error(`[magic-sessionmanager/geolocation] Error fetching IP info for ${ipAddress}:`, error.message);
|
|
104
|
-
return this.getFallbackData(ipAddress);
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Calculate security score based on threat indicators
|
|
110
|
-
*/
|
|
111
|
-
calculateSecurityScore({ isVpn, isProxy, isThreat, isAnonymous, isTor }) {
|
|
112
|
-
let score = 100;
|
|
113
|
-
|
|
114
|
-
if (isTor) score -= 50; // Tor = sehr verdächtig
|
|
115
|
-
if (isThreat) score -= 40; // Known threat
|
|
116
|
-
if (isVpn) score -= 20; // VPN = moderate risk
|
|
117
|
-
if (isProxy) score -= 15; // Proxy = low-moderate risk
|
|
118
|
-
if (isAnonymous) score -= 10; // Anonymous service
|
|
119
|
-
|
|
120
|
-
return Math.max(0, score);
|
|
121
|
-
},
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Get risk level based on indicators
|
|
125
|
-
*/
|
|
126
|
-
getRiskLevel({ isVpn, isProxy, isThreat }) {
|
|
127
|
-
if (isThreat) return 'High';
|
|
128
|
-
if (isVpn && isProxy) return 'Medium-High';
|
|
129
|
-
if (isVpn || isProxy) return 'Medium';
|
|
130
|
-
return 'Low';
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Get country flag emoji
|
|
135
|
-
* @param {string} countryCode - ISO 2-letter country code
|
|
136
|
-
* @returns {string} Flag emoji or empty string
|
|
137
|
-
*/
|
|
138
|
-
getCountryFlag(countryCode) {
|
|
139
|
-
if (!countryCode) return '';
|
|
140
|
-
|
|
141
|
-
// Convert country code to flag emoji
|
|
142
|
-
const codePoints = countryCode
|
|
143
|
-
.toUpperCase()
|
|
144
|
-
.split('')
|
|
145
|
-
.map(char => 127397 + char.charCodeAt());
|
|
146
|
-
|
|
147
|
-
return String.fromCodePoint(...codePoints);
|
|
148
|
-
},
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Fallback data when API fails
|
|
152
|
-
*/
|
|
153
|
-
getFallbackData(ipAddress) {
|
|
154
|
-
return {
|
|
155
|
-
ip: ipAddress,
|
|
156
|
-
country: 'Unknown',
|
|
157
|
-
country_code: 'XX',
|
|
158
|
-
country_flag: '[GEO]',
|
|
159
|
-
city: 'Unknown',
|
|
160
|
-
region: 'Unknown',
|
|
161
|
-
timezone: 'Unknown',
|
|
162
|
-
latitude: null,
|
|
163
|
-
longitude: null,
|
|
164
|
-
isVpn: false,
|
|
165
|
-
isProxy: false,
|
|
166
|
-
isThreat: false,
|
|
167
|
-
securityScore: 50,
|
|
168
|
-
riskLevel: 'Unknown',
|
|
169
|
-
};
|
|
170
|
-
},
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Batch lookup multiple IPs (for efficiency)
|
|
174
|
-
*/
|
|
175
|
-
async batchGetIpInfo(ipAddresses) {
|
|
176
|
-
const results = await Promise.all(
|
|
177
|
-
ipAddresses.map(ip => this.getIpInfo(ip))
|
|
178
|
-
);
|
|
179
|
-
return results;
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const session = require('./session');
|
|
4
|
-
const licenseGuard = require('./license-guard');
|
|
5
|
-
const geolocation = require('./geolocation');
|
|
6
|
-
const notifications = require('./notifications');
|
|
7
|
-
|
|
8
|
-
module.exports = {
|
|
9
|
-
session,
|
|
10
|
-
'license-guard': licenseGuard,
|
|
11
|
-
geolocation,
|
|
12
|
-
notifications,
|
|
13
|
-
};
|