strapi-plugin-magic-link-v5 4.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/.eslintignore +1 -0
- package/.github/workflows/semantic-release.yml +27 -0
- package/README.md +318 -0
- package/admin/jsconfig.json +10 -0
- package/admin/src/components/Initializer/index.jsx +20 -0
- package/admin/src/components/Initializer.jsx +18 -0
- package/admin/src/components/LazyComponentLoader.jsx +27 -0
- package/admin/src/components/PluginIcon/index.jsx +6 -0
- package/admin/src/components/PluginIcon.jsx +5 -0
- package/admin/src/index.js +101 -0
- package/admin/src/pages/App/index.jsx +50 -0
- package/admin/src/pages/App.jsx +15 -0
- package/admin/src/pages/HomePage/index.js +2 -0
- package/admin/src/pages/HomePage/index.jsx +228 -0
- package/admin/src/pages/HomePage.jsx +655 -0
- package/admin/src/pages/Settings/index.jsx +1289 -0
- package/admin/src/pages/Settings/utils/api.js +13 -0
- package/admin/src/pages/Settings/utils/index.js +5 -0
- package/admin/src/pages/Settings/utils/layout.js +100 -0
- package/admin/src/pages/Settings/utils/schema.js +18 -0
- package/admin/src/pages/Tokens/index.jsx +2250 -0
- package/admin/src/permissions.js +7 -0
- package/admin/src/pluginId.js +3 -0
- package/admin/src/routes.js +40 -0
- package/admin/src/translations/de.json +188 -0
- package/admin/src/translations/en.json +189 -0
- package/admin/src/utils/getRequestURL.js +5 -0
- package/admin/src/utils/getTrad.js +17 -0
- package/admin/src/utils/getTranslation.js +3 -0
- package/admin/src/utils/index.js +4 -0
- package/build.js +75 -0
- package/package.json +59 -0
- package/server/bootstrap.js +127 -0
- package/server/controllers/settings.js +122 -0
- package/server/jsconfig.json +10 -0
- package/server/services/store.js +35 -0
- package/server/src/bootstrap.js +110 -0
- package/server/src/config/index.js +6 -0
- package/server/src/content-types/index.js +7 -0
- package/server/src/content-types/token/index.js +5 -0
- package/server/src/content-types/token/schema.json +47 -0
- package/server/src/controllers/auth.js +211 -0
- package/server/src/controllers/controller.js +213 -0
- package/server/src/controllers/index.js +16 -0
- package/server/src/controllers/jwt.js +261 -0
- package/server/src/controllers/tokens.js +654 -0
- package/server/src/destroy.js +5 -0
- package/server/src/index.js +33 -0
- package/server/src/middlewares/index.js +3 -0
- package/server/src/policies/index.js +3 -0
- package/server/src/register.js +5 -0
- package/server/src/routes/admin.js +160 -0
- package/server/src/routes/content-api.js +27 -0
- package/server/src/routes/index.js +9 -0
- package/server/src/services/index.js +11 -0
- package/server/src/services/magic-link.js +356 -0
- package/server/src/services/service.js +13 -0
- package/server/utils/index.js +14 -0
- package/strapi-admin.js +82 -0
- package/strapi-server.js +4 -0
- package/vite.config.js +36 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
async getSettings(ctx) {
|
|
5
|
+
try {
|
|
6
|
+
const pluginStore = strapi.store({
|
|
7
|
+
environment: '',
|
|
8
|
+
type: 'plugin',
|
|
9
|
+
name: 'magic-link',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const settings = await pluginStore.get({ key: 'settings' });
|
|
13
|
+
|
|
14
|
+
// Stelle sicher, dass alle Boolean-Werte korrekt formatiert sind
|
|
15
|
+
const processedSettings = { ...settings };
|
|
16
|
+
|
|
17
|
+
// Entferne verschachtelte settings-Objekte, falls vorhanden
|
|
18
|
+
if (processedSettings && processedSettings.settings) {
|
|
19
|
+
delete processedSettings.settings;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Verarbeite alle Eigenschaften, die als Boolean behandelt werden sollten
|
|
23
|
+
const booleanFields = [
|
|
24
|
+
'enabled', 'createUserIfNotExists', 'stays_valid', 'verify_email',
|
|
25
|
+
'welcome_email', 'use_jwt_token', 'allow_magic_links_on_public_registration',
|
|
26
|
+
'store_login_info', 'use_email_designer'
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
booleanFields.forEach(field => {
|
|
30
|
+
if (field in processedSettings) {
|
|
31
|
+
// Konvertiere zu echten Boolean-Werten
|
|
32
|
+
processedSettings[field] = !!processedSettings[field];
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Ensure store_login_info has a value
|
|
37
|
+
if (processedSettings.store_login_info === undefined) {
|
|
38
|
+
processedSettings.store_login_info = true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Ensure email designer settings have values
|
|
42
|
+
if (processedSettings.use_email_designer === undefined) {
|
|
43
|
+
processedSettings.use_email_designer = false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (processedSettings.email_designer_template_id === undefined) {
|
|
47
|
+
processedSettings.email_designer_template_id = '';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Korrigiere den Login-Pfad, wenn er den alten Wert hat
|
|
51
|
+
if (processedSettings.login_path === '/passwordless-login' || processedSettings.login_path === '/api/magic-link/login') {
|
|
52
|
+
processedSettings.login_path = '/magic-link/login';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check if Email Designer plugin is installed
|
|
56
|
+
const isEmailDesignerInstalled = !!strapi.plugin('email-designer-5');
|
|
57
|
+
|
|
58
|
+
ctx.send({
|
|
59
|
+
settings: processedSettings,
|
|
60
|
+
emailDesignerInstalled: isEmailDesignerInstalled
|
|
61
|
+
});
|
|
62
|
+
} catch (error) {
|
|
63
|
+
ctx.throw(500, error);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
async updateSettings(ctx) {
|
|
68
|
+
const { body } = ctx.request;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Stelle sicher, dass alle Boolean-Werte korrekt formatiert sind
|
|
72
|
+
const processedBody = { ...body };
|
|
73
|
+
|
|
74
|
+
// Entferne verschachtelte settings-Objekte, falls vorhanden
|
|
75
|
+
if (processedBody && processedBody.settings) {
|
|
76
|
+
delete processedBody.settings;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Verarbeite alle Eigenschaften, die als Boolean behandelt werden sollten
|
|
80
|
+
const booleanFields = [
|
|
81
|
+
'enabled', 'createUserIfNotExists', 'stays_valid', 'verify_email',
|
|
82
|
+
'welcome_email', 'use_jwt_token', 'allow_magic_links_on_public_registration',
|
|
83
|
+
'store_login_info', 'use_email_designer'
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
booleanFields.forEach(field => {
|
|
87
|
+
if (field in processedBody) {
|
|
88
|
+
// Konvertiere verschiedene Formate zu echten Boolean-Werten
|
|
89
|
+
if (typeof processedBody[field] === 'string') {
|
|
90
|
+
processedBody[field] = processedBody[field] === 'true';
|
|
91
|
+
} else if (typeof processedBody[field] === 'object' && processedBody[field]?.type === 'boolean') {
|
|
92
|
+
processedBody[field] = !!processedBody[field].value;
|
|
93
|
+
} else {
|
|
94
|
+
processedBody[field] = !!processedBody[field];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Ensure store_login_info is included in the stored settings
|
|
100
|
+
if (processedBody.store_login_info === undefined) {
|
|
101
|
+
processedBody.store_login_info = true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const pluginStore = strapi.store({
|
|
105
|
+
environment: '',
|
|
106
|
+
type: 'plugin',
|
|
107
|
+
name: 'magic-link',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await pluginStore.set({ key: 'settings', value: processedBody });
|
|
111
|
+
ctx.send({ settings: processedBody });
|
|
112
|
+
} catch (error) {
|
|
113
|
+
ctx.throw(500, error);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
async index(ctx) {
|
|
118
|
+
ctx.body = { message: 'Welcome to Magic Link plugin!' };
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Setzt alle Magic Link Daten und Einstellungen zurück
|
|
123
|
+
* @param {Object} ctx - Context
|
|
124
|
+
*/
|
|
125
|
+
async resetData(ctx) {
|
|
126
|
+
try {
|
|
127
|
+
// Plugin Store für die Einstellungen
|
|
128
|
+
const pluginStore = strapi.store({
|
|
129
|
+
environment: '',
|
|
130
|
+
type: 'plugin',
|
|
131
|
+
name: 'magic-link',
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Standardeinstellungen definieren
|
|
135
|
+
const defaultSettings = {
|
|
136
|
+
enabled: true,
|
|
137
|
+
createUserIfNotExists: false,
|
|
138
|
+
stays_valid: false,
|
|
139
|
+
expire_period: 3600,
|
|
140
|
+
token_length: 20,
|
|
141
|
+
max_login_attempts: 5,
|
|
142
|
+
login_path: '/magic-link/login',
|
|
143
|
+
confirmationUrl: process.env.FRONTEND_URL || 'http://localhost:3000',
|
|
144
|
+
store_login_info: true,
|
|
145
|
+
default_role: 'authenticated',
|
|
146
|
+
object: 'Your Magic Link for Login',
|
|
147
|
+
from_name: 'Magic Link Service',
|
|
148
|
+
from_email: 'noreply@example.com',
|
|
149
|
+
message_html: `<!DOCTYPE html>
|
|
150
|
+
<html>
|
|
151
|
+
<head>
|
|
152
|
+
<meta charset="utf-8">
|
|
153
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
154
|
+
<title>Magic Link Login</title>
|
|
155
|
+
</head>
|
|
156
|
+
<body>
|
|
157
|
+
<h2>Magic Link Login</h2>
|
|
158
|
+
<p>Click the link below to log in:</p>
|
|
159
|
+
<p><a href="<%= URL %>?loginToken=<%= CODE %>">Log in to your account</a></p>
|
|
160
|
+
<p>Or use this URL: <%= URL %>?loginToken=<%= CODE %></p>
|
|
161
|
+
<p>This link will expire in 1 hour.</p>
|
|
162
|
+
</body>
|
|
163
|
+
</html>`,
|
|
164
|
+
message_text: `Hello,
|
|
165
|
+
|
|
166
|
+
Click the link below to log in:
|
|
167
|
+
|
|
168
|
+
<%= URL %>?loginToken=<%= CODE %>
|
|
169
|
+
|
|
170
|
+
This link will expire in 1 hour.`,
|
|
171
|
+
jwt_token_expires_in: '30d'
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Einstellungen zurücksetzen
|
|
175
|
+
await pluginStore.set({ key: 'settings', value: defaultSettings });
|
|
176
|
+
|
|
177
|
+
// Alle Magic Link Tokens löschen
|
|
178
|
+
await strapi.db.query('plugin::magic-link.token').deleteMany({
|
|
179
|
+
where: {},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// JWT Sessions löschen
|
|
183
|
+
try {
|
|
184
|
+
await pluginStore.delete({ key: 'jwt_sessions' });
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error('Fehler beim Löschen der JWT Sessions:', error);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Gebannte IPs zurücksetzen
|
|
190
|
+
try {
|
|
191
|
+
await pluginStore.set({ key: 'banned_ips', value: { ips: [] } });
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error('Fehler beim Zurücksetzen der gebannten IPs:', error);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Gesperrte JWT-Tokens zurücksetzen
|
|
197
|
+
try {
|
|
198
|
+
await pluginStore.set({ key: 'blocked_jwt_tokens', value: { tokens: [] } });
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error('Fehler beim Zurücksetzen der gesperrten JWT-Tokens:', error);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Erfolgreiche Antwort senden
|
|
204
|
+
ctx.send({
|
|
205
|
+
success: true,
|
|
206
|
+
message: 'Alle Magic Link Daten wurden zurückgesetzt.',
|
|
207
|
+
});
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error('Fehler beim Zurücksetzen der Magic Link Daten:', error);
|
|
210
|
+
ctx.throw(500, error);
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Controllers
|
|
5
|
+
*/
|
|
6
|
+
const controller = require('./controller');
|
|
7
|
+
const auth = require('./auth');
|
|
8
|
+
const tokens = require('./tokens');
|
|
9
|
+
const jwt = require('./jwt');
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
controller,
|
|
13
|
+
auth,
|
|
14
|
+
tokens,
|
|
15
|
+
jwt,
|
|
16
|
+
};
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* JWT controller für die Verwaltung von JWT-Tokens
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
/**
|
|
9
|
+
* Alle aktiven JWT-Sessions abrufen
|
|
10
|
+
* @param {Object} ctx - Koa-Kontext
|
|
11
|
+
*/
|
|
12
|
+
async getSessions(ctx) {
|
|
13
|
+
try {
|
|
14
|
+
// Aus dem Plugin-Store auslesen
|
|
15
|
+
const pluginStore = strapi.store({
|
|
16
|
+
environment: '',
|
|
17
|
+
type: 'plugin',
|
|
18
|
+
name: 'magic-link',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const storedData = await pluginStore.get({ key: 'jwt_sessions' }) || { sessions: [] };
|
|
22
|
+
const jwtSessions = storedData.sessions || [];
|
|
23
|
+
|
|
24
|
+
// Aktuelles Datum für Ablaufprüfung
|
|
25
|
+
const now = new Date();
|
|
26
|
+
|
|
27
|
+
// Aufbereitung der Daten für die Anzeige
|
|
28
|
+
const sessions = jwtSessions.map(session => ({
|
|
29
|
+
id: session.id,
|
|
30
|
+
userId: session.userId,
|
|
31
|
+
username: session.username,
|
|
32
|
+
email: session.userEmail,
|
|
33
|
+
token: session.jwtToken ? session.jwtToken.substring(0, 30) + '...' : 'N/A', // Nur Teile des Tokens anzeigen
|
|
34
|
+
createdAt: session.createdAt,
|
|
35
|
+
expiresAt: session.expiresAt,
|
|
36
|
+
ipAddress: session.ipAddress,
|
|
37
|
+
userAgent: session.userAgent || 'Unbekannt',
|
|
38
|
+
source: session.source || 'Magic Link Login',
|
|
39
|
+
revoked: session.isRevoked,
|
|
40
|
+
isExpired: new Date(session.expiresAt) < now
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
ctx.send(sessions);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Error fetching JWT sessions:', error);
|
|
46
|
+
ctx.throw(500, error);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Ein JWT-Token sperren
|
|
52
|
+
* @param {Object} ctx - Koa-Kontext
|
|
53
|
+
*/
|
|
54
|
+
async revokeToken(ctx) {
|
|
55
|
+
try {
|
|
56
|
+
const { token, sessionId } = ctx.request.body;
|
|
57
|
+
|
|
58
|
+
// Hole aktuelle JWT-Sessions
|
|
59
|
+
const pluginStore = strapi.store({
|
|
60
|
+
environment: '',
|
|
61
|
+
type: 'plugin',
|
|
62
|
+
name: 'magic-link',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const storedData = await pluginStore.get({ key: 'jwt_sessions' }) || { sessions: [] };
|
|
66
|
+
let jwtSessions = storedData.sessions || [];
|
|
67
|
+
|
|
68
|
+
// Möglichkeit 1: Sperren über sessionId
|
|
69
|
+
if (sessionId) {
|
|
70
|
+
// Finde die Session anhand der ID
|
|
71
|
+
const sessionIndex = jwtSessions.findIndex(s => s.id === sessionId);
|
|
72
|
+
|
|
73
|
+
if (sessionIndex === -1) {
|
|
74
|
+
return ctx.badRequest('Session not found');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Markiere Session als gesperrt
|
|
78
|
+
jwtSessions[sessionIndex].isRevoked = true;
|
|
79
|
+
jwtSessions[sessionIndex].revokedAt = new Date().toISOString();
|
|
80
|
+
jwtSessions[sessionIndex].revokeReason = 'Manually revoked from admin UI';
|
|
81
|
+
|
|
82
|
+
// Sperrung auch in der Sperrliste erfassen
|
|
83
|
+
if (jwtSessions[sessionIndex].jwtToken) {
|
|
84
|
+
const { magicLink } = strapi.plugins['magic-link'].services;
|
|
85
|
+
await magicLink.blockJwtToken(
|
|
86
|
+
jwtSessions[sessionIndex].jwtToken,
|
|
87
|
+
jwtSessions[sessionIndex].userId,
|
|
88
|
+
'Manually revoked from admin UI'
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Speichere aktualisierte Liste
|
|
93
|
+
await pluginStore.set({ key: 'jwt_sessions', value: { sessions: jwtSessions } });
|
|
94
|
+
|
|
95
|
+
return ctx.send({
|
|
96
|
+
success: true,
|
|
97
|
+
message: 'JWT session revoked successfully'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Möglichkeit 2: Sperren über Token (Legacy)
|
|
102
|
+
if (token) {
|
|
103
|
+
const { magicLink } = strapi.plugins['magic-link'].services;
|
|
104
|
+
const result = await magicLink.blockJwtToken(
|
|
105
|
+
token,
|
|
106
|
+
ctx.request.body.userId || 'unknown',
|
|
107
|
+
'Manually revoked from admin UI'
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Auch alle Sessions mit diesem Token sperren
|
|
111
|
+
const tokenPrefix = token.substring(0, 30);
|
|
112
|
+
|
|
113
|
+
jwtSessions = jwtSessions.map(session => {
|
|
114
|
+
// Prüfe, ob der Token-Anfang übereinstimmt
|
|
115
|
+
if (session.jwtToken && session.jwtToken.startsWith(tokenPrefix)) {
|
|
116
|
+
return {
|
|
117
|
+
...session,
|
|
118
|
+
isRevoked: true,
|
|
119
|
+
revokedAt: new Date().toISOString(),
|
|
120
|
+
revokeReason: 'Manually revoked from admin UI via token'
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return session;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Speichere aktualisierte Liste
|
|
127
|
+
await pluginStore.set({ key: 'jwt_sessions', value: { sessions: jwtSessions } });
|
|
128
|
+
|
|
129
|
+
return ctx.send({
|
|
130
|
+
success: true,
|
|
131
|
+
message: "Die Session wurde erfolgreich gesperrt."
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return ctx.badRequest('Token or sessionId is required');
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('Error revoking JWT token:', error);
|
|
138
|
+
ctx.throw(500, error);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Ein JWT-Token entsperren
|
|
144
|
+
* @param {Object} ctx - Koa-Kontext
|
|
145
|
+
*/
|
|
146
|
+
async unrevokeToken(ctx) {
|
|
147
|
+
try {
|
|
148
|
+
const { sessionId, userId } = ctx.request.body;
|
|
149
|
+
|
|
150
|
+
if (!sessionId) {
|
|
151
|
+
return ctx.badRequest('Session ID is required');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Hole aktuelle JWT-Sessions
|
|
155
|
+
const pluginStore = strapi.store({
|
|
156
|
+
environment: '',
|
|
157
|
+
type: 'plugin',
|
|
158
|
+
name: 'magic-link',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const storedData = await pluginStore.get({ key: 'jwt_sessions' }) || { sessions: [] };
|
|
162
|
+
let jwtSessions = storedData.sessions || [];
|
|
163
|
+
|
|
164
|
+
// Finde die Session anhand der ID
|
|
165
|
+
const sessionIndex = jwtSessions.findIndex(s => s.id === sessionId);
|
|
166
|
+
|
|
167
|
+
if (sessionIndex === -1) {
|
|
168
|
+
return ctx.badRequest('Session not found');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Prüfe, ob die Session abgelaufen ist
|
|
172
|
+
const expiresAt = new Date(jwtSessions[sessionIndex].expiresAt);
|
|
173
|
+
const now = new Date();
|
|
174
|
+
|
|
175
|
+
if (expiresAt < now) {
|
|
176
|
+
return ctx.badRequest('Cannot unrevoke an expired session');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Session entsperren
|
|
180
|
+
jwtSessions[sessionIndex].isRevoked = false;
|
|
181
|
+
jwtSessions[sessionIndex].revokedAt = null;
|
|
182
|
+
jwtSessions[sessionIndex].revokeReason = null;
|
|
183
|
+
|
|
184
|
+
// Token aus der Sperrliste entfernen, falls vorhanden
|
|
185
|
+
if (jwtSessions[sessionIndex].jwtToken) {
|
|
186
|
+
const { magicLink } = strapi.plugins['magic-link'].services;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// JWT von der Blacklist entfernen
|
|
190
|
+
await magicLink.unblockJwtToken(
|
|
191
|
+
jwtSessions[sessionIndex].jwtToken,
|
|
192
|
+
userId || jwtSessions[sessionIndex].userId
|
|
193
|
+
);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.warn('Warning: Could not unblock JWT token from blacklist:', error);
|
|
196
|
+
// Fehler ignorieren, da der Token möglicherweise nicht in der Blacklist ist
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Speichere aktualisierte Liste
|
|
201
|
+
await pluginStore.set({ key: 'jwt_sessions', value: { sessions: jwtSessions } });
|
|
202
|
+
|
|
203
|
+
return ctx.send({
|
|
204
|
+
success: true,
|
|
205
|
+
message: 'JWT session unrevoked successfully'
|
|
206
|
+
});
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error('Error unrevoking JWT token:', error);
|
|
209
|
+
ctx.throw(500, error);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Alle abgelaufenen und gesperrten Sessions aufräumen
|
|
215
|
+
* @param {Object} ctx - Koa-Kontext
|
|
216
|
+
*/
|
|
217
|
+
async cleanupSessions(ctx) {
|
|
218
|
+
try {
|
|
219
|
+
const now = new Date();
|
|
220
|
+
|
|
221
|
+
// Hole aktuelle JWT-Sessions
|
|
222
|
+
const pluginStore = strapi.store({
|
|
223
|
+
environment: '',
|
|
224
|
+
type: 'plugin',
|
|
225
|
+
name: 'magic-link',
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const storedData = await pluginStore.get({ key: 'jwt_sessions' }) || { sessions: [] };
|
|
229
|
+
let jwtSessions = storedData.sessions || [];
|
|
230
|
+
|
|
231
|
+
// Identifiziere abgelaufene Sessions
|
|
232
|
+
const expiredSessionIds = [];
|
|
233
|
+
|
|
234
|
+
jwtSessions = jwtSessions.map(session => {
|
|
235
|
+
// Prüfe, ob abgelaufen und nicht bereits gesperrt
|
|
236
|
+
if (new Date(session.expiresAt) < now && !session.isRevoked) {
|
|
237
|
+
expiredSessionIds.push(session.id);
|
|
238
|
+
return {
|
|
239
|
+
...session,
|
|
240
|
+
isRevoked: true,
|
|
241
|
+
revokedAt: now.toISOString(),
|
|
242
|
+
revokeReason: 'Automatically expired'
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return session;
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Speichere aktualisierte Liste
|
|
249
|
+
await pluginStore.set({ key: 'jwt_sessions', value: { sessions: jwtSessions } });
|
|
250
|
+
|
|
251
|
+
ctx.send({
|
|
252
|
+
success: true,
|
|
253
|
+
count: expiredSessionIds.length,
|
|
254
|
+
message: `${expiredSessionIds.length} abgelaufene Sessions wurden aufgeräumt.`
|
|
255
|
+
});
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error('Error cleaning up sessions:', error);
|
|
258
|
+
ctx.throw(500, error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|