strapi-identity 0.1.2 → 0.3.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 (88) hide show
  1. package/README.md +2 -1
  2. package/dist/admin/{AdminReset-H453s-DE.mjs → AdminReset-CCGgw-0k.mjs} +2 -2
  3. package/dist/admin/{AdminReset-C0QibZXW.js → AdminReset-QClQP2Il.js} +2 -2
  4. package/dist/admin/{ProfileToggle-BFmIWCrN.js → ProfileToggle-H31GHNDA.js} +299 -39
  5. package/dist/admin/{ProfileToggle-DQeXCx34.mjs → ProfileToggle-gPuH6dGP.mjs} +299 -39
  6. package/dist/admin/{SettingsPage-OwMik_IK.mjs → SettingsPage-DgPikS3m.mjs} +344 -102
  7. package/dist/admin/{SettingsPage-DyF7YbsX.js → SettingsPage-DulEjadb.js} +343 -101
  8. package/dist/admin/{ar-i2eiMZkz.js → ar-BYnI7Tsa.js} +36 -23
  9. package/dist/admin/{ar-BXaam37U.mjs → ar-DwZqj0qM.mjs} +36 -23
  10. package/dist/admin/{ca-DZ9DbEcQ.mjs → ca-aKVVc8iQ.mjs} +36 -23
  11. package/dist/admin/{ca-BVpGzY8r.js → ca-sBRHuaFU.js} +36 -23
  12. package/dist/admin/{cs-Gok16KLy.mjs → cs--prflMHS.mjs} +36 -23
  13. package/dist/admin/{cs-_PZVkwt0.js → cs-gU7KP3Lx.js} +36 -23
  14. package/dist/admin/de-BT25lv_6.mjs +49 -0
  15. package/dist/admin/de-CrlCAUuf.js +49 -0
  16. package/dist/admin/{dk-B7EOsAdU.js → dk-BNC3WUzY.js} +36 -23
  17. package/dist/admin/{dk-CI64Xmli.mjs → dk-Ck3AQYU7.mjs} +36 -23
  18. package/dist/admin/{en-B_vJwdfS.mjs → en-9qzlpde0.mjs} +36 -23
  19. package/dist/admin/{en-D4KP9t1Y.js → en-DBj0AD5g.js} +36 -23
  20. package/dist/admin/{es-CHwF7YK-.js → es-D5Sn41_H.js} +36 -23
  21. package/dist/admin/{es-CqJcXo4j.mjs → es-lh6XoPb7.mjs} +36 -23
  22. package/dist/admin/{eu-D-snytN8.mjs → eu-Cuz6ijBX.mjs} +36 -23
  23. package/dist/admin/{eu-DvdbwE5E.js → eu-Qr3RvDPW.js} +36 -23
  24. package/dist/admin/fr-C4pmkPYn.js +49 -0
  25. package/dist/admin/fr-ChlDcZsG.mjs +49 -0
  26. package/dist/admin/{gu-3wJbbAmw.mjs → gu-B6zyD1bW.mjs} +36 -23
  27. package/dist/admin/{gu-D2LgVfMp.js → gu-BMZL76zM.js} +36 -23
  28. package/dist/admin/{he-Bjv7eygt.mjs → he-C5V-qZCX.mjs} +36 -23
  29. package/dist/admin/{he-DnhYpbvN.js → he-H6iBa45A.js} +36 -23
  30. package/dist/admin/{hi-DDD2E3A3.js → hi-Be8rPk7I.js} +36 -23
  31. package/dist/admin/{hi-CNiDezU7.mjs → hi-czhOWo6-.mjs} +36 -23
  32. package/dist/admin/{hu-C1_YkZHU.js → hu-DKp6kOmc.js} +36 -23
  33. package/dist/admin/{hu-aLaIWmGw.mjs → hu-NbZ3aiYV.mjs} +36 -23
  34. package/dist/admin/{id-u3wVE6Rv.js → id-DO0bwFgY.js} +36 -23
  35. package/dist/admin/{id-C8WRgGm1.mjs → id-NH9PvcR5.mjs} +36 -23
  36. package/dist/admin/{index-BXZI8nMZ.js → index-C9K2h5UC.js} +65 -12
  37. package/dist/admin/{index-D45I6rWF.mjs → index-C_4USMnn.mjs} +65 -12
  38. package/dist/admin/index.js +1 -1
  39. package/dist/admin/index.mjs +1 -1
  40. package/dist/admin/{it-CjoRoJj1.mjs → it-Cmrey6tg.mjs} +36 -23
  41. package/dist/admin/{it-CDw6dG9Z.js → it-Df6-7-M7.js} +36 -23
  42. package/dist/admin/{ja-CewucIUY.mjs → ja-DH3KMqOL.mjs} +36 -23
  43. package/dist/admin/{ja-CbMXy2ym.js → ja-HuAq9ZwT.js} +36 -23
  44. package/dist/admin/{ko-D-kAxDtd.mjs → ko-DPN28RE8.mjs} +36 -23
  45. package/dist/admin/{ko-BEtJPpfJ.js → ko-S9k8KA8K.js} +36 -23
  46. package/dist/admin/{ml-0fR2_MmA.js → ml-Bh9GGqcW.js} +36 -23
  47. package/dist/admin/{ml-DR3AaofF.mjs → ml-MsHNacm6.mjs} +36 -23
  48. package/dist/admin/{ms-COHLS5e5.mjs → ms-TjHAaxTd.mjs} +36 -23
  49. package/dist/admin/{ms-DLvuGSlk.js → ms-hO5YeEg4.js} +36 -23
  50. package/dist/admin/{nl-wj6kn642.js → nl-BF98NBwL.js} +36 -23
  51. package/dist/admin/{nl-DVtHsM2H.mjs → nl-BLILZU8-.mjs} +36 -23
  52. package/dist/admin/{no-D_0yjyCy.mjs → no-BtVZ-siy.mjs} +36 -23
  53. package/dist/admin/{no-DVBgWt8q.js → no-bl1OXlfa.js} +36 -23
  54. package/dist/admin/{pl-C3GNxjVX.mjs → pl-DCSB6LwZ.mjs} +36 -23
  55. package/dist/admin/{pl-B2ghisbC.js → pl-DCnOWIDw.js} +36 -23
  56. package/dist/admin/{pt-BR-BbKV8YoX.mjs → pt-BR-CeLqmj88.mjs} +36 -23
  57. package/dist/admin/{pt-BR-CfgNaB1-.js → pt-BR-D2_UrxTp.js} +36 -23
  58. package/dist/admin/{pt-DKe8rRWa.js → pt-DIu8RT_X.js} +36 -23
  59. package/dist/admin/{pt-z4K3cCjf.mjs → pt-fgjdOyW5.mjs} +36 -23
  60. package/dist/admin/{ru-C85izLFa.mjs → ru-B_hlpAyP.mjs} +36 -23
  61. package/dist/admin/{ru-BFSm68HC.js → ru-BccMCf0l.js} +36 -23
  62. package/dist/admin/{sa-B1XoTTrE.mjs → sa-BtuJ_I1t.mjs} +36 -23
  63. package/dist/admin/{sa-BOPaqylt.js → sa-D3A-fo85.js} +36 -23
  64. package/dist/admin/{sk-C48lUPuC.mjs → sk-mmuTFlCK.mjs} +36 -23
  65. package/dist/admin/{sk-Dd-S1612.js → sk-uSLC6KhO.js} +36 -23
  66. package/dist/admin/{sv-BLma_kJl.mjs → sv-BlaHc5ax.mjs} +36 -23
  67. package/dist/admin/{sv-lg64Cw78.js → sv-CuKk5tE-.js} +36 -23
  68. package/dist/admin/{th-DPbm5NrX.js → th-Bv3NKkYO.js} +36 -23
  69. package/dist/admin/{th-BJEu5n7q.mjs → th-BwyhFaeE.mjs} +36 -23
  70. package/dist/admin/{tokenHelpers-jtoRu0q5.js → tokenHelpers-B54WRTn1.js} +4 -10
  71. package/dist/admin/{tokenHelpers-DagDzpso.mjs → tokenHelpers-CKVyT1sz.mjs} +4 -10
  72. package/dist/admin/{tr-DkIUODKq.mjs → tr-BLocNlbZ.mjs} +36 -23
  73. package/dist/admin/{tr-Bm1QZr4v.js → tr-Bmvs-Hx-.js} +36 -23
  74. package/dist/admin/{uk-FARzIGx4.js → uk-BDxn-EZU.js} +36 -23
  75. package/dist/admin/{uk-D7ArtSe3.mjs → uk-CyZ10xtq.mjs} +36 -23
  76. package/dist/admin/{vi-DS0yslPP.mjs → vi-Bx_UJ8up.mjs} +36 -23
  77. package/dist/admin/{vi-Bi9B6eTY.js → vi-F_mqQCme.js} +36 -23
  78. package/dist/admin/{zh-DkEx28ZA.js → zh-CFZJPG5N.js} +36 -23
  79. package/dist/admin/{zh-DwCvIPSz.mjs → zh-CjJdRa3l.mjs} +36 -23
  80. package/dist/admin/{zh-Hans-BwwKCR6_.js → zh-Hans-4BhSwSQw.js} +36 -23
  81. package/dist/admin/{zh-Hans-DP2xZyda.mjs → zh-Hans-s7G2GUHU.mjs} +36 -23
  82. package/dist/server/index.js +487 -50
  83. package/dist/server/index.mjs +487 -50
  84. package/package.json +5 -4
  85. package/dist/admin/de-BuYn1AYX.mjs +0 -26
  86. package/dist/admin/de-GItli7en.js +0 -26
  87. package/dist/admin/fr-Bt6sS5GX.mjs +0 -26
  88. package/dist/admin/fr-CbCW6hVD.js +0 -26
package/README.md CHANGED
@@ -7,6 +7,7 @@ Detailed Multi-Factor Authentication (MFA) plugin for Strapi v5+. Secure your St
7
7
  - **MFA Login Interception**: Seamlessly integrates with the default Strapi login flow.
8
8
  - **TOTP Compatibility**: Works with all major authenticator apps (Google Authenticator, Authy, 1Password, etc.).
9
9
  - **Recovery Codes**: Generates secure recovery codes for emergency access.
10
+ - **Email Passcode**: Option to receive a one-time passcode via email as an alternative MFA method.
10
11
  - **Native UI Integration**:
11
12
  - Matches Strapi's design system.
12
13
  - Profile integration for easy setup.
@@ -114,5 +115,5 @@ Below is the implementation status of planned features.
114
115
  - [x] **Custom Issuer**: Configurable app label.
115
116
  - [x] **Multi-language Support**: i18n ready.
116
117
  - [x] **Admin Reset**: Allow super-admins to reset MFA for other users who lost access.
117
- - [ ] **Email Passcode**: Alternative MFA method via Email.
118
+ - [x] **Email Passcode**: Alternative MFA method via Email.
118
119
  - [ ] **Enforced Mode**: Mandatory MFA for specific roles or all users.
@@ -2,8 +2,8 @@ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from "react";
3
3
  import { W as WarningAlert } from "./WarningAlert-VU011LVF.mjs";
4
4
  import { Box, Flex, Typography, Grid, Button } from "@strapi/design-system";
5
- import { g as getTranslation } from "./index-D45I6rWF.mjs";
6
- import { g as getToken } from "./tokenHelpers-DagDzpso.mjs";
5
+ import { g as getTranslation } from "./index-C_4USMnn.mjs";
6
+ import { g as getToken } from "./tokenHelpers-CKVyT1sz.mjs";
7
7
  import { useIntl } from "react-intl";
8
8
  const AdminReset = ({ id }) => {
9
9
  const { formatMessage } = useIntl();
@@ -4,8 +4,8 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const React = require("react");
5
5
  const WarningAlert = require("./WarningAlert-DFE5euMk.js");
6
6
  const designSystem = require("@strapi/design-system");
7
- const index = require("./index-BXZI8nMZ.js");
8
- const tokenHelpers = require("./tokenHelpers-jtoRu0q5.js");
7
+ const index = require("./index-C9K2h5UC.js");
8
+ const tokenHelpers = require("./tokenHelpers-B54WRTn1.js");
9
9
  const reactIntl = require("react-intl");
10
10
  const AdminReset = ({ id }) => {
11
11
  const { formatMessage } = reactIntl.useIntl();
@@ -4,10 +4,10 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const React = require("react");
5
5
  const designSystem = require("@strapi/design-system");
6
6
  const styled = require("styled-components");
7
- const index = require("./index-BXZI8nMZ.js");
7
+ const index = require("./index-C9K2h5UC.js");
8
8
  const qrcode_react = require("qrcode.react");
9
9
  const reactIntl = require("react-intl");
10
- const tokenHelpers = require("./tokenHelpers-jtoRu0q5.js");
10
+ const tokenHelpers = require("./tokenHelpers-B54WRTn1.js");
11
11
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
12
12
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
13
13
  function ConfirmModal({
@@ -124,11 +124,155 @@ function RemoveModal({ open, onOpenChange, onSubmit }) {
124
124
  ] })
125
125
  ] }) }) });
126
126
  }
127
+ function EmailOTPModal({
128
+ mode,
129
+ open,
130
+ email,
131
+ onOpenChange,
132
+ onSuccess
133
+ }) {
134
+ const { formatMessage } = reactIntl.useIntl();
135
+ const [step, setStep] = React.useState("send");
136
+ const [loading, setLoading] = React.useState(false);
137
+ const [error, setError] = React.useState(null);
138
+ const handleOpenChange = (nextOpen) => {
139
+ if (!nextOpen) {
140
+ setStep("send");
141
+ setError(null);
142
+ }
143
+ onOpenChange(nextOpen);
144
+ };
145
+ const handleSend = async () => {
146
+ const token = tokenHelpers.getToken();
147
+ setLoading(true);
148
+ setError(null);
149
+ try {
150
+ const endpoint = mode === "setup" ? "/strapi-identity/enable-email" : "/strapi-identity/disable-email/request";
151
+ const response = await fetch(endpoint, {
152
+ method: "POST",
153
+ headers: { "Content-Type": "application/json", authorization: `Bearer ${token}` }
154
+ });
155
+ const body = await response.json();
156
+ if (!response.ok) {
157
+ throw new Error(body.error || "Failed to send verification email");
158
+ }
159
+ setStep("confirm");
160
+ } catch (err) {
161
+ setError(err.message);
162
+ } finally {
163
+ setLoading(false);
164
+ }
165
+ };
166
+ const handleConfirm = async (e) => {
167
+ e.preventDefault();
168
+ const token = tokenHelpers.getToken();
169
+ const formData = new FormData(e.target);
170
+ const code = formData.get("otp");
171
+ setLoading(true);
172
+ setError(null);
173
+ try {
174
+ const endpoint = mode === "setup" ? "/strapi-identity/setup-email" : "/strapi-identity/disable";
175
+ const response = await fetch(endpoint, {
176
+ method: "POST",
177
+ headers: { "Content-Type": "application/json", authorization: `Bearer ${token}` },
178
+ body: JSON.stringify({ code })
179
+ });
180
+ const body = await response.json();
181
+ if (!response.ok) {
182
+ throw new Error(body.error || "Invalid or expired code");
183
+ }
184
+ handleOpenChange(false);
185
+ onSuccess();
186
+ } catch (err) {
187
+ setError(err.message);
188
+ } finally {
189
+ setLoading(false);
190
+ }
191
+ };
192
+ const title = mode === "setup" ? formatMessage({
193
+ id: index.getTranslation("email_otp.setup_title"),
194
+ defaultMessage: "Enable Email OTP"
195
+ }) : formatMessage({
196
+ id: index.getTranslation("email_otp.disable_title"),
197
+ defaultMessage: "Disable Email OTP"
198
+ });
199
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
200
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: title }) }),
201
+ step === "send" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
202
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "center", gap: 4, marginTop: 4, marginBottom: 4, children: [
203
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textAlign: "center", children: mode === "setup" ? formatMessage(
204
+ {
205
+ id: index.getTranslation("email_otp.setup_description"),
206
+ defaultMessage: "We'll send a 6-digit verification code to {email}. Enter it to enable Email OTP."
207
+ },
208
+ { email: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: email }) }
209
+ ) : formatMessage(
210
+ {
211
+ id: index.getTranslation("email_otp.disable_description"),
212
+ defaultMessage: "We'll send a 6-digit verification code to {email}. Enter it to disable Email OTP."
213
+ },
214
+ { email: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: email }) }
215
+ ) }),
216
+ error ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { role: "alert", textColor: "danger600", textAlign: "center", children: error }) : null
217
+ ] }) }),
218
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
219
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: formatMessage({ id: "app.components.Button.cancel", defaultMessage: "Cancel" }) }) }),
220
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleSend, loading, children: formatMessage({
221
+ id: index.getTranslation("email_otp.send_code"),
222
+ defaultMessage: "Send verification email"
223
+ }) })
224
+ ] })
225
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleConfirm, children: [
226
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "center", gap: 4, marginTop: 4, marginBottom: 4, children: [
227
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textAlign: "center", children: formatMessage(
228
+ {
229
+ id: index.getTranslation("email_otp.confirm_description"),
230
+ defaultMessage: "Enter the 6-digit code sent to {email}."
231
+ },
232
+ { email: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: email }) }
233
+ ) }),
234
+ /* @__PURE__ */ jsxRuntime.jsxs(index.InputOTP, { maxLength: 6, name: "otp", id: "otp", autoFocus: true, children: [
235
+ /* @__PURE__ */ jsxRuntime.jsxs(index.InputOTPGroup, { children: [
236
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 0 }),
237
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 1 }),
238
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 2 })
239
+ ] }),
240
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSeparator, {}),
241
+ /* @__PURE__ */ jsxRuntime.jsxs(index.InputOTPGroup, { children: [
242
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 3 }),
243
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 4 }),
244
+ /* @__PURE__ */ jsxRuntime.jsx(index.InputOTPSlot, { index: 5 })
245
+ ] })
246
+ ] }),
247
+ error ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { role: "alert", textColor: "danger600", textAlign: "center", children: error }) : null,
248
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", type: "button", onClick: () => handleSend(), children: formatMessage({
249
+ id: index.getTranslation("email_otp.resend_code"),
250
+ defaultMessage: "Resend code"
251
+ }) })
252
+ ] }) }),
253
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
254
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: formatMessage({
255
+ id: "app.components.Button.cancel",
256
+ defaultMessage: "Cancel"
257
+ }) }) }),
258
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { type: "submit", loading, children: formatMessage({
259
+ id: "app.components.Button.confirm",
260
+ defaultMessage: "Confirm"
261
+ }) })
262
+ ] })
263
+ ] })
264
+ ] }) });
265
+ }
127
266
  const ProfileToggle = () => {
128
267
  const { formatMessage } = reactIntl.useIntl();
129
268
  const [enabled, setEnabled] = React.useState(null);
269
+ const [mfaType, setMfaType] = React.useState(null);
130
270
  const [mfaEnabled, setMfaEnabled] = React.useState(false);
271
+ const [emailConfigured, setEmailConfigured] = React.useState(false);
272
+ const [userEmail, setUserEmail] = React.useState("");
131
273
  const [disableDialogOpen, setDisableDialogOpen] = React.useState(false);
274
+ const [emailSetupOpen, setEmailSetupOpen] = React.useState(false);
275
+ const [emailDisableOpen, setEmailDisableOpen] = React.useState(false);
132
276
  const [modalOpen, setModalOpen] = React.useState(false);
133
277
  const [uri, setUri] = React.useState(null);
134
278
  const [secret, setSecret] = React.useState(null);
@@ -140,6 +284,9 @@ const ProfileToggle = () => {
140
284
  setDisableDialogOpen(true);
141
285
  return;
142
286
  }
287
+ if (enable && enabled === "full" && mfaType === "email") {
288
+ return;
289
+ }
143
290
  try {
144
291
  const response = await fetch("/strapi-identity/enable", {
145
292
  method: "POST",
@@ -158,11 +305,25 @@ const ProfileToggle = () => {
158
305
  setUri(data?.uri || null);
159
306
  setSecret(data?.secret || null);
160
307
  setEnabled(enable ? "temp" : null);
308
+ if (enable) setMfaType("totp");
161
309
  } catch (error) {
162
310
  console.error(error);
163
311
  setEnabled(null);
164
312
  }
165
313
  };
314
+ const handleEmailToggle = ({ target }) => {
315
+ const enable = target?.checked || false;
316
+ if (!enable && enabled === "full" && mfaType === "email") {
317
+ setEmailDisableOpen(true);
318
+ return;
319
+ }
320
+ if (enable && enabled === "full") {
321
+ return;
322
+ }
323
+ if (enable) {
324
+ setEmailSetupOpen(true);
325
+ }
326
+ };
166
327
  const handleConfirm = async (e) => {
167
328
  e.preventDefault();
168
329
  const form = e.target;
@@ -187,6 +348,7 @@ const ProfileToggle = () => {
187
348
  setUri(null);
188
349
  setSecret(null);
189
350
  setEnabled("full");
351
+ setMfaType("totp");
190
352
  } catch (error) {
191
353
  console.error(error);
192
354
  }
@@ -218,6 +380,7 @@ const ProfileToggle = () => {
218
380
  setUri(null);
219
381
  setSecret(null);
220
382
  setEnabled(null);
383
+ setMfaType(null);
221
384
  } catch (error) {
222
385
  console.error(error);
223
386
  }
@@ -227,7 +390,7 @@ const ProfileToggle = () => {
227
390
  (async () => {
228
391
  const token = tokenHelpers.getToken();
229
392
  try {
230
- const [status, enabled2] = await Promise.all([
393
+ const [statusRes, enabledRes, meRes] = await Promise.all([
231
394
  fetch("/strapi-identity/status", {
232
395
  method: "GET",
233
396
  headers: { "Content-Type": "application/json", authorization: `Bearer ${token}` },
@@ -237,18 +400,37 @@ const ProfileToggle = () => {
237
400
  method: "GET",
238
401
  headers: { "Content-Type": "application/json", authorization: `Bearer ${token}` },
239
402
  signal: ac.signal
403
+ }),
404
+ fetch("/admin/users/me", {
405
+ method: "GET",
406
+ headers: { "Content-Type": "application/json", authorization: `Bearer ${token}` },
407
+ signal: ac.signal
240
408
  })
241
409
  ]);
242
- const statusBody = await status.json();
243
- const enabledBody = await enabled2.json();
244
- if (!status.ok) {
245
- throw new Error(`${status.status} - ${statusBody.error || "Failed to set up MFA"}`);
410
+ const statusBody = await statusRes.json();
411
+ const enabledBody = await enabledRes.json();
412
+ if (!statusRes.ok) {
413
+ throw new Error(`${statusRes.status} - ${statusBody.error || "Failed to get MFA status"}`);
246
414
  }
247
- if (!enabled2.ok) {
248
- throw new Error(`${enabled2.status} - ${enabledBody.error || "Failed to get MFA config"}`);
415
+ if (!enabledRes.ok) {
416
+ throw new Error(`${enabledRes.status} - ${enabledBody.error || "Failed to get MFA config"}`);
249
417
  }
250
418
  setMfaEnabled(enabledBody.data);
251
419
  setEnabled(statusBody.data?.status || null);
420
+ setMfaType(statusBody.data?.type || null);
421
+ if (meRes.ok) {
422
+ const meBody = await meRes.json();
423
+ setUserEmail(meBody.data?.email || "");
424
+ const configRes = await fetch("/strapi-identity/config", {
425
+ method: "GET",
426
+ headers: { "Content-Type": "application/json", authorization: `Bearer ${token}` },
427
+ signal: ac.signal
428
+ });
429
+ if (configRes.ok) {
430
+ const configBody = await configRes.json();
431
+ setEmailConfigured(!!configBody.data?.email_enabled);
432
+ }
433
+ }
252
434
  } catch (error) {
253
435
  if (error.name === "AbortError") return;
254
436
  console.error("Failed to fetch MFA status:", error);
@@ -257,6 +439,10 @@ const ProfileToggle = () => {
257
439
  return () => ac.abort();
258
440
  }, []);
259
441
  if (!mfaEnabled) return null;
442
+ const totpChecked = enabled !== null && mfaType === "totp";
443
+ const emailChecked = enabled !== null && mfaType === "email";
444
+ const totpDisabled = enabled !== null && mfaType === "email";
445
+ const emailDisabled = enabled !== null && mfaType === "totp";
260
446
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
261
447
  /* @__PURE__ */ jsxRuntime.jsx(
262
448
  designSystem.Box,
@@ -279,36 +465,78 @@ const ProfileToggle = () => {
279
465
  defaultMessage: "Add an additional layer of security to your account."
280
466
  }) })
281
467
  ] }),
282
- /* @__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(
283
- designSystem.Field.Root,
284
- {
285
- width: "100%",
286
- name: "two-factor-authentication",
287
- id: "two-factor-authentication",
288
- children: [
289
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
290
- id: index.getTranslation("profile.toggle_label"),
291
- defaultMessage: "Enable Two-Factor Authentication"
292
- }) }),
293
- /* @__PURE__ */ jsxRuntime.jsx(
294
- designSystem.Toggle,
295
- {
296
- offLabel: formatMessage({
297
- id: "app.components.ToggleCheckbox.off-label",
298
- defaultMessage: "False"
299
- }),
300
- onLabel: formatMessage({
301
- id: "app.components.ToggleCheckbox.on-label",
302
- defaultMessage: "True"
303
- }),
304
- checked: enabled !== null,
305
- onChange: handleToggle
306
- }
307
- ),
308
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
309
- ]
310
- }
311
- ) }) })
468
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { tag: "div", gap: 5, children: [
469
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(
470
+ designSystem.Field.Root,
471
+ {
472
+ width: "100%",
473
+ name: "two-factor-authentication",
474
+ id: "two-factor-authentication",
475
+ hint: totpDisabled ? formatMessage({
476
+ id: index.getTranslation("profile.totp_disabled_hint"),
477
+ defaultMessage: "Disable Email OTP first to enable the authenticator app."
478
+ }) : void 0,
479
+ children: [
480
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
481
+ id: index.getTranslation("profile.toggle_label"),
482
+ defaultMessage: "Enable Two-Factor Authentication"
483
+ }) }),
484
+ /* @__PURE__ */ jsxRuntime.jsx(
485
+ designSystem.Toggle,
486
+ {
487
+ offLabel: formatMessage({
488
+ id: "app.components.ToggleCheckbox.off-label",
489
+ defaultMessage: "False"
490
+ }),
491
+ onLabel: formatMessage({
492
+ id: "app.components.ToggleCheckbox.on-label",
493
+ defaultMessage: "True"
494
+ }),
495
+ checked: totpChecked,
496
+ onChange: handleToggle,
497
+ disabled: totpDisabled
498
+ }
499
+ ),
500
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
501
+ ]
502
+ }
503
+ ) }),
504
+ emailConfigured && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(
505
+ designSystem.Field.Root,
506
+ {
507
+ width: "100%",
508
+ name: "email-otp",
509
+ id: "email-otp",
510
+ hint: emailDisabled ? formatMessage({
511
+ id: index.getTranslation("profile.email_otp_disabled_hint"),
512
+ defaultMessage: "Disable the authenticator app first to enable Email OTP."
513
+ }) : void 0,
514
+ children: [
515
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
516
+ id: index.getTranslation("profile.email_otp_label"),
517
+ defaultMessage: "Enable Email OTP"
518
+ }) }),
519
+ /* @__PURE__ */ jsxRuntime.jsx(
520
+ designSystem.Toggle,
521
+ {
522
+ offLabel: formatMessage({
523
+ id: "app.components.ToggleCheckbox.off-label",
524
+ defaultMessage: "False"
525
+ }),
526
+ onLabel: formatMessage({
527
+ id: "app.components.ToggleCheckbox.on-label",
528
+ defaultMessage: "True"
529
+ }),
530
+ checked: emailChecked,
531
+ onChange: handleEmailToggle,
532
+ disabled: emailDisabled
533
+ }
534
+ ),
535
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
536
+ ]
537
+ }
538
+ ) })
539
+ ] })
312
540
  ] })
313
541
  }
314
542
  ),
@@ -330,6 +558,38 @@ const ProfileToggle = () => {
330
558
  onOpenChange: setDisableDialogOpen,
331
559
  onSubmit: handleDisable
332
560
  }
561
+ ),
562
+ /* @__PURE__ */ jsxRuntime.jsx(
563
+ EmailOTPModal,
564
+ {
565
+ mode: "setup",
566
+ open: emailSetupOpen,
567
+ email: userEmail,
568
+ onOpenChange: (open) => {
569
+ if (!open) setEmailSetupOpen(false);
570
+ },
571
+ onSuccess: () => {
572
+ setEnabled("full");
573
+ setMfaType("email");
574
+ setEmailSetupOpen(false);
575
+ }
576
+ }
577
+ ),
578
+ /* @__PURE__ */ jsxRuntime.jsx(
579
+ EmailOTPModal,
580
+ {
581
+ mode: "disable",
582
+ open: emailDisableOpen,
583
+ email: userEmail,
584
+ onOpenChange: (open) => {
585
+ if (!open) setEmailDisableOpen(false);
586
+ },
587
+ onSuccess: () => {
588
+ setEnabled(null);
589
+ setMfaType(null);
590
+ setEmailDisableOpen(false);
591
+ }
592
+ }
333
593
  )
334
594
  ] });
335
595
  };