strapi-identity 0.0.1-debug.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 (124) hide show
  1. package/README.md +112 -0
  2. package/dist/_chunks/AdminReset-B7_r8Jh1.mjs +112 -0
  3. package/dist/_chunks/AdminReset-BohoWW_F.js +112 -0
  4. package/dist/_chunks/ProfileToggle-CHVH6P3j.js +339 -0
  5. package/dist/_chunks/ProfileToggle-DIVxRCbJ.mjs +337 -0
  6. package/dist/_chunks/SettingsPage-C_Ke-Agb.mjs +5752 -0
  7. package/dist/_chunks/SettingsPage-IAGo8KRd.js +5752 -0
  8. package/dist/_chunks/WarningAlert-DFE5euMk.js +33 -0
  9. package/dist/_chunks/WarningAlert-VU011LVF.mjs +34 -0
  10. package/dist/_chunks/ar-BXaam37U.mjs +36 -0
  11. package/dist/_chunks/ar-i2eiMZkz.js +36 -0
  12. package/dist/_chunks/ca-BVpGzY8r.js +36 -0
  13. package/dist/_chunks/ca-DZ9DbEcQ.mjs +36 -0
  14. package/dist/_chunks/cs-Gok16KLy.mjs +36 -0
  15. package/dist/_chunks/cs-_PZVkwt0.js +36 -0
  16. package/dist/_chunks/de-BuYn1AYX.mjs +26 -0
  17. package/dist/_chunks/de-GItli7en.js +26 -0
  18. package/dist/_chunks/dk-B7EOsAdU.js +36 -0
  19. package/dist/_chunks/dk-CI64Xmli.mjs +36 -0
  20. package/dist/_chunks/en-B_vJwdfS.mjs +36 -0
  21. package/dist/_chunks/en-D4KP9t1Y.js +36 -0
  22. package/dist/_chunks/es-CHwF7YK-.js +36 -0
  23. package/dist/_chunks/es-CqJcXo4j.mjs +36 -0
  24. package/dist/_chunks/eu-D-snytN8.mjs +36 -0
  25. package/dist/_chunks/eu-DvdbwE5E.js +36 -0
  26. package/dist/_chunks/fr-Bt6sS5GX.mjs +26 -0
  27. package/dist/_chunks/fr-CbCW6hVD.js +26 -0
  28. package/dist/_chunks/gu-3wJbbAmw.mjs +36 -0
  29. package/dist/_chunks/gu-D2LgVfMp.js +36 -0
  30. package/dist/_chunks/he-Bjv7eygt.mjs +36 -0
  31. package/dist/_chunks/he-DnhYpbvN.js +36 -0
  32. package/dist/_chunks/hi-CNiDezU7.mjs +36 -0
  33. package/dist/_chunks/hi-DDD2E3A3.js +36 -0
  34. package/dist/_chunks/hu-C1_YkZHU.js +36 -0
  35. package/dist/_chunks/hu-aLaIWmGw.mjs +36 -0
  36. package/dist/_chunks/id-C8WRgGm1.mjs +36 -0
  37. package/dist/_chunks/id-u3wVE6Rv.js +36 -0
  38. package/dist/_chunks/index-CeYv7uJ3.js +1448 -0
  39. package/dist/_chunks/index-Df5ytRzw.mjs +1430 -0
  40. package/dist/_chunks/it-CDw6dG9Z.js +36 -0
  41. package/dist/_chunks/it-CjoRoJj1.mjs +36 -0
  42. package/dist/_chunks/ja-CbMXy2ym.js +36 -0
  43. package/dist/_chunks/ja-CewucIUY.mjs +36 -0
  44. package/dist/_chunks/ko-BEtJPpfJ.js +36 -0
  45. package/dist/_chunks/ko-D-kAxDtd.mjs +36 -0
  46. package/dist/_chunks/ml-0fR2_MmA.js +36 -0
  47. package/dist/_chunks/ml-DR3AaofF.mjs +36 -0
  48. package/dist/_chunks/ms-COHLS5e5.mjs +36 -0
  49. package/dist/_chunks/ms-DLvuGSlk.js +36 -0
  50. package/dist/_chunks/nl-DVtHsM2H.mjs +36 -0
  51. package/dist/_chunks/nl-wj6kn642.js +36 -0
  52. package/dist/_chunks/no-DVBgWt8q.js +36 -0
  53. package/dist/_chunks/no-D_0yjyCy.mjs +36 -0
  54. package/dist/_chunks/pl-B2ghisbC.js +36 -0
  55. package/dist/_chunks/pl-C3GNxjVX.mjs +36 -0
  56. package/dist/_chunks/pt-BR-BbKV8YoX.mjs +36 -0
  57. package/dist/_chunks/pt-BR-CfgNaB1-.js +36 -0
  58. package/dist/_chunks/pt-DKe8rRWa.js +36 -0
  59. package/dist/_chunks/pt-z4K3cCjf.mjs +36 -0
  60. package/dist/_chunks/ru-BFSm68HC.js +36 -0
  61. package/dist/_chunks/ru-C85izLFa.mjs +36 -0
  62. package/dist/_chunks/sa-B1XoTTrE.mjs +36 -0
  63. package/dist/_chunks/sa-BOPaqylt.js +36 -0
  64. package/dist/_chunks/sk-C48lUPuC.mjs +36 -0
  65. package/dist/_chunks/sk-Dd-S1612.js +36 -0
  66. package/dist/_chunks/sv-BLma_kJl.mjs +36 -0
  67. package/dist/_chunks/sv-lg64Cw78.js +36 -0
  68. package/dist/_chunks/th-BJEu5n7q.mjs +36 -0
  69. package/dist/_chunks/th-DPbm5NrX.js +36 -0
  70. package/dist/_chunks/tokenHelpers-DagDzpso.mjs +22 -0
  71. package/dist/_chunks/tokenHelpers-jtoRu0q5.js +21 -0
  72. package/dist/_chunks/tr-Bm1QZr4v.js +36 -0
  73. package/dist/_chunks/tr-DkIUODKq.mjs +36 -0
  74. package/dist/_chunks/uk-D7ArtSe3.mjs +36 -0
  75. package/dist/_chunks/uk-FARzIGx4.js +36 -0
  76. package/dist/_chunks/vi-Bi9B6eTY.js +36 -0
  77. package/dist/_chunks/vi-DS0yslPP.mjs +36 -0
  78. package/dist/_chunks/zh-DkEx28ZA.js +36 -0
  79. package/dist/_chunks/zh-DwCvIPSz.mjs +36 -0
  80. package/dist/_chunks/zh-Hans-BwwKCR6_.js +36 -0
  81. package/dist/_chunks/zh-Hans-DP2xZyda.mjs +36 -0
  82. package/dist/admin/index.js +4 -0
  83. package/dist/admin/index.mjs +5 -0
  84. package/dist/admin/src/components/ConfirmModal/ConfirmModal.d.ts +10 -0
  85. package/dist/admin/src/components/ConfirmModal/index.d.ts +1 -0
  86. package/dist/admin/src/components/Initializer.d.ts +5 -0
  87. package/dist/admin/src/components/InputOTP.d.ts +11 -0
  88. package/dist/admin/src/components/RemoveModal/RemoveModal.d.ts +7 -0
  89. package/dist/admin/src/components/RemoveModal/index.d.ts +1 -0
  90. package/dist/admin/src/components/WarningAlert/WarningAlert.d.ts +11 -0
  91. package/dist/admin/src/components/WarningAlert/index.d.ts +1 -0
  92. package/dist/admin/src/index.d.ts +3 -0
  93. package/dist/admin/src/injection/AdminReset.d.ts +4 -0
  94. package/dist/admin/src/injection/ProfileToggle.d.ts +2 -0
  95. package/dist/admin/src/pluginId.d.ts +1 -0
  96. package/dist/admin/src/public/VerifyPage.d.ts +12 -0
  97. package/dist/admin/src/settings/SettingsPage.d.ts +1 -0
  98. package/dist/admin/src/utils/getTranslation.d.ts +2 -0
  99. package/dist/admin/src/utils/tokenHelpers.d.ts +12 -0
  100. package/dist/server/index.js +11765 -0
  101. package/dist/server/index.mjs +11761 -0
  102. package/dist/server/src/bootstrap.d.ts +3 -0
  103. package/dist/server/src/config/index.d.ts +3 -0
  104. package/dist/server/src/content-types/config/index.d.ts +38 -0
  105. package/dist/server/src/content-types/index.d.ts +3 -0
  106. package/dist/server/src/content-types/mfa/index.d.ts +56 -0
  107. package/dist/server/src/content-types/temp-mfa/index.d.ts +35 -0
  108. package/dist/server/src/controllers/admin.d.ts +3 -0
  109. package/dist/server/src/controllers/config.d.ts +3 -0
  110. package/dist/server/src/controllers/controller.d.ts +3 -0
  111. package/dist/server/src/controllers/index.d.ts +3 -0
  112. package/dist/server/src/destroy.d.ts +3 -0
  113. package/dist/server/src/index.d.ts +26 -0
  114. package/dist/server/src/middlewares/index.d.ts +3 -0
  115. package/dist/server/src/policies/has-mfa.d.ts +3 -0
  116. package/dist/server/src/policies/index.d.ts +3 -0
  117. package/dist/server/src/register.d.ts +3 -0
  118. package/dist/server/src/routes/admin/index.d.ts +2 -0
  119. package/dist/server/src/routes/index.d.ts +3 -0
  120. package/dist/server/src/services/admin.d.ts +11 -0
  121. package/dist/server/src/services/config.d.ts +28 -0
  122. package/dist/server/src/services/index.d.ts +3 -0
  123. package/dist/server/src/services/mfa.d.ts +50 -0
  124. package/package.json +88 -0
package/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Strapi Plugin Strapi Identity
2
+
3
+ Detailed Multi-Factor Authentication (MFA) plugin for Strapi v5+. Secure your Strapi Admin panel with TOTP-based 2FA, fully integrated into the Strapi interface.
4
+
5
+ ## Features
6
+
7
+ - **MFA Login Interception**: Seamlessly integrates with the default Strapi login flow.
8
+ - **TOTP Compatibility**: Works with all major authenticator apps (Google Authenticator, Authy, 1Password, etc.).
9
+ - **Recovery Codes**: Generates secure recovery codes for emergency access.
10
+ - **Native UI Integration**:
11
+ - Matches Strapi's design system.
12
+ - Profile integration for easy setup.
13
+ - Dedicated verification page.
14
+ - **Global Configuration**:
15
+ - Enable/Disable globally.
16
+ - Custom "Issuer" name for authenticator apps.
17
+ - **Role-Based Access Control**: Granular permissions for managing plugin settings.
18
+ - **Multi-language Support**: Fully localized interface.
19
+
20
+ ## Installation
21
+
22
+ To install this plugin, you'll need to include it in your Strapi project.
23
+
24
+ 1. **Install the dependency** (if published to npm) or link the local plugin.
25
+ 2. **Enable the plugin** in `config/plugins.ts`:
26
+
27
+ ```typescript
28
+ export default {
29
+ // ...
30
+ 'strapi-identity': {
31
+ enabled: true,
32
+ resolve: './src/plugins/strapi-identity', // If local
33
+ },
34
+ // ...
35
+ };
36
+ ```
37
+
38
+ 3. **Build the admin panel**:
39
+
40
+ ```bash
41
+ npm run build
42
+ ```
43
+
44
+ 4. **Restart Strapi**:
45
+
46
+ ```bash
47
+ npm run develop
48
+ ```
49
+
50
+ ## Configuration
51
+
52
+ Access the global settings via the admin panel:
53
+ **Settings** -> **Global Settings** -> **Strapi Identify Settings**
54
+
55
+ | Option | Description |
56
+ | ----------- | ------------------------------------------------------------------------------------------ |
57
+ | **Enabled** | Master switch to enable or disable the MFA interception logic globally. |
58
+ | **Enforce** | _(Coming Soon)_ Force all users to set up MFA before accessing the dashboard. |
59
+ | **Issuer** | The name that appears in the authenticator app (e.g., "My Project"). Defaults to "Strapi". |
60
+
61
+ ### Permissions
62
+
63
+ Go to **Settings** -> **Administration Panel** -> **Roles** to configure who can manage these settings:
64
+
65
+ - `plugins::strapi-identity.settings.read`: View configuration.
66
+ - `plugins::strapi-identity.settings.update`: Modify configuration.
67
+
68
+ ## User Guide
69
+
70
+ ### Setting up MFA (User)
71
+
72
+ 1. Log in to the Strapi Admin panel.
73
+ 2. Click on your **User Profile** avatar in the top-right corner.
74
+ 3. Click **Profile**.
75
+ 4. In the "Two-Factor Authentication" section, toggle the switch to **Enable Two-Factor Authentication**.
76
+ 5. A modal will appear:
77
+ - **Scan the QR Code** with your authenticator app.
78
+ - Enter the **6-digit code** displayed in your app.
79
+ - **Save your Recovery Codes** in a safe place. You will not see them again!
80
+ 6. Click **Finish**.
81
+
82
+ ### Signing In
83
+
84
+ 1. Enter your Email and Password on the standard Strapi login page.
85
+ 2. If credentials are correct and MFA is enabled, you will be redirected to the Verification Page.
86
+ 3. Enter the code from your authenticator app.
87
+ 4. Upon success, you will be redirected to the dashboard.
88
+
89
+ ### Admin Reset (Super Admin)
90
+
91
+ Administrators with the `settings.update` permission can reset MFA for other users:
92
+
93
+ 1. Navigate to **Settings** -> **Administration Panel** -> **Users**.
94
+ 2. Click the **Edit** (pencil) icon for the user you wish to manage.
95
+ 3. Locate the **Two-Factor Authentication** section in the user form.
96
+ 4. If MFA is enabled for that user, click the **Reset** button.
97
+ - This will disable 2FA for the user, allowing them to log in with just their password and set up MFA again.
98
+
99
+ ## Roadmap & Status
100
+
101
+ Below is the implementation status of planned features.
102
+
103
+ - [x] **MFA Login**: Intercepts admin login securely.
104
+ - [x] **Recovery Codes**: Backup access method.
105
+ - [x] **TOTP App Compatibility**: Standard RFC 6238 implementation.
106
+ - [x] **Integrated Setup Screen**: User-friendly wizard in profile settings.
107
+ - [x] **MFA Page Matches Theme**: Consistent UI/UX.
108
+ - [x] **Custom Issuer**: Configurable app label.
109
+ - [x] **Multi-language Support**: i18n ready.
110
+ - [x] **Admin Reset**: Allow super-admins to reset MFA for other users who lost access.
111
+ - [ ] **Email Passcode**: Alternative MFA method via Email.
112
+ - [ ] **Enforced Mode**: Mandatory MFA for specific roles or all users.
@@ -0,0 +1,112 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { W as WarningAlert } from "./WarningAlert-VU011LVF.mjs";
4
+ import { Box, Flex, Typography, Grid, Button } from "@strapi/design-system";
5
+ import { g as getTranslation } from "./index-Df5ytRzw.mjs";
6
+ import { g as getToken } from "./tokenHelpers-DagDzpso.mjs";
7
+ import { useIntl } from "react-intl";
8
+ const AdminReset = ({ id }) => {
9
+ const { formatMessage } = useIntl();
10
+ const [is2FAEnabled, setIs2FAEnabled] = useState(false);
11
+ const [warningOpen, setWarningOpen] = useState(false);
12
+ const [loading, setLoading] = useState(false);
13
+ const handleReset = async () => {
14
+ const token = getToken();
15
+ setLoading(true);
16
+ try {
17
+ const response = await fetch(`/strapi-identity/admin/user/${id}`, {
18
+ headers: { Authorization: `Bearer ${token}` },
19
+ method: "DELETE"
20
+ });
21
+ if (!response.ok) throw new Error("Failed to fetch 2FA status for user");
22
+ setIs2FAEnabled(false);
23
+ } catch (error) {
24
+ console.error("Error resetting 2FA for user:", error);
25
+ } finally {
26
+ setLoading(false);
27
+ setWarningOpen(false);
28
+ }
29
+ };
30
+ useEffect(() => {
31
+ if (!id) return;
32
+ const ac = new AbortController();
33
+ const token = getToken();
34
+ (async () => {
35
+ try {
36
+ const response = await fetch(`/strapi-identity/admin/user/${id}`, {
37
+ headers: { Authorization: `Bearer ${token}` },
38
+ signal: ac.signal
39
+ });
40
+ if (!response.ok) throw new Error("Failed to fetch 2FA status for user");
41
+ const data = await response.json();
42
+ setIs2FAEnabled(data.data);
43
+ } catch (error) {
44
+ console.error("Error fetching 2FA status for user:", error);
45
+ }
46
+ })();
47
+ }, [id]);
48
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
49
+ /* @__PURE__ */ jsx(
50
+ Box,
51
+ {
52
+ background: "neutral0",
53
+ hasRadius: true,
54
+ shadow: "filterShadow",
55
+ paddingTop: 6,
56
+ paddingBottom: 6,
57
+ paddingLeft: 7,
58
+ paddingRight: 7,
59
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
60
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 1, children: [
61
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: formatMessage({
62
+ id: getTranslation("admin.title"),
63
+ defaultMessage: "Two-Factor Authentication"
64
+ }) }),
65
+ /* @__PURE__ */ jsx(Typography, { children: formatMessage({
66
+ id: getTranslation("admin.subtitle"),
67
+ defaultMessage: "Reset the Two-Factor Authentication for a user."
68
+ }) })
69
+ ] }),
70
+ /* @__PURE__ */ jsx(Grid.Root, { children: /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, alignItems: "stretch", children: /* @__PURE__ */ jsx(
71
+ Button,
72
+ {
73
+ disabled: !is2FAEnabled,
74
+ variant: "danger",
75
+ onClick: () => setWarningOpen(true),
76
+ children: formatMessage({
77
+ id: getTranslation("app.components.Button.reset"),
78
+ defaultMessage: "Reset"
79
+ })
80
+ }
81
+ ) }) })
82
+ ] })
83
+ }
84
+ ),
85
+ /* @__PURE__ */ jsxs(
86
+ WarningAlert,
87
+ {
88
+ loading,
89
+ title: formatMessage({
90
+ id: getTranslation("admin.warn-title"),
91
+ defaultMessage: "Reset 2FA for this user?"
92
+ }),
93
+ open: warningOpen,
94
+ onCancel: () => setWarningOpen(false),
95
+ onConfirm: handleReset,
96
+ children: [
97
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", textAlign: "center", children: formatMessage({
98
+ id: getTranslation("admin.warning"),
99
+ defaultMessage: "Resetting the Two-Factor Authentication will allow the user to set it up again with a new device. This action cannot be undone."
100
+ }) }),
101
+ /* @__PURE__ */ jsx(Typography, { textAlign: "center", fontWeight: "semiBold", children: formatMessage({
102
+ id: getTranslation("app.confirm.body"),
103
+ defaultMessage: "Are you sure?"
104
+ }) })
105
+ ]
106
+ }
107
+ )
108
+ ] });
109
+ };
110
+ export {
111
+ AdminReset as default
112
+ };
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const React = require("react");
5
+ const WarningAlert = require("./WarningAlert-DFE5euMk.js");
6
+ const designSystem = require("@strapi/design-system");
7
+ const index = require("./index-CeYv7uJ3.js");
8
+ const tokenHelpers = require("./tokenHelpers-jtoRu0q5.js");
9
+ const reactIntl = require("react-intl");
10
+ const AdminReset = ({ id }) => {
11
+ const { formatMessage } = reactIntl.useIntl();
12
+ const [is2FAEnabled, setIs2FAEnabled] = React.useState(false);
13
+ const [warningOpen, setWarningOpen] = React.useState(false);
14
+ const [loading, setLoading] = React.useState(false);
15
+ const handleReset = async () => {
16
+ const token = tokenHelpers.getToken();
17
+ setLoading(true);
18
+ try {
19
+ const response = await fetch(`/strapi-identity/admin/user/${id}`, {
20
+ headers: { Authorization: `Bearer ${token}` },
21
+ method: "DELETE"
22
+ });
23
+ if (!response.ok) throw new Error("Failed to fetch 2FA status for user");
24
+ setIs2FAEnabled(false);
25
+ } catch (error) {
26
+ console.error("Error resetting 2FA for user:", error);
27
+ } finally {
28
+ setLoading(false);
29
+ setWarningOpen(false);
30
+ }
31
+ };
32
+ React.useEffect(() => {
33
+ if (!id) return;
34
+ const ac = new AbortController();
35
+ const token = tokenHelpers.getToken();
36
+ (async () => {
37
+ try {
38
+ const response = await fetch(`/strapi-identity/admin/user/${id}`, {
39
+ headers: { Authorization: `Bearer ${token}` },
40
+ signal: ac.signal
41
+ });
42
+ if (!response.ok) throw new Error("Failed to fetch 2FA status for user");
43
+ const data = await response.json();
44
+ setIs2FAEnabled(data.data);
45
+ } catch (error) {
46
+ console.error("Error fetching 2FA status for user:", error);
47
+ }
48
+ })();
49
+ }, [id]);
50
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
51
+ /* @__PURE__ */ jsxRuntime.jsx(
52
+ designSystem.Box,
53
+ {
54
+ background: "neutral0",
55
+ hasRadius: true,
56
+ shadow: "filterShadow",
57
+ paddingTop: 6,
58
+ paddingBottom: 6,
59
+ paddingLeft: 7,
60
+ paddingRight: 7,
61
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
62
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 1, children: [
63
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", children: formatMessage({
64
+ id: index.getTranslation("admin.title"),
65
+ defaultMessage: "Two-Factor Authentication"
66
+ }) }),
67
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
68
+ id: index.getTranslation("admin.subtitle"),
69
+ defaultMessage: "Reset the Two-Factor Authentication for a user."
70
+ }) })
71
+ ] }),
72
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsx(
73
+ designSystem.Button,
74
+ {
75
+ disabled: !is2FAEnabled,
76
+ variant: "danger",
77
+ onClick: () => setWarningOpen(true),
78
+ children: formatMessage({
79
+ id: index.getTranslation("app.components.Button.reset"),
80
+ defaultMessage: "Reset"
81
+ })
82
+ }
83
+ ) }) })
84
+ ] })
85
+ }
86
+ ),
87
+ /* @__PURE__ */ jsxRuntime.jsxs(
88
+ WarningAlert.WarningAlert,
89
+ {
90
+ loading,
91
+ title: formatMessage({
92
+ id: index.getTranslation("admin.warn-title"),
93
+ defaultMessage: "Reset 2FA for this user?"
94
+ }),
95
+ open: warningOpen,
96
+ onCancel: () => setWarningOpen(false),
97
+ onConfirm: handleReset,
98
+ children: [
99
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textAlign: "center", children: formatMessage({
100
+ id: index.getTranslation("admin.warning"),
101
+ defaultMessage: "Resetting the Two-Factor Authentication will allow the user to set it up again with a new device. This action cannot be undone."
102
+ }) }),
103
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textAlign: "center", fontWeight: "semiBold", children: formatMessage({
104
+ id: index.getTranslation("app.confirm.body"),
105
+ defaultMessage: "Are you sure?"
106
+ }) })
107
+ ]
108
+ }
109
+ )
110
+ ] });
111
+ };
112
+ exports.default = AdminReset;
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const React = require("react");
5
+ const designSystem = require("@strapi/design-system");
6
+ const index = require("./index-CeYv7uJ3.js");
7
+ const QRCode = require("react-qr-code");
8
+ const reactIntl = require("react-intl");
9
+ const tokenHelpers = require("./tokenHelpers-jtoRu0q5.js");
10
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
11
+ const QRCode__default = /* @__PURE__ */ _interopDefault(QRCode);
12
+ function ConfirmModal({
13
+ open,
14
+ onOpenChange,
15
+ onSubmit,
16
+ qrCodeUri,
17
+ secret,
18
+ passcodes
19
+ }) {
20
+ const { formatMessage } = reactIntl.useIntl();
21
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit, children: [
22
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: formatMessage({
23
+ id: index.getTranslation("profile.setup"),
24
+ defaultMessage: "Set up Two-Factor Authentication"
25
+ }) }) }),
26
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: passcodes ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "center", gap: 4, marginTop: 4, marginBottom: 4, children: [
27
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textAlign: "center", children: formatMessage({
28
+ id: index.getTranslation("profile.recovery_codes"),
29
+ defaultMessage: "Please save the following recovery codes in a safe place. Each code can only be used once to access your account if you lose access to your authenticator app."
30
+ }) }),
31
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Root, { gap: 4, marginTop: 4, marginBottom: 4, children: passcodes.map((code) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: code }) }, code)) }),
32
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textAlign: "center", children: formatMessage({
33
+ id: index.getTranslation("profile.recovery_codes_warning"),
34
+ defaultMessage: "If you lose both your authenticator app and your recovery codes, you will need to contact an administrator to regain access to your account."
35
+ }) })
36
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
37
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "center", gap: 4, marginTop: 4, marginBottom: 4, children: [
38
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
39
+ id: index.getTranslation("profile.scan_qr"),
40
+ defaultMessage: "You will need an authenticator app to scan the QR code below."
41
+ }) }),
42
+ /* @__PURE__ */ jsxRuntime.jsx(QRCode__default.default, { value: qrCodeUri || "" }),
43
+ secret && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: secret || "" })
44
+ ] }),
45
+ /* @__PURE__ */ jsxRuntime.jsx(
46
+ "hr",
47
+ {
48
+ style: {
49
+ height: "1px",
50
+ border: "0",
51
+ backgroundColor: "#e5e5e5"
52
+ }
53
+ }
54
+ ),
55
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "center", gap: 4, marginTop: 4, marginBottom: 4, children: [
56
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
57
+ id: index.getTranslation("profile.enter_otp"),
58
+ defaultMessage: "Enter the 6-digit code from your authenticator app to confirm."
59
+ }) }),
60
+ /* @__PURE__ */ jsxRuntime.jsxs(index.InputOTP, { maxLength: 6, name: "otp", id: "otp", autoFocus: true, children: [
61
+ /* @__PURE__ */ jsxRuntime.jsxs(index.InputOTPGroup, { children: [
62
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 0 }),
63
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 1 }),
64
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 2 })
65
+ ] }),
66
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSeparator, {}),
67
+ /* @__PURE__ */ jsxRuntime.jsxs(index.InputOTPGroup, { children: [
68
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 3 }),
69
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 4 }),
70
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 5 })
71
+ ] })
72
+ ] })
73
+ ] })
74
+ ] }) }),
75
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
76
+ passcodes && /* @__PURE__ */ jsxRuntime.jsx("span", {}),
77
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: passcodes ? void 0 : "tertiary", children: passcodes ? formatMessage({ id: "global.close", defaultMessage: "Close" }) : formatMessage({ id: "app.components.Button.cancel", defaultMessage: "Cancel" }) }) }),
78
+ !passcodes && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { type: "submit", children: formatMessage({ id: "app.components.Button.confirm", defaultMessage: "Confirm" }) })
79
+ ] })
80
+ ] }) }) });
81
+ }
82
+ function RemoveModal({ open, onOpenChange, onSubmit }) {
83
+ const { formatMessage } = reactIntl.useIntl();
84
+ const [showRecovery, setShowRecovery] = React.useState(false);
85
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit, children: [
86
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: formatMessage({
87
+ id: index.getTranslation("profile.disable_title"),
88
+ defaultMessage: "Disable Two-Factor Authentication"
89
+ }) }) }),
90
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "center", gap: 4, marginTop: 4, marginBottom: 4, children: [
91
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
92
+ id: index.getTranslation("profile.disable_instruction"),
93
+ defaultMessage: "Enter the 6-digit code from your authenticator app to disable Two-Factor Authentication."
94
+ }) }),
95
+ showRecovery ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.TextInput, { name: "otp", id: "otp", autoFocus: true }) : /* @__PURE__ */ jsxRuntime.jsxs(index.InputOTP, { maxLength: 6, name: "otp", id: "otp", autoFocus: true, children: [
96
+ /* @__PURE__ */ jsxRuntime.jsxs(index.InputOTPGroup, { children: [
97
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 0 }),
98
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 1 }),
99
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 2 })
100
+ ] }),
101
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSeparator, {}),
102
+ /* @__PURE__ */ jsxRuntime.jsxs(index.InputOTPGroup, { children: [
103
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 3 }),
104
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 4 }),
105
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 5 })
106
+ ] })
107
+ ] }),
108
+ /* @__PURE__ */ jsxRuntime.jsx(
109
+ designSystem.Button,
110
+ {
111
+ variant: "ghost",
112
+ type: "button",
113
+ onClick: () => setShowRecovery((prev) => !prev),
114
+ children: showRecovery ? formatMessage({
115
+ id: index.getTranslation("general.use_verification_code"),
116
+ defaultMessage: "Use verification code"
117
+ }) : formatMessage({
118
+ id: index.getTranslation("general.use_recovery_code"),
119
+ defaultMessage: "Use recovery code"
120
+ })
121
+ }
122
+ )
123
+ ] }) }),
124
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
125
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: formatMessage({ id: "app.components.Button.cancel", defaultMessage: "Cancel" }) }) }),
126
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { type: "submit", children: formatMessage({ id: "app.components.Button.confirm", defaultMessage: "Confirm" }) })
127
+ ] })
128
+ ] }) }) });
129
+ }
130
+ const ProfileToggle = () => {
131
+ const { formatMessage } = reactIntl.useIntl();
132
+ const [enabled, setEnabled] = React.useState(null);
133
+ const [mfaEnabled, setMfaEnabled] = React.useState(false);
134
+ const [disableDialogOpen, setDisableDialogOpen] = React.useState(false);
135
+ const [modalOpen, setModalOpen] = React.useState(false);
136
+ const [uri, setUri] = React.useState(null);
137
+ const [secret, setSecret] = React.useState(null);
138
+ const [passcodes, setPasscodes] = React.useState(null);
139
+ const handleToggle = async ({ target }) => {
140
+ const token = tokenHelpers.getToken();
141
+ const enable = target?.checked || false;
142
+ if (!enable && enabled === "full") {
143
+ setDisableDialogOpen(true);
144
+ return;
145
+ }
146
+ try {
147
+ const response = await fetch("/strapi-identity/enable", {
148
+ method: "POST",
149
+ headers: { "Content-Type": "application/json", authorization: `Bearer ${token}` },
150
+ body: JSON.stringify({ enable })
151
+ });
152
+ const body = await response.json();
153
+ if (!response.ok) {
154
+ throw new Error(`${response.status} - ${body.error || "Failed to update MFA status"}`);
155
+ }
156
+ const data = body.data;
157
+ if (!data) {
158
+ throw new Error("No data returned from server");
159
+ }
160
+ if (enable) setModalOpen(true);
161
+ setUri(data?.uri || null);
162
+ setSecret(data?.secret || null);
163
+ setEnabled(enable ? "temp" : null);
164
+ } catch (error) {
165
+ console.error(error);
166
+ setEnabled(null);
167
+ }
168
+ };
169
+ const handleConfirm = async (e) => {
170
+ e.preventDefault();
171
+ const form = e.target;
172
+ const formData = new FormData(form);
173
+ const code = formData.get("otp");
174
+ const token = tokenHelpers.getToken();
175
+ try {
176
+ const response = await fetch("/strapi-identity/setup", {
177
+ method: "POST",
178
+ headers: { "Content-Type": "application/json", authorization: `Bearer ${token}` },
179
+ body: JSON.stringify({ code })
180
+ });
181
+ const body = await response.json();
182
+ if (!response.ok) {
183
+ throw new Error(`${response.status} - ${body.error || "Failed to set up MFA"}`);
184
+ }
185
+ if (body.data?.recoveryCodes) {
186
+ setPasscodes(body.data.recoveryCodes);
187
+ } else {
188
+ setModalOpen(false);
189
+ }
190
+ setUri(null);
191
+ setSecret(null);
192
+ setEnabled("full");
193
+ } catch (error) {
194
+ console.error(error);
195
+ }
196
+ };
197
+ const handleClose = () => {
198
+ if (!passcodes) setEnabled(null);
199
+ setModalOpen(false);
200
+ setUri(null);
201
+ setSecret(null);
202
+ setPasscodes(null);
203
+ };
204
+ const handleDisable = async (e) => {
205
+ e.preventDefault();
206
+ const form = e.target;
207
+ const formData = new FormData(form);
208
+ const code = formData.get("otp");
209
+ const token = tokenHelpers.getToken();
210
+ try {
211
+ const response = await fetch("/strapi-identity/disable", {
212
+ method: "POST",
213
+ headers: { "Content-Type": "application/json", authorization: `Bearer ${token}` },
214
+ body: JSON.stringify({ code })
215
+ });
216
+ const body = await response.json();
217
+ if (!response.ok) {
218
+ throw new Error(`${response.status} - ${body.error || "Failed to disable MFA"}`);
219
+ }
220
+ setDisableDialogOpen(false);
221
+ setUri(null);
222
+ setSecret(null);
223
+ setEnabled(null);
224
+ } catch (error) {
225
+ console.error(error);
226
+ }
227
+ };
228
+ React.useEffect(() => {
229
+ const ac = new AbortController();
230
+ (async () => {
231
+ const token = tokenHelpers.getToken();
232
+ try {
233
+ const [status, enabled2] = await Promise.all([
234
+ fetch("/strapi-identity/status", {
235
+ method: "GET",
236
+ headers: { "Content-Type": "application/json", authorization: `Bearer ${token}` },
237
+ signal: ac.signal
238
+ }),
239
+ fetch("/strapi-identity/config/enabled", {
240
+ method: "GET",
241
+ headers: { "Content-Type": "application/json", authorization: `Bearer ${token}` },
242
+ signal: ac.signal
243
+ })
244
+ ]);
245
+ const statusBody = await status.json();
246
+ const enabledBody = await enabled2.json();
247
+ if (!status.ok) {
248
+ throw new Error(`${status.status} - ${statusBody.error || "Failed to set up MFA"}`);
249
+ }
250
+ if (!enabled2.ok) {
251
+ throw new Error(`${enabled2.status} - ${enabledBody.error || "Failed to get MFA config"}`);
252
+ }
253
+ setMfaEnabled(enabledBody.data);
254
+ setEnabled(statusBody.data?.status || null);
255
+ } catch (error) {
256
+ if (error.name === "AbortError") return;
257
+ console.error("Failed to fetch MFA status:", error);
258
+ }
259
+ })();
260
+ return () => ac.abort();
261
+ }, []);
262
+ if (!mfaEnabled) return null;
263
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
264
+ /* @__PURE__ */ jsxRuntime.jsx(
265
+ designSystem.Box,
266
+ {
267
+ background: "neutral0",
268
+ hasRadius: true,
269
+ shadow: "filterShadow",
270
+ paddingTop: 6,
271
+ paddingBottom: 6,
272
+ paddingLeft: 7,
273
+ paddingRight: 7,
274
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
275
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 1, children: [
276
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", children: formatMessage({
277
+ id: index.getTranslation("profile.title"),
278
+ defaultMessage: "Two-Factor Authentication"
279
+ }) }),
280
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
281
+ id: index.getTranslation("profile.subtitle"),
282
+ defaultMessage: "Add an additional layer of security to your account."
283
+ }) })
284
+ ] }),
285
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Root, { tag: "div", gap: 5, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(
286
+ designSystem.Field.Root,
287
+ {
288
+ width: "100%",
289
+ name: "two-factor-authentication",
290
+ id: "two-factor-authentication",
291
+ children: [
292
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
293
+ id: index.getTranslation("profile.toggle_label"),
294
+ defaultMessage: "Enable Two-Factor Authentication"
295
+ }) }),
296
+ /* @__PURE__ */ jsxRuntime.jsx(
297
+ designSystem.Toggle,
298
+ {
299
+ offLabel: formatMessage({
300
+ id: "app.components.ToggleCheckbox.off-label",
301
+ defaultMessage: "False"
302
+ }),
303
+ onLabel: formatMessage({
304
+ id: "app.components.ToggleCheckbox.on-label",
305
+ defaultMessage: "True"
306
+ }),
307
+ checked: enabled !== null,
308
+ onChange: handleToggle
309
+ }
310
+ ),
311
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
312
+ ]
313
+ }
314
+ ) }) })
315
+ ] })
316
+ }
317
+ ),
318
+ /* @__PURE__ */ jsxRuntime.jsx(
319
+ ConfirmModal,
320
+ {
321
+ open: modalOpen,
322
+ onOpenChange: handleClose,
323
+ qrCodeUri: uri,
324
+ secret,
325
+ passcodes,
326
+ onSubmit: handleConfirm
327
+ }
328
+ ),
329
+ /* @__PURE__ */ jsxRuntime.jsx(
330
+ RemoveModal,
331
+ {
332
+ open: disableDialogOpen,
333
+ onOpenChange: setDisableDialogOpen,
334
+ onSubmit: handleDisable
335
+ }
336
+ )
337
+ ] });
338
+ };
339
+ exports.default = ProfileToggle;