strapi-plugin-magic-link-v5 5.5.6 → 5.5.8

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.5.6",
2
+ "version": "5.5.8",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "plugin",
@@ -103,10 +103,10 @@
103
103
  "kind": "plugin",
104
104
  "name": "magic-link",
105
105
  "displayName": "Magic Link Authentication",
106
- "description": "This plugin provides passwordless authentication via magic links sent to email"
106
+ "description": "Passwordless authentication for Strapi v5 via magic links, email OTP and TOTP-based MFA. Drop-in replacement for password login that works with the Users & Permissions plugin."
107
107
  },
108
108
  "name": "strapi-plugin-magic-link-v5",
109
- "description": "This plugin provides passwordless authentication via magic links sent to email",
109
+ "description": "Passwordless authentication for Strapi v5 via magic links, email OTP and TOTP-based MFA. Drop-in replacement for password login.",
110
110
  "license": "MIT",
111
111
  "private": false,
112
112
  "author": "Schero A. <schero@magicdx.dev>",
@@ -1,646 +0,0 @@
1
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import { useState, createContext, useContext, useEffect } from "react";
3
- import { IntlProvider } from "react-intl";
4
- import enTranslations from "./en-gXXgGDV1.mjs";
5
- import deTranslations from "./de-CwhYs3O3.mjs";
6
- import frTranslations from "./fr-BQn1OHOR.mjs";
7
- import esTranslations from "./es-DG5gW3RZ.mjs";
8
- import ptTranslations from "./pt-trziAOzT.mjs";
9
- import { Box, Loader, Typography, Flex, TextInput, Button } from "@strapi/design-system";
10
- import { useFetchClient, useNotification } from "@strapi/strapi/admin";
11
- import { Cross, Key, Check } from "@strapi/icons";
12
- import { useNavigate } from "react-router-dom";
13
- import styled, { keyframes } from "styled-components";
14
- const LanguageContext = createContext();
15
- const usePluginLanguage = () => {
16
- const context = useContext(LanguageContext);
17
- if (!context) {
18
- throw new Error("usePluginLanguage must be used within LanguageProvider");
19
- }
20
- return context;
21
- };
22
- const translations = {
23
- en: enTranslations,
24
- de: deTranslations,
25
- fr: frTranslations,
26
- es: esTranslations,
27
- pt: ptTranslations
28
- };
29
- const LanguageProvider = ({ children }) => {
30
- const [currentLanguage, setCurrentLanguage] = useState(() => {
31
- return localStorage.getItem("magic-link-language") || "en";
32
- });
33
- const changeLanguage = (newLang) => {
34
- setCurrentLanguage(newLang);
35
- localStorage.setItem("magic-link-language", newLang);
36
- };
37
- const value = {
38
- language: currentLanguage,
39
- changeLanguage,
40
- t: (key) => {
41
- const messages = translations[currentLanguage] || translations.en;
42
- return messages[key] || key;
43
- }
44
- };
45
- return /* @__PURE__ */ jsx(LanguageContext.Provider, { value, children: /* @__PURE__ */ jsx(
46
- IntlProvider,
47
- {
48
- locale: currentLanguage,
49
- messages: translations[currentLanguage],
50
- defaultLocale: "en",
51
- children
52
- }
53
- ) });
54
- };
55
- const fadeIn = keyframes`
56
- from { opacity: 0; }
57
- to { opacity: 1; }
58
- `;
59
- const slideUp = keyframes`
60
- from {
61
- opacity: 0;
62
- transform: translateY(30px);
63
- }
64
- to {
65
- opacity: 1;
66
- transform: translateY(0);
67
- }
68
- `;
69
- const ModalOverlay = styled.div`
70
- position: fixed;
71
- top: 0;
72
- left: 0;
73
- right: 0;
74
- bottom: 0;
75
- background: rgba(4, 28, 47, 0.85);
76
- backdrop-filter: blur(8px);
77
- z-index: 9999;
78
- display: flex;
79
- align-items: center;
80
- justify-content: center;
81
- animation: ${fadeIn} 0.3s ease-out;
82
- padding: 20px;
83
- `;
84
- const ModalContent = styled(Box)`
85
- background: ${(p) => p.theme.colors.neutral0};
86
- border-radius: 16px;
87
- width: 100%;
88
- max-width: 580px;
89
- box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
90
- animation: ${slideUp} 0.4s cubic-bezier(0.4, 0, 0.2, 1);
91
- overflow: hidden;
92
- `;
93
- const GradientHeader = styled(Box)`
94
- background: linear-gradient(135deg, #4945ff 0%, #7c3aed 100%);
95
- padding: 32px 40px;
96
- position: relative;
97
- overflow: hidden;
98
-
99
- &::before {
100
- content: '';
101
- position: absolute;
102
- top: -50%;
103
- right: -50%;
104
- width: 200%;
105
- height: 200%;
106
- background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
107
- }
108
- `;
109
- const IconWrapper = styled.div`
110
- width: 72px;
111
- height: 72px;
112
- border-radius: 50%;
113
- background: rgba(255, 255, 255, 0.2);
114
- display: flex;
115
- align-items: center;
116
- justify-content: center;
117
- margin: 0 auto 16px;
118
- backdrop-filter: blur(10px);
119
- border: 2px solid rgba(255, 255, 255, 0.3);
120
-
121
- svg {
122
- width: 36px;
123
- height: 36px;
124
- color: white;
125
- }
126
- `;
127
- const CloseButton = styled.button`
128
- position: absolute;
129
- top: 16px;
130
- right: 16px;
131
- background: rgba(255, 255, 255, 0.2);
132
- border: 2px solid rgba(255, 255, 255, 0.3);
133
- border-radius: 50%;
134
- width: 36px;
135
- height: 36px;
136
- display: flex;
137
- align-items: center;
138
- justify-content: center;
139
- cursor: pointer;
140
- transition: all 0.2s;
141
- z-index: 10;
142
-
143
- svg {
144
- width: 20px;
145
- height: 20px;
146
- color: white;
147
- }
148
-
149
- &:hover {
150
- background: rgba(255, 255, 255, 0.3);
151
- transform: scale(1.1);
152
- }
153
-
154
- &:active {
155
- transform: scale(0.95);
156
- }
157
- `;
158
- const ToggleButton = styled.button`
159
- background: none;
160
- border: none;
161
- color: #4945ff;
162
- font-size: 13px;
163
- font-weight: 600;
164
- cursor: pointer;
165
- padding: 8px 0;
166
- text-decoration: underline;
167
- transition: color 0.2s;
168
-
169
- &:hover {
170
- color: #7c3aed;
171
- }
172
-
173
- &:disabled {
174
- opacity: 0.5;
175
- cursor: not-allowed;
176
- }
177
- `;
178
- const GradientButton = styled(Button)`
179
- && {
180
- background: linear-gradient(135deg, #4945ff 0%, #7c3aed 100%);
181
- color: white;
182
- font-weight: 600;
183
- border: none;
184
- box-shadow: 0 4px 12px rgba(73, 69, 255, 0.4);
185
-
186
- &:hover:not(:disabled) {
187
- background: linear-gradient(135deg, #4338ca 0%, #6d28d9 100%);
188
- box-shadow: 0 6px 16px rgba(73, 69, 255, 0.5);
189
- }
190
-
191
- &:disabled {
192
- opacity: 0.6;
193
- cursor: not-allowed;
194
- }
195
- }
196
- `;
197
- styled(Box)`
198
- background: rgba(73, 69, 255, 0.06);
199
- border: 2px solid rgba(73, 69, 255, 0.3);
200
- border-radius: 8px;
201
- padding: 16px;
202
- `;
203
- styled(Typography)`
204
- font-size: 13px;
205
- line-height: 1.6;
206
- color: ${(p) => p.theme.colors.neutral800};
207
- `;
208
- const LicenseGuard = ({ children }) => {
209
- const { get, post } = useFetchClient();
210
- const { toggleNotification } = useNotification();
211
- const navigate = useNavigate();
212
- const [isChecking, setIsChecking] = useState(true);
213
- const [needsLicense, setNeedsLicense] = useState(false);
214
- const [isCreating, setIsCreating] = useState(false);
215
- const [useExistingKey, setUseExistingKey] = useState(false);
216
- const [useAutoCreate, setUseAutoCreate] = useState(true);
217
- const [existingLicenseKey, setExistingLicenseKey] = useState("");
218
- const [existingEmail, setExistingEmail] = useState("");
219
- const [adminUser, setAdminUser] = useState(null);
220
- const [formData, setFormData] = useState({
221
- email: "",
222
- firstName: "",
223
- lastName: ""
224
- });
225
- useEffect(() => {
226
- checkLicenseStatus();
227
- fetchAdminUser();
228
- }, []);
229
- const fetchAdminUser = async () => {
230
- try {
231
- const response = await get("/admin/users/me");
232
- console.log("Admin user response:", response);
233
- const userData = response.data?.data || response.data;
234
- console.log("User data extracted:", userData);
235
- if (userData) {
236
- setAdminUser(userData);
237
- setFormData({
238
- email: userData.email || "",
239
- firstName: userData.firstname || "",
240
- lastName: userData.lastname || ""
241
- });
242
- console.log("Admin user set:", {
243
- email: userData.email,
244
- firstname: userData.firstname,
245
- lastname: userData.lastname
246
- });
247
- }
248
- } catch (error) {
249
- console.error("Error fetching admin user:", error);
250
- }
251
- };
252
- const checkLicenseStatus = async () => {
253
- setIsChecking(true);
254
- try {
255
- const response = await get("/magic-link/license/status");
256
- if (response.data.valid) {
257
- setNeedsLicense(false);
258
- } else {
259
- setNeedsLicense(true);
260
- }
261
- } catch (error) {
262
- console.error("Error checking license:", error);
263
- setNeedsLicense(true);
264
- } finally {
265
- setIsChecking(false);
266
- }
267
- };
268
- const handleAutoCreateLicense = async (e) => {
269
- e.preventDefault();
270
- setIsCreating(true);
271
- try {
272
- console.log("Auto-creating license with admin user data...");
273
- const response = await post("/magic-link/license/auto-create", {});
274
- console.log("Auto-create response:", response);
275
- if (response.data && response.data.success) {
276
- toggleNotification({
277
- type: "success",
278
- message: `License automatically created! Reloading...`
279
- });
280
- setNeedsLicense(false);
281
- setTimeout(() => {
282
- window.location.reload();
283
- }, 500);
284
- } else {
285
- throw new Error("Failed to auto-create license");
286
- }
287
- } catch (error) {
288
- console.error("Error auto-creating license:", error);
289
- toggleNotification({
290
- type: "danger",
291
- message: error?.response?.data?.error?.message || "Failed to auto-create license. Try manual creation."
292
- });
293
- setIsCreating(false);
294
- setUseAutoCreate(false);
295
- }
296
- };
297
- const handleCreateLicense = async (e) => {
298
- e.preventDefault();
299
- if (!formData.email || !formData.firstName || !formData.lastName) {
300
- toggleNotification({
301
- type: "warning",
302
- message: "Please fill in all fields"
303
- });
304
- return;
305
- }
306
- setIsCreating(true);
307
- try {
308
- console.log("Creating license with data:", formData);
309
- const response = await post("/magic-link/license/create", formData);
310
- console.log("License creation response:", response);
311
- if (response.data && response.data.success) {
312
- toggleNotification({
313
- type: "success",
314
- message: `License created successfully! Reloading...`
315
- });
316
- setNeedsLicense(false);
317
- setTimeout(() => {
318
- window.location.reload();
319
- }, 500);
320
- } else {
321
- throw new Error("Failed to create license");
322
- }
323
- } catch (error) {
324
- console.error("Error creating license:", error);
325
- toggleNotification({
326
- type: "danger",
327
- message: error?.response?.data?.error?.message || "Failed to create license. Please try again."
328
- });
329
- setIsCreating(false);
330
- }
331
- };
332
- const handleValidateExistingKey = async (e) => {
333
- e.preventDefault();
334
- if (!existingLicenseKey.trim() || !existingEmail.trim()) {
335
- toggleNotification({
336
- type: "warning",
337
- message: "Please enter both license key and email address",
338
- title: "Validation Error"
339
- });
340
- return;
341
- }
342
- setIsCreating(true);
343
- try {
344
- console.log("Validating existing license key...");
345
- const pluginStore = await post("/magic-link/license/store-key", {
346
- licenseKey: existingLicenseKey.trim(),
347
- email: existingEmail.trim()
348
- });
349
- if (pluginStore.data && pluginStore.data.success) {
350
- toggleNotification({
351
- type: "success",
352
- message: "License key validated successfully! Reloading...",
353
- title: "✅ License Activated"
354
- });
355
- setNeedsLicense(false);
356
- setTimeout(() => {
357
- window.location.reload();
358
- }, 500);
359
- } else {
360
- throw new Error("Invalid license key or email");
361
- }
362
- } catch (error) {
363
- console.error("Error validating license key:", error);
364
- toggleNotification({
365
- type: "danger",
366
- message: error?.response?.data?.error?.message || "Invalid license key or email address. Please check and try again.",
367
- title: "Validation Error"
368
- });
369
- setIsCreating(false);
370
- }
371
- };
372
- const handleClose = () => {
373
- navigate("/content-manager");
374
- };
375
- if (isChecking) {
376
- return /* @__PURE__ */ jsx(Box, { padding: 8, style: { textAlign: "center" }, children: /* @__PURE__ */ jsx(Loader, { children: "Checking license..." }) });
377
- }
378
- if (needsLicense) {
379
- return /* @__PURE__ */ jsx(ModalOverlay, { children: /* @__PURE__ */ jsxs(ModalContent, { children: [
380
- /* @__PURE__ */ jsxs(GradientHeader, { children: [
381
- /* @__PURE__ */ jsx(CloseButton, { onClick: handleClose, type: "button", children: /* @__PURE__ */ jsx(Cross, {}) }),
382
- /* @__PURE__ */ jsx(IconWrapper, { children: /* @__PURE__ */ jsx(Key, {}) }),
383
- /* @__PURE__ */ jsxs(Box, { style: { textAlign: "center", position: "relative" }, children: [
384
- /* @__PURE__ */ jsx(
385
- Typography,
386
- {
387
- variant: "alpha",
388
- style: {
389
- color: "white",
390
- fontSize: "24px",
391
- fontWeight: "700",
392
- marginBottom: "12px",
393
- display: "block"
394
- },
395
- children: "🔐 Activate Magic Link Plugin"
396
- }
397
- ),
398
- /* @__PURE__ */ jsx(
399
- Typography,
400
- {
401
- variant: "epsilon",
402
- style: {
403
- color: "rgba(255, 255, 255, 0.9)",
404
- fontSize: "14px",
405
- display: "block"
406
- },
407
- children: useExistingKey ? "Enter your existing license key" : "Create a license to start using the plugin"
408
- }
409
- )
410
- ] })
411
- ] }),
412
- /* @__PURE__ */ jsx(
413
- Box,
414
- {
415
- as: "form",
416
- onSubmit: useExistingKey ? handleValidateExistingKey : useAutoCreate ? handleAutoCreateLicense : handleCreateLicense,
417
- padding: 6,
418
- paddingLeft: 8,
419
- paddingRight: 8,
420
- children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 5, style: { width: "100%" }, children: [
421
- /* @__PURE__ */ jsxs(Box, { style: { textAlign: "center", width: "100%" }, children: [
422
- !useExistingKey && /* @__PURE__ */ jsx(
423
- ToggleButton,
424
- {
425
- type: "button",
426
- onClick: () => setUseAutoCreate(!useAutoCreate),
427
- disabled: isCreating,
428
- style: { marginBottom: "8px", display: "block", margin: "0 auto" },
429
- children: useAutoCreate ? "→ Manual entry" : "← Auto-create with my account"
430
- }
431
- ),
432
- /* @__PURE__ */ jsx(
433
- ToggleButton,
434
- {
435
- type: "button",
436
- onClick: () => {
437
- setUseExistingKey(!useExistingKey);
438
- if (!useExistingKey) setUseAutoCreate(false);
439
- },
440
- disabled: isCreating,
441
- children: useExistingKey ? "← Create new license" : "Have a license key? →"
442
- }
443
- )
444
- ] }),
445
- /* @__PURE__ */ jsx(
446
- Box,
447
- {
448
- background: "primary100",
449
- padding: 4,
450
- style: {
451
- borderRadius: "8px",
452
- border: "2px solid #BAE6FD",
453
- width: "100%"
454
- },
455
- children: /* @__PURE__ */ jsx(Typography, { variant: "omega", style: { fontSize: "13px", lineHeight: "1.6" }, children: useExistingKey ? "🔑 Enter your email and license key to activate." : useAutoCreate && adminUser && adminUser.email ? `✨ Click "Activate" to auto-create a license with your account (${adminUser.email})` : useAutoCreate ? '✨ Click "Activate" to auto-create a license with your admin account' : "💡 A license will be created with the details below." })
456
- }
457
- ),
458
- useExistingKey ? (
459
- // Existing License Key Input
460
- /* @__PURE__ */ jsxs(Fragment, { children: [
461
- /* @__PURE__ */ jsxs(Box, { style: { width: "100%" }, children: [
462
- /* @__PURE__ */ jsx(
463
- Typography,
464
- {
465
- variant: "pi",
466
- fontWeight: "bold",
467
- style: { marginBottom: "8px", display: "block" },
468
- children: "Email Address *"
469
- }
470
- ),
471
- /* @__PURE__ */ jsx(
472
- TextInput,
473
- {
474
- placeholder: "admin@example.com",
475
- type: "email",
476
- value: existingEmail,
477
- onChange: (e) => setExistingEmail(e.target.value),
478
- required: true,
479
- disabled: isCreating,
480
- style: { width: "100%" }
481
- }
482
- ),
483
- /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", style: { fontSize: "11px", marginTop: "4px" }, children: "Enter the email address associated with this license" })
484
- ] }),
485
- /* @__PURE__ */ jsxs(Box, { style: { width: "100%" }, children: [
486
- /* @__PURE__ */ jsx(
487
- Typography,
488
- {
489
- variant: "pi",
490
- fontWeight: "bold",
491
- style: { marginBottom: "8px", display: "block" },
492
- children: "License Key *"
493
- }
494
- ),
495
- /* @__PURE__ */ jsx(
496
- TextInput,
497
- {
498
- placeholder: "67C5-40D2-7695-718C",
499
- value: existingLicenseKey,
500
- onChange: (e) => setExistingLicenseKey(e.target.value),
501
- required: true,
502
- disabled: isCreating,
503
- style: { width: "100%", fontFamily: "monospace" }
504
- }
505
- ),
506
- /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", style: { fontSize: "11px", marginTop: "4px" }, children: "Enter the license key in the format: XXXX-XXXX-XXXX-XXXX" })
507
- ] })
508
- ] })
509
- ) : useAutoCreate && adminUser ? (
510
- // Auto-create mode - Show user info
511
- /* @__PURE__ */ jsxs(
512
- Box,
513
- {
514
- background: "success100",
515
- padding: 5,
516
- style: {
517
- borderRadius: "8px",
518
- border: "2px solid #DCFCE7",
519
- textAlign: "center"
520
- },
521
- children: [
522
- /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", style: { marginBottom: "12px", display: "block" }, children: "Ready to activate with your account:" }),
523
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", style: { marginBottom: "4px", display: "block" }, children: [
524
- "👤 ",
525
- adminUser.firstname || "Admin",
526
- " ",
527
- adminUser.lastname || "User"
528
- ] }),
529
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
530
- "📧 ",
531
- adminUser.email || "Loading..."
532
- ] })
533
- ]
534
- }
535
- )
536
- ) : (
537
- // Manual Create License Fields
538
- /* @__PURE__ */ jsxs(Fragment, { children: [
539
- /* @__PURE__ */ jsxs(Box, { style: { width: "100%" }, children: [
540
- /* @__PURE__ */ jsx(
541
- Typography,
542
- {
543
- variant: "pi",
544
- fontWeight: "bold",
545
- style: { marginBottom: "8px", display: "block" },
546
- children: "Email Address *"
547
- }
548
- ),
549
- /* @__PURE__ */ jsx(
550
- TextInput,
551
- {
552
- placeholder: "admin@example.com",
553
- type: "email",
554
- value: formData.email,
555
- onChange: (e) => setFormData({ ...formData, email: e.target.value }),
556
- required: true,
557
- disabled: isCreating,
558
- style: { width: "100%" }
559
- }
560
- )
561
- ] }),
562
- /* @__PURE__ */ jsxs(Box, { style: { width: "100%" }, children: [
563
- /* @__PURE__ */ jsx(
564
- Typography,
565
- {
566
- variant: "pi",
567
- fontWeight: "bold",
568
- style: { marginBottom: "8px", display: "block" },
569
- children: "First Name *"
570
- }
571
- ),
572
- /* @__PURE__ */ jsx(
573
- TextInput,
574
- {
575
- placeholder: "John",
576
- value: formData.firstName,
577
- onChange: (e) => setFormData({ ...formData, firstName: e.target.value }),
578
- required: true,
579
- disabled: isCreating,
580
- style: { width: "100%" }
581
- }
582
- )
583
- ] }),
584
- /* @__PURE__ */ jsxs(Box, { style: { width: "100%" }, children: [
585
- /* @__PURE__ */ jsx(
586
- Typography,
587
- {
588
- variant: "pi",
589
- fontWeight: "bold",
590
- style: { marginBottom: "8px", display: "block" },
591
- children: "Last Name *"
592
- }
593
- ),
594
- /* @__PURE__ */ jsx(
595
- TextInput,
596
- {
597
- placeholder: "Doe",
598
- value: formData.lastName,
599
- onChange: (e) => setFormData({ ...formData, lastName: e.target.value }),
600
- required: true,
601
- disabled: isCreating,
602
- style: { width: "100%" }
603
- }
604
- )
605
- ] })
606
- ] })
607
- ),
608
- /* @__PURE__ */ jsx(Flex, { gap: 3, justifyContent: "center", style: { marginTop: "16px" }, children: useExistingKey ? (
609
- // Validate Button
610
- /* @__PURE__ */ jsx(
611
- GradientButton,
612
- {
613
- type: "submit",
614
- size: "L",
615
- startIcon: /* @__PURE__ */ jsx(Check, {}),
616
- loading: isCreating,
617
- disabled: isCreating || !existingLicenseKey.trim() || !existingEmail.trim(),
618
- children: "Validate License"
619
- }
620
- )
621
- ) : (
622
- // Create/Auto-Create License Button
623
- /* @__PURE__ */ jsx(
624
- GradientButton,
625
- {
626
- type: "submit",
627
- size: "L",
628
- startIcon: /* @__PURE__ */ jsx(Check, {}),
629
- loading: isCreating,
630
- disabled: isCreating || !useAutoCreate && (!formData.email || !formData.firstName || !formData.lastName),
631
- children: useAutoCreate ? "Activate License" : "Create License"
632
- }
633
- )
634
- ) })
635
- ] })
636
- }
637
- )
638
- ] }) });
639
- }
640
- return /* @__PURE__ */ jsx(Fragment, { children });
641
- };
642
- export {
643
- LicenseGuard as L,
644
- LanguageProvider as a,
645
- usePluginLanguage as u
646
- };