diff --git a/src/components/Login.js b/src/components/Login.js
index 81be1d2..0fe3441 100644
--- a/src/components/Login.js
+++ b/src/components/Login.js
@@ -1,13 +1,20 @@
import React, { useState } from "react";
import { magic } from "../lib/magic.js";
+import {
+ DisableMFAEventOnReceived,
+ LoginWithEmailOTPEventOnReceived,
+ LoginWithEmailOTPEventEmit,
+} from "magic-sdk";
import EmailOTPModal from "./EmailOTPModal.js";
import EmailForm from "./EmailForm.js";
import DeviceRegistration from "./DeviceRegistration.js";
import MFAOTPModal from "./MFA/MFAOTPModal.js";
+import RecoveryCodeModal from "./RecoveryCodeModal.js";
export default function Login({ setUser }) {
const [showEmailOTPModal, setShowEmailOTPModal] = useState(false);
const [showMFAOTPModal, setShowMFAOTPModal] = useState(false);
+ const [showRecoveryCodeModal, setShowRecoveryCodeModal] = useState(false);
const [showDeviceRegistrationModal, setShowDeviceRegistrationModal] =
useState(false);
const [otpLogin, setOtpLogin] = useState();
@@ -25,22 +32,18 @@ export default function Login({ setUser }) {
otpLogin
.on("device-needs-approval", () => {
// is called when device is not recognized and requires approval
-
setShowDeviceRegistrationModal(true);
})
.on("device-approved", () => {
// is called when the device has been approved
-
setShowDeviceRegistrationModal(false);
})
.on("email-otp-sent", () => {
// The email has been sent to the user
-
setShowEmailOTPModal(true);
})
.on("done", (result) => {
handleGetMetadata();
-
console.log(`DID Token: %c${result}`, "color: orange");
})
.catch((err) => {
@@ -53,12 +56,22 @@ export default function Login({ setUser }) {
setShowEmailOTPModal(false);
setShowMFAOTPModal(false);
setShowDeviceRegistrationModal(false);
+ setShowRecoveryCodeModal(false);
})
.on("mfa-sent-handle", (mfaHandle) => {
// Display the MFA OTP modal
-
+ console.log("MFA sent handle received, showing MFA modal");
setShowEmailOTPModal(false);
setShowMFAOTPModal(true);
+ })
+ .on("recovery-code-sent-handle", () => {
+ // This is critical for the recovery flow
+ console.log(
+ "RecoveryCodeSentHandle received, showing recovery code modal"
+ );
+ setShowEmailOTPModal(false);
+ setShowMFAOTPModal(false);
+ setShowRecoveryCodeModal(true);
});
} catch (err) {
console.error(err);
@@ -67,17 +80,22 @@ export default function Login({ setUser }) {
const handleGetMetadata = async () => {
const metadata = await magic.user.getInfo();
-
setUser(metadata);
-
console.table(metadata);
};
const handleCancel = () => {
try {
- otpLogin.emit("cancel");
+ if (otpLogin) {
+ otpLogin.emit("cancel");
+ console.log("%cUser canceled login.", "color: orange");
+ }
- console.log("%cUser canceled login.", "color: orange");
+ // Reset all UI states
+ setShowEmailOTPModal(false);
+ setShowMFAOTPModal(false);
+ setShowRecoveryCodeModal(false);
+ setShowDeviceRegistrationModal(false);
} catch (err) {
console.log("Error canceling login:", err);
}
@@ -95,8 +113,12 @@ export default function Login({ setUser }) {
) : showMFAOTPModal ? (
+ ) : showRecoveryCodeModal ? (
+
) : (
-
+
+
+
)}
);
diff --git a/src/components/MFA/MFAOTPModal.js b/src/components/MFA/MFAOTPModal.js
index fa0aaf1..79e96ab 100644
--- a/src/components/MFA/MFAOTPModal.js
+++ b/src/components/MFA/MFAOTPModal.js
@@ -1,4 +1,5 @@
import React, { useState } from "react";
+import { LoginWithEmailOTPEventEmit } from "magic-sdk";
export default function MFAOTPModal({ handle, handleCancel }) {
const [passcode, setPasscode] = useState("");
@@ -27,9 +28,8 @@ export default function MFAOTPModal({ handle, handleCancel }) {
setDisabled(false);
if (!retries) {
- setMessage("No more retries. Please try again later.");
-
- handleCancel();
+ setMessage("No more retries. Please try recovery flow.");
+ // Instead of canceling, offer recovery option
} else {
// Prompt the user again for the MFA OTP
setMessage(
@@ -41,9 +41,30 @@ export default function MFAOTPModal({ handle, handleCancel }) {
});
};
+ const handleLostDevice = () => {
+ // This is the key function that initiates the recovery flow
+ console.log("Initiating recovery flow due to lost device");
+ try {
+ // Emit the LostDevice event to trigger the recovery flow
+ console.log("Emitting LostDevice event");
+ handle.emit(LoginWithEmailOTPEventEmit.LostDevice);
+
+ // RecoveryCodeSentHandle event will be handled by the parent component
+ // which will show the RecoveryCodeModal
+ console.log("Waiting for RecoveryCodeSentHandle event");
+
+ // Disable the UI while we wait
+ setDisabled(true);
+ setMessage("Initiating recovery flow... Please wait.");
+ } catch (error) {
+ console.error("Error initiating recovery flow:", error);
+ setDisabled(false);
+ }
+ };
+
return (
-
enter the code from your authenticator app
+
Enter the code from your authenticator app
{message && (
@@ -71,7 +92,7 @@ export default function MFAOTPModal({ handle, handleCancel }) {
}}
disabled={disabled}
>
- cancel
+ Cancel
+
+
+
+
);
}
diff --git a/src/components/RecoveryCodeModal.js b/src/components/RecoveryCodeModal.js
new file mode 100644
index 0000000..11d8f1d
--- /dev/null
+++ b/src/components/RecoveryCodeModal.js
@@ -0,0 +1,112 @@
+import React, { useState, useEffect } from "react";
+import {
+ LoginWithEmailOTPEventEmit,
+ LoginWithEmailOTPEventOnReceived,
+} from "magic-sdk";
+
+export default function RecoveryCodeModal({ handle, handleCancel }) {
+ const [recoveryCode, setRecoveryCode] = useState("");
+ const [message, setMessage] = useState("");
+ const [disabled, setDisabled] = useState(false);
+
+ // Add listener for InvalidRecoveryCode event
+ useEffect(() => {
+ if (!handle) return;
+
+ const invalidCodeListener = () => {
+ console.log("Invalid recovery code received");
+ setDisabled(false);
+ setMessage("Invalid recovery code. Please try again.");
+ };
+
+ // Register listener for InvalidRecoveryCode event
+ handle.on(
+ LoginWithEmailOTPEventOnReceived.InvalidRecoveryCode,
+ invalidCodeListener
+ );
+
+ // Clean up the listener when component unmounts
+ return () => {
+ handle.off(
+ LoginWithEmailOTPEventOnReceived.InvalidRecoveryCode,
+ invalidCodeListener
+ );
+ };
+ }, [handle]);
+
+ // This component is only shown after RecoveryCodeSentHandle event has been received,
+ // so we can directly submit the recovery code when the user clicks Submit.
+
+ const handleSubmit = async (e) => {
+ if (e) e.preventDefault();
+
+ if (!recoveryCode.trim()) {
+ setMessage("Please enter your recovery code");
+ return;
+ }
+
+ setDisabled(true);
+ setMessage("Verifying recovery code...");
+
+ try {
+ // The lost device event has already been emitted and RecoveryCodeSentHandle
+ // has already been received (that's why this modal is showing),
+ // so we can directly submit the recovery code.
+ console.log("Submitting recovery code for verification");
+ handle.emit(LoginWithEmailOTPEventEmit.VerifyRecoveryCode, recoveryCode);
+
+ // Reset the input field
+ setRecoveryCode("");
+ } catch (error) {
+ console.error("Error submitting recovery code:", error);
+ setDisabled(false);
+ setMessage("Error submitting recovery code. Please try again.");
+ }
+ };
+
+ return (
+
+
Enter your recovery code
+
Please enter the recovery code you received during MFA setup.
+
+ {message && (
+
+ {message}
+
+ )}
+
+
+
+
+
+
+
+
+ );
+}