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.
Files changed (61) hide show
  1. package/.eslintignore +1 -0
  2. package/.github/workflows/semantic-release.yml +27 -0
  3. package/README.md +318 -0
  4. package/admin/jsconfig.json +10 -0
  5. package/admin/src/components/Initializer/index.jsx +20 -0
  6. package/admin/src/components/Initializer.jsx +18 -0
  7. package/admin/src/components/LazyComponentLoader.jsx +27 -0
  8. package/admin/src/components/PluginIcon/index.jsx +6 -0
  9. package/admin/src/components/PluginIcon.jsx +5 -0
  10. package/admin/src/index.js +101 -0
  11. package/admin/src/pages/App/index.jsx +50 -0
  12. package/admin/src/pages/App.jsx +15 -0
  13. package/admin/src/pages/HomePage/index.js +2 -0
  14. package/admin/src/pages/HomePage/index.jsx +228 -0
  15. package/admin/src/pages/HomePage.jsx +655 -0
  16. package/admin/src/pages/Settings/index.jsx +1289 -0
  17. package/admin/src/pages/Settings/utils/api.js +13 -0
  18. package/admin/src/pages/Settings/utils/index.js +5 -0
  19. package/admin/src/pages/Settings/utils/layout.js +100 -0
  20. package/admin/src/pages/Settings/utils/schema.js +18 -0
  21. package/admin/src/pages/Tokens/index.jsx +2250 -0
  22. package/admin/src/permissions.js +7 -0
  23. package/admin/src/pluginId.js +3 -0
  24. package/admin/src/routes.js +40 -0
  25. package/admin/src/translations/de.json +188 -0
  26. package/admin/src/translations/en.json +189 -0
  27. package/admin/src/utils/getRequestURL.js +5 -0
  28. package/admin/src/utils/getTrad.js +17 -0
  29. package/admin/src/utils/getTranslation.js +3 -0
  30. package/admin/src/utils/index.js +4 -0
  31. package/build.js +75 -0
  32. package/package.json +59 -0
  33. package/server/bootstrap.js +127 -0
  34. package/server/controllers/settings.js +122 -0
  35. package/server/jsconfig.json +10 -0
  36. package/server/services/store.js +35 -0
  37. package/server/src/bootstrap.js +110 -0
  38. package/server/src/config/index.js +6 -0
  39. package/server/src/content-types/index.js +7 -0
  40. package/server/src/content-types/token/index.js +5 -0
  41. package/server/src/content-types/token/schema.json +47 -0
  42. package/server/src/controllers/auth.js +211 -0
  43. package/server/src/controllers/controller.js +213 -0
  44. package/server/src/controllers/index.js +16 -0
  45. package/server/src/controllers/jwt.js +261 -0
  46. package/server/src/controllers/tokens.js +654 -0
  47. package/server/src/destroy.js +5 -0
  48. package/server/src/index.js +33 -0
  49. package/server/src/middlewares/index.js +3 -0
  50. package/server/src/policies/index.js +3 -0
  51. package/server/src/register.js +5 -0
  52. package/server/src/routes/admin.js +160 -0
  53. package/server/src/routes/content-api.js +27 -0
  54. package/server/src/routes/index.js +9 -0
  55. package/server/src/services/index.js +11 -0
  56. package/server/src/services/magic-link.js +356 -0
  57. package/server/src/services/service.js +13 -0
  58. package/server/utils/index.js +14 -0
  59. package/strapi-admin.js +82 -0
  60. package/strapi-server.js +4 -0
  61. package/vite.config.js +36 -0
@@ -0,0 +1,654 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Hilfsfunktion zum Senden einer Standard-E-Mail ohne Email Designer
5
+ * @param {Object} user - Der Benutzer, an den die E-Mail gesendet wird
6
+ * @param {string} magicLink - Der vollständige Magic-Link-URL
7
+ * @param {Object} settings - Die Plugin-Einstellungen
8
+ * @param {string} token - Der generierte Token-Wert
9
+ */
10
+ const sendStandardEmail = async (user, magicLink, settings, token) => {
11
+ try {
12
+ // HTML- und Text-Versionen der Nachricht vorbereiten
13
+ let htmlMessage = settings.message_html || '';
14
+ let textMessage = settings.message_text || '';
15
+
16
+ // Ersetze Platzhalter in den Nachrichten
17
+ htmlMessage = htmlMessage
18
+ .replace(/{link}/g, magicLink)
19
+ .replace(/<%= URL %>/g, settings.confirmationUrl || strapi.config.server.url)
20
+ .replace(/<%= CODE %>/g, token)
21
+ .replace(/{username}/g, user.username || '')
22
+ .replace(/{email}/g, user.email || '');
23
+
24
+ textMessage = textMessage
25
+ .replace(/{link}/g, magicLink)
26
+ .replace(/<%= URL %>/g, settings.confirmationUrl || strapi.config.server.url)
27
+ .replace(/<%= CODE %>/g, token)
28
+ .replace(/{username}/g, user.username || '')
29
+ .replace(/{email}/g, user.email || '');
30
+
31
+ // Sende die Email
32
+ await strapi.plugins.email.services.email.send({
33
+ to: user.email,
34
+ from: settings.from_email ? `${settings.from_name} <${settings.from_email}>` : undefined,
35
+ replyTo: settings.response_email || undefined,
36
+ subject: settings.object,
37
+ html: htmlMessage,
38
+ text: textMessage,
39
+ });
40
+
41
+ strapi.log.info(`Standard Magic Link Email an ${user.email} gesendet`);
42
+ } catch (error) {
43
+ strapi.log.error('Fehler beim Senden der Standard-E-Mail:', error);
44
+ throw error;
45
+ }
46
+ };
47
+
48
+ /**
49
+ * Tokens controller
50
+ */
51
+
52
+ module.exports = {
53
+ /**
54
+ * Get all tokens
55
+ * @param {Object} ctx - The request context
56
+ */
57
+ async find(ctx) {
58
+ try {
59
+ // Query all tokens
60
+ const tokens = await strapi.db.query('plugin::magic-link.token').findMany({
61
+ orderBy: { createdAt: 'desc' },
62
+ });
63
+
64
+ // Berechne den Sicherheitswert
65
+ const securityScore = await this.calculateSecurityScore();
66
+
67
+ // Füge den Sicherheitswert als Metadaten hinzu
68
+ return {
69
+ data: tokens,
70
+ meta: {
71
+ securityScore
72
+ }
73
+ };
74
+ } catch (error) {
75
+ ctx.throw(500, error);
76
+ }
77
+ },
78
+
79
+ /**
80
+ * Create a new token
81
+ * @param {Object} ctx - The request context
82
+ */
83
+ async create(ctx) {
84
+ try {
85
+ const { email, send_email = true, context = {} } = ctx.request.body;
86
+
87
+ if (!email) {
88
+ return ctx.badRequest('Email is required');
89
+ }
90
+
91
+ // Überprüfe, ob die Plugin-Einstellungen das Erstellen neuer Benutzer erlauben
92
+ const pluginStore = strapi.store({
93
+ environment: '',
94
+ type: 'plugin',
95
+ name: 'magic-link',
96
+ });
97
+
98
+ const settings = await pluginStore.get({ key: 'settings' });
99
+
100
+ // Find the user
101
+ let user = await strapi.db.query('plugin::users-permissions.user').findOne({
102
+ where: { email },
103
+ select: ['id', 'username', 'email'],
104
+ });
105
+
106
+ // If user doesn't exist and create_new_user is not enabled, return error
107
+ if (!user && !settings.create_new_user) {
108
+ return ctx.badRequest('User does not exist and automatic user creation is disabled');
109
+ }
110
+
111
+ // If user doesn't exist, create a new one
112
+ if (!user && settings.create_new_user) {
113
+ // Generate a random username based on the email
114
+ const username = email.split('@')[0] + Math.floor(Math.random() * 10000);
115
+
116
+ // Create a random password
117
+ const password = Math.random().toString(36).substring(2, 15) +
118
+ Math.random().toString(36).substring(2, 15);
119
+
120
+ // Get the default role (authenticated)
121
+ const defaultRole = await strapi
122
+ .query('plugin::users-permissions.role')
123
+ .findOne({ where: { type: 'authenticated' } });
124
+
125
+ if (!defaultRole) {
126
+ return ctx.badRequest('Authenticated role not found');
127
+ }
128
+
129
+ // Create the user
130
+ user = await strapi.plugins['users-permissions'].services.user.add({
131
+ username,
132
+ email,
133
+ password,
134
+ provider: 'magic-link',
135
+ confirmed: true, // Auto-confirm the user
136
+ blocked: false,
137
+ role: defaultRole.id,
138
+ });
139
+
140
+ strapi.log.info(`Created new user with email: ${email}`);
141
+ }
142
+
143
+ // Generate random token (16 characters)
144
+ const tokenValue = Math.random().toString(36).substring(2, 10) +
145
+ Math.random().toString(36).substring(2, 10);
146
+
147
+ // Set expiration (24 hours from now)
148
+ const expiresAt = new Date();
149
+ expiresAt.setHours(expiresAt.getHours() + 24);
150
+
151
+ // Erweitere den Kontext mit Ablaufdatum und Benutzerinformationen
152
+ const enrichedContext = {
153
+ ...typeof context === 'string' ? JSON.parse(context) : context,
154
+ expires_at: expiresAt.toISOString(),
155
+ expiry_formatted: new Intl.DateTimeFormat('de-DE', {
156
+ year: 'numeric',
157
+ month: '2-digit',
158
+ day: '2-digit',
159
+ hour: '2-digit',
160
+ minute: '2-digit'
161
+ }).format(expiresAt),
162
+ user: {
163
+ id: user.id,
164
+ email: user.email,
165
+ username: user.username
166
+ }
167
+ };
168
+
169
+ // Create the token
170
+ const token = await strapi.db.query('plugin::magic-link.token').create({
171
+ data: {
172
+ token: tokenValue,
173
+ email: user.email,
174
+ user_id: user.id,
175
+ expires_at: expiresAt,
176
+ is_active: true,
177
+ ip_address: null, // Wird beim Verwenden gesetzt
178
+ user_agent: null, // Wird beim Verwenden gesetzt
179
+ context: enrichedContext, // Verwende den angereicherten Kontext
180
+ },
181
+ });
182
+
183
+ // Sende eine E-Mail mit dem Magic-Link nur wenn send_email true ist
184
+ if (send_email) {
185
+ try {
186
+ // Lade die Einstellungen aus dem Plugin-Store, falls nicht bereits geladen
187
+ if (!settings) {
188
+ settings = await pluginStore.get({ key: 'settings' });
189
+ }
190
+
191
+ if (settings && settings.enabled && settings.from_email && settings.object) {
192
+ // Erstelle die Magic-Link-URL
193
+ const baseUrl = settings.confirmationUrl || strapi.config.server.url;
194
+ const magicLink = `${baseUrl}?token=${tokenValue}`;
195
+
196
+ // Prüfen, ob wir Email Designer verwenden sollen
197
+ if (settings.use_email_designer && settings.email_designer_template_id) {
198
+ // Prüfen, ob das Email Designer Plugin verfügbar ist
199
+ if (strapi.plugin('email-designer-5')) {
200
+ // Konvertiere die Template-ID zu einer Zahl
201
+ const templateId = parseInt(settings.email_designer_template_id, 10);
202
+
203
+ // Stelle sicher, dass die Template-ID eine gültige Zahl ist
204
+ if (!isNaN(templateId) && templateId > 0) {
205
+ // Email mit dem Email Designer Plugin versenden
206
+ try {
207
+ // Die korrekte Email Designer 5 API verwenden
208
+ await strapi
209
+ .plugin('email-designer-5')
210
+ .service('email')
211
+ .sendTemplatedEmail(
212
+ {
213
+ to: user.email,
214
+ from: settings.from_email ? `${settings.from_name} <${settings.from_email}>` : undefined,
215
+ replyTo: settings.response_email || undefined,
216
+ },
217
+ {
218
+ templateReferenceId: templateId,
219
+ subject: settings.object, // Optional: überschreibt den Betreff der Vorlage
220
+ },
221
+ {
222
+ // Variablen für die Vorlage
223
+ user: {
224
+ username: user.username,
225
+ email: user.email,
226
+ },
227
+ magicLink: magicLink,
228
+ token: tokenValue,
229
+ expiresAt: expiresAt.toISOString(),
230
+ }
231
+ );
232
+
233
+ strapi.log.info(`Magic Link Email mit Email Designer (Template ID: ${templateId}) an ${user.email} gesendet`);
234
+ } catch (emailDesignerError) {
235
+ strapi.log.error('Fehler bei Email Designer:', emailDesignerError);
236
+ strapi.log.info('Fallback auf Standard-Email-Versand...');
237
+ await sendStandardEmail(user, magicLink, settings, tokenValue);
238
+ }
239
+ } else {
240
+ strapi.log.warn(`Ungültige Email Designer Template ID: '${settings.email_designer_template_id}', verwende Standard-Email`);
241
+ // Fallback auf Standard-Email wenn die Template-ID ungültig ist
242
+ await sendStandardEmail(user, magicLink, settings, tokenValue);
243
+ }
244
+ } else {
245
+ strapi.log.warn('Email Designer Plugin ist aktiviert, aber nicht installiert');
246
+
247
+ // Fallback auf Standard-Email
248
+ await sendStandardEmail(user, magicLink, settings, tokenValue);
249
+ }
250
+ } else {
251
+ // Standard-Email-Versand
252
+ await sendStandardEmail(user, magicLink, settings, tokenValue);
253
+ }
254
+
255
+ strapi.log.info(`Magic Link Token erstellt und E-Mail an ${user.email} gesendet`);
256
+ } else {
257
+ strapi.log.info(`Magic Link Token erstellt, aber keine E-Mail-Einstellungen konfiguriert`);
258
+ }
259
+ } catch (emailError) {
260
+ strapi.log.error('Fehler beim Senden der Magic Link Email:', emailError);
261
+ // Wir werfen keinen Fehler, da der Token trotzdem erstellt wurde
262
+ }
263
+ }
264
+
265
+ // Entferne den Token-Wert aus der Antwort aus Sicherheitsgründen
266
+ const { token: _, ...safeToken } = token;
267
+
268
+ return safeToken;
269
+ } catch (error) {
270
+ strapi.log.error('Error creating token:', error);
271
+ ctx.throw(500, error);
272
+ }
273
+ },
274
+
275
+ /**
276
+ * Block a token
277
+ * @param {Object} ctx - The request context
278
+ */
279
+ async block(ctx) {
280
+ try {
281
+ const { id } = ctx.params;
282
+
283
+ // Check if token exists
284
+ const token = await strapi.db.query('plugin::magic-link.token').findOne({
285
+ where: { id },
286
+ });
287
+
288
+ if (!token) {
289
+ return ctx.notFound('Token not found');
290
+ }
291
+
292
+ // Update the token to be inactive
293
+ const updatedToken = await strapi.db.query('plugin::magic-link.token').update({
294
+ where: { id },
295
+ data: {
296
+ is_active: false,
297
+ },
298
+ });
299
+
300
+ return updatedToken;
301
+ } catch (error) {
302
+ ctx.throw(500, error);
303
+ }
304
+ },
305
+
306
+ /**
307
+ * Delete a token
308
+ * @param {Object} ctx - The request context
309
+ */
310
+ async delete(ctx) {
311
+ try {
312
+ const { id } = ctx.params;
313
+
314
+ // Check if token exists
315
+ const token = await strapi.db.query('plugin::magic-link.token').findOne({
316
+ where: { id },
317
+ });
318
+
319
+ if (!token) {
320
+ return ctx.notFound('Token not found');
321
+ }
322
+
323
+ // Delete the token
324
+ await strapi.db.query('plugin::magic-link.token').delete({
325
+ where: { id },
326
+ });
327
+
328
+ return { success: true };
329
+ } catch (error) {
330
+ strapi.log.error('Error deleting token:', error);
331
+ ctx.throw(500, error);
332
+ }
333
+ },
334
+
335
+ /**
336
+ * Activate a token
337
+ * @param {Object} ctx - The request context
338
+ */
339
+ async activate(ctx) {
340
+ try {
341
+ const { id } = ctx.params;
342
+
343
+ // Prüfe, ob Token existiert
344
+ const token = await strapi.db.query('plugin::magic-link.token').findOne({
345
+ where: { id },
346
+ });
347
+
348
+ if (!token) {
349
+ return ctx.notFound('Token nicht gefunden');
350
+ }
351
+
352
+ // Aktualisiere den Token auf aktiv
353
+ const updatedToken = await strapi.db.query('plugin::magic-link.token').update({
354
+ where: { id },
355
+ data: {
356
+ is_active: true,
357
+ },
358
+ });
359
+
360
+ return updatedToken;
361
+ } catch (error) {
362
+ strapi.log.error('Fehler beim Aktivieren des Tokens:', error);
363
+ ctx.throw(500, error);
364
+ }
365
+ },
366
+
367
+ /**
368
+ * Extend token validity
369
+ * @param {Object} ctx - The request context
370
+ */
371
+ async extend(ctx) {
372
+ try {
373
+ const { id } = ctx.params;
374
+ const { days } = ctx.request.body;
375
+
376
+ // Prüfe, ob Token existiert
377
+ const token = await strapi.db.query('plugin::magic-link.token').findOne({
378
+ where: { id },
379
+ });
380
+
381
+ if (!token) {
382
+ return ctx.notFound('Token nicht gefunden');
383
+ }
384
+
385
+ // Verarbeite die Anzahl der Tage
386
+ const daysToAdd = parseInt(days) || 7; // Standard: 7 Tage
387
+
388
+ // Berechne das neue Ablaufdatum
389
+ let newExpiryDate;
390
+
391
+ // Wenn der Token bereits abgelaufen ist, vom aktuellen Datum ausgehen
392
+ if (new Date(token.expires_at) < new Date()) {
393
+ newExpiryDate = new Date();
394
+ } else {
395
+ // Sonst vom aktuellen Ablaufdatum ausgehen
396
+ newExpiryDate = new Date(token.expires_at);
397
+ }
398
+
399
+ // Tage hinzufügen
400
+ newExpiryDate.setDate(newExpiryDate.getDate() + daysToAdd);
401
+
402
+ // Aktualisiere den Token mit dem neuen Ablaufdatum
403
+ const updatedToken = await strapi.db.query('plugin::magic-link.token').update({
404
+ where: { id },
405
+ data: {
406
+ expires_at: newExpiryDate,
407
+ },
408
+ });
409
+
410
+ return updatedToken;
411
+ } catch (error) {
412
+ strapi.log.error('Fehler beim Verlängern des Tokens:', error);
413
+ ctx.throw(500, error);
414
+ }
415
+ },
416
+
417
+ /**
418
+ * Find a user by email
419
+ * @param {Object} ctx - The request context
420
+ */
421
+ async findUserByEmail(ctx) {
422
+ try {
423
+ const { email } = ctx.query;
424
+
425
+ if (!email) {
426
+ return ctx.badRequest('Email is required');
427
+ }
428
+
429
+ // Find the user
430
+ const user = await strapi.db.query('plugin::users-permissions.user').findOne({
431
+ where: { email },
432
+ select: ['id', 'username', 'email', 'confirmed', 'blocked', 'documentId', 'UUID'],
433
+ });
434
+
435
+ if (!user) {
436
+ return ctx.notFound('User not found');
437
+ }
438
+
439
+ return user;
440
+ } catch (error) {
441
+ ctx.throw(500, error);
442
+ }
443
+ },
444
+
445
+ /**
446
+ * Ban an IP address
447
+ * @param {Object} ctx - The request context
448
+ */
449
+ async banIP(ctx) {
450
+ try {
451
+ const { data } = ctx.request.body;
452
+
453
+ if (!data || !data.ip) {
454
+ return ctx.badRequest('IP address is required');
455
+ }
456
+
457
+ const ipAddress = data.ip;
458
+
459
+ // Get plugin store to save banned IPs
460
+ const pluginStore = strapi.store({
461
+ environment: '',
462
+ type: 'plugin',
463
+ name: 'magic-link',
464
+ });
465
+
466
+ // Get current banned IPs or initialize empty array
467
+ const bannedIPs = await pluginStore.get({ key: 'banned_ips' }) || { ips: [] };
468
+
469
+ // Add new IP to the list if not already present
470
+ if (!bannedIPs.ips.includes(ipAddress)) {
471
+ bannedIPs.ips.push(ipAddress);
472
+
473
+ // Save updated banned IPs list
474
+ await pluginStore.set({ key: 'banned_ips', value: bannedIPs });
475
+
476
+ // Deactivate all tokens associated with this IP
477
+ await strapi.db.query('plugin::magic-link.token').updateMany({
478
+ where: { ip_address: ipAddress },
479
+ data: { is_active: false },
480
+ });
481
+ }
482
+
483
+ return { success: true, message: `IP ${ipAddress} has been banned` };
484
+ } catch (error) {
485
+ strapi.log.error('Error banning IP:', error);
486
+ ctx.throw(500, error);
487
+ }
488
+ },
489
+
490
+ /**
491
+ * Ruft die Liste der gesperrten IP-Adressen ab
492
+ * @param {Object} ctx - The request context
493
+ */
494
+ async getBannedIPs(ctx) {
495
+ try {
496
+ // Get plugin store to retrieve banned IPs
497
+ const pluginStore = strapi.store({
498
+ environment: '',
499
+ type: 'plugin',
500
+ name: 'magic-link',
501
+ });
502
+
503
+ // Get current banned IPs or initialize empty array
504
+ const bannedIPs = await pluginStore.get({ key: 'banned_ips' }) || { ips: [] };
505
+
506
+ return bannedIPs;
507
+ } catch (error) {
508
+ strapi.log.error('Error fetching banned IPs:', error);
509
+ ctx.throw(500, error);
510
+ }
511
+ },
512
+
513
+ /**
514
+ * Entsperrt eine IP-Adresse
515
+ * @param {Object} ctx - The request context
516
+ */
517
+ async unbanIP(ctx) {
518
+ try {
519
+ const { data } = ctx.request.body;
520
+
521
+ if (!data || !data.ip) {
522
+ return ctx.badRequest('IP address is required');
523
+ }
524
+
525
+ const ipAddress = data.ip;
526
+
527
+ // Get plugin store
528
+ const pluginStore = strapi.store({
529
+ environment: '',
530
+ type: 'plugin',
531
+ name: 'magic-link',
532
+ });
533
+
534
+ // Get current banned IPs
535
+ const bannedIPs = await pluginStore.get({ key: 'banned_ips' }) || { ips: [] };
536
+
537
+ // Remove IP from the banned list
538
+ bannedIPs.ips = bannedIPs.ips.filter(ip => ip !== ipAddress);
539
+
540
+ // Save updated banned IPs list
541
+ await pluginStore.set({ key: 'banned_ips', value: bannedIPs });
542
+
543
+ return { success: true, message: `IP ${ipAddress} has been unbanned` };
544
+ } catch (error) {
545
+ strapi.log.error('Error unbanning IP:', error);
546
+ ctx.throw(500, error);
547
+ }
548
+ },
549
+
550
+ /**
551
+ * Berechnet einen Sicherheitswert basierend auf verschiedenen Plugin-Metriken
552
+ * @returns {Promise<number>} Sicherheitswert zwischen 0 und 100
553
+ */
554
+ async calculateSecurityScore() {
555
+ try {
556
+ let score = 0;
557
+ const maxScore = 100;
558
+
559
+ // Hol die Plugin-Einstellungen
560
+ const pluginStore = strapi.store({
561
+ environment: '',
562
+ type: 'plugin',
563
+ name: 'magic-link',
564
+ });
565
+
566
+ const settings = await pluginStore.get({ key: 'settings' }) || {};
567
+
568
+ // 1. Bewerte Token-Lebensdauer: max. 20 Punkte
569
+ // (Kürzere Lebensdauer ist sicherer)
570
+ let tokenLifetimePoints = 0;
571
+ const tokenLifetime = settings.token_lifetime || 24; // Standardwert: 24 Stunden
572
+ if (tokenLifetime <= 1) tokenLifetimePoints = 20; // 1 Stunde oder weniger
573
+ else if (tokenLifetime <= 6) tokenLifetimePoints = 15; // 6 Stunden oder weniger
574
+ else if (tokenLifetime <= 12) tokenLifetimePoints = 10; // 12 Stunden oder weniger
575
+ else if (tokenLifetime <= 24) tokenLifetimePoints = 5; // 24 Stunden oder weniger
576
+
577
+ // 2. Bewerte JWT-Lebensdauer: max. 20 Punkte
578
+ let jwtLifetimePoints = 0;
579
+ const jwtLifetime = settings.jwt_token_expires_in || '30d';
580
+ const jwtDays = jwtLifetime.endsWith('d') ? parseInt(jwtLifetime) :
581
+ jwtLifetime.endsWith('h') ? parseInt(jwtLifetime) / 24 : 30;
582
+
583
+ if (jwtDays <= 1) jwtLifetimePoints = 20; // 1 Tag oder weniger
584
+ else if (jwtDays <= 7) jwtLifetimePoints = 15; // 1 Woche oder weniger
585
+ else if (jwtDays <= 14) jwtLifetimePoints = 10; // 2 Wochen oder weniger
586
+ else if (jwtDays <= 30) jwtLifetimePoints = 5; // 1 Monat oder weniger
587
+
588
+ // 3. Bewerte Sicherheitseinstellungen: max. 30 Punkte
589
+ let configPoints = 0;
590
+
591
+ // Ist das Auto-Create-User Feature deaktiviert? (sicherer)
592
+ if (settings.create_new_user === false) configPoints += 10;
593
+
594
+ // Ist das Email-Send Feature aktiviert? (sicherer)
595
+ if (settings.enabled === true) configPoints += 5;
596
+
597
+ // Ist Remember Me deaktiviert? (sicherer)
598
+ if (settings.remember_me === false) configPoints += 5;
599
+
600
+ // Ist stays_valid deaktiviert? (sicherer)
601
+ if (settings.stays_valid === false) configPoints += 10;
602
+
603
+ // 4. Bewerte Token-Status: max. 15 Punkte
604
+ let tokenStatusPoints = 0;
605
+
606
+ // Hole alle Tokens
607
+ const tokens = await strapi.db.query('plugin::magic-link.token').findMany({});
608
+
609
+ // Berechne Verhältnis von aktiven zu inaktiven Tokens
610
+ const activeTokens = tokens.filter(token => token.is_active).length;
611
+ const inactiveTokens = tokens.filter(token => !token.is_active).length;
612
+ const tokenRatio = tokens.length > 0 ? inactiveTokens / tokens.length : 0;
613
+
614
+ // Bewerte basierend auf dem Verhältnis (mehr inaktive = höhere Sicherheit)
615
+ if (tokenRatio >= 0.8) tokenStatusPoints = 15;
616
+ else if (tokenRatio >= 0.6) tokenStatusPoints = 10;
617
+ else if (tokenRatio >= 0.4) tokenStatusPoints = 5;
618
+
619
+ // 5. Bewerte gebannte IPs: max. 15 Punkte
620
+ let bannedIPsPoints = 0;
621
+
622
+ // Hole gebannte IPs
623
+ const bannedIPsData = await pluginStore.get({ key: 'banned_ips' }) || { ips: [] };
624
+ const bannedIPsCount = bannedIPsData.ips?.length || 0;
625
+
626
+ // Bewerte basierend auf Anzahl der gebannten IPs
627
+ if (bannedIPsCount >= 10) bannedIPsPoints = 15;
628
+ else if (bannedIPsCount >= 5) bannedIPsPoints = 10;
629
+ else if (bannedIPsCount >= 1) bannedIPsPoints = 5;
630
+
631
+ // Gesamtpunktzahl berechnen
632
+ score = tokenLifetimePoints + jwtLifetimePoints + configPoints + tokenStatusPoints + bannedIPsPoints;
633
+
634
+ // Stelle sicher, dass der Score im Bereich 0-100 liegt
635
+ return Math.min(Math.max(score, 0), maxScore);
636
+ } catch (error) {
637
+ console.error('Fehler bei der Berechnung des Sicherheitswerts:', error);
638
+ return 85; // Fallback auf den Standardwert
639
+ }
640
+ },
641
+
642
+ /**
643
+ * Get the security score
644
+ * @param {Object} ctx - The request context
645
+ */
646
+ async getSecurityScore(ctx) {
647
+ try {
648
+ const score = await this.calculateSecurityScore();
649
+ return { score };
650
+ } catch (error) {
651
+ ctx.throw(500, error);
652
+ }
653
+ },
654
+ };
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ module.exports = ({ strapi }) => {
4
+ // Cleanup logic when the plugin is disabled
5
+ };
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Application methods
5
+ */
6
+ const bootstrap = require('./bootstrap');
7
+ const destroy = require('./destroy');
8
+ const register = require('./register');
9
+
10
+ /**
11
+ * Plugin server methods
12
+ */
13
+ const config = require('./config');
14
+ const contentTypes = require('./content-types');
15
+ const controllers = require('./controllers');
16
+ const middlewares = require('./middlewares');
17
+ const policies = require('./policies');
18
+ const routes = require('./routes');
19
+ const services = require('./services');
20
+
21
+ module.exports = {
22
+ bootstrap,
23
+ destroy,
24
+ register,
25
+
26
+ config,
27
+ controllers,
28
+ contentTypes,
29
+ middlewares,
30
+ policies,
31
+ routes,
32
+ services,
33
+ };
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ module.exports = {};
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ module.exports = {};
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ module.exports = ({ strapi }) => {
4
+ // Registration logic
5
+ };