Addressed PR comments

This commit is contained in:
Carlos Monastyrski
2025-09-23 09:27:14 -03:00
parent e57cd54ead
commit ba277a057e
3 changed files with 28 additions and 26 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => {
const cfg = getConfig(); const cfg = getConfig();
server.decorateRequest("mfa", null); server.decorateRequest("mfa", null);
server.addHook("preParsing", async (req, res) => { server.addHook("preValidation", async (req, res) => {
const authorizationHeader = req.headers.authorization; const authorizationHeader = req.headers.authorization;
if (!authorizationHeader || !authorizationHeader.startsWith("Bearer ")) { if (!authorizationHeader || !authorizationHeader.startsWith("Bearer ")) {
+24 -13
View File
@@ -7,7 +7,7 @@ import Error from "@app/components/basic/Error";
import TotpRegistration from "@app/components/mfa/TotpRegistration"; import TotpRegistration from "@app/components/mfa/TotpRegistration";
import SecurityClient from "@app/components/utilities/SecurityClient"; import SecurityClient from "@app/components/utilities/SecurityClient";
import { Button } from "@app/components/v2"; import { Button } from "@app/components/v2";
import { useSendMfaToken } from "@app/hooks/api"; import { useLogoutUser, useSendMfaToken } from "@app/hooks/api";
import { checkUserTotpMfa, verifyMfaToken, verifyRecoveryCode } from "@app/hooks/api/auth/queries"; import { checkUserTotpMfa, verifyMfaToken, verifyRecoveryCode } from "@app/hooks/api/auth/queries";
import { MfaMethod } from "@app/hooks/api/auth/types"; import { MfaMethod } from "@app/hooks/api/auth/types";
@@ -66,6 +66,7 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
const [isLoadingResend, setIsLoadingResend] = useState(false); const [isLoadingResend, setIsLoadingResend] = useState(false);
const [triesLeft, setTriesLeft] = useState<number | undefined>(undefined); const [triesLeft, setTriesLeft] = useState<number | undefined>(undefined);
const [shouldShowTotpRegistration, setShouldShowTotpRegistration] = useState(false); const [shouldShowTotpRegistration, setShouldShowTotpRegistration] = useState(false);
const logout = useLogoutUser(true);
const sendMfaToken = useSendMfaToken(); const sendMfaToken = useSendMfaToken();
@@ -80,10 +81,18 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
} }
}, []); }, []);
const getExpectedCodeLength = () => {
if (method === MfaMethod.EMAIL) return 6;
if (method === MfaMethod.TOTP) return showRecoveryCodeInput ? 8 : 6;
return 6;
};
const isCodeComplete = mfaCode.length === getExpectedCodeLength();
const verifyMfa = async (event: React.FormEvent<HTMLFormElement>) => { const verifyMfa = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
if (!mfaCode.trim()) return; if (!mfaCode.trim() || !isCodeComplete) return;
setIsLoading(true); setIsLoading(true);
try { try {
@@ -107,16 +116,18 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
closeMfa(); closeMfa();
} }
} catch { } catch {
if (triesLeft) { if (typeof triesLeft === "number") {
setTriesLeft((left) => { const newTriesLeft = triesLeft - 1;
if (triesLeft === 1) { setTriesLeft(newTriesLeft);
navigate({ to: "/" }); if (newTriesLeft <= 0) {
setIsLoading(false);
SecurityClient.setMfaToken(""); SecurityClient.setMfaToken("");
SecurityClient.setToken(""); SecurityClient.setToken("");
} SecurityClient.setSignupToken("");
return (left as number) - 1; await logout.mutateAsync();
}); navigate({ to: "/login" });
return;
}
} else { } else {
setTriesLeft(2); setTriesLeft(2);
} }
@@ -251,6 +262,7 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
colorSchema="primary" colorSchema="primary"
variant="outline_bg" variant="outline_bg"
isLoading={isLoading} isLoading={isLoading}
isDisabled={!isCodeComplete}
> >
{String(t("mfa.verify"))} {String(t("mfa.verify"))}
</Button> </Button>
@@ -263,7 +275,6 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
onClick={() => { onClick={() => {
setShowRecoveryCodeInput(!showRecoveryCodeInput); setShowRecoveryCodeInput(!showRecoveryCodeInput);
setMfaCode(""); setMfaCode("");
setTriesLeft(undefined);
}} }}
className="text-bunker-400 transition-colors duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4" className="text-bunker-400 transition-colors duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4"
> >
@@ -21,16 +21,7 @@ export const RecoveryCodesDownload = ({
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const downloadRecoveryCodes = () => { const downloadRecoveryCodes = () => {
const content = [ const content = [...recoveryCodes].join("\n");
"Infisical Two-Factor Authentication Recovery Codes",
`Generated on: ${new Date().toLocaleString()}`,
"",
"Important: Store these codes in a safe place. Each code can only be used once.",
"If you lose access to your mobile authenticator, you can use these codes to regain access to your account.",
"",
"Recovery Codes:",
...recoveryCodes.map((code, index) => `${index + 1}. ${code}`)
].join("\n");
const blob = new Blob([content], { type: "text/plain" }); const blob = new Blob([content], { type: "text/plain" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
@@ -75,8 +66,8 @@ export const RecoveryCodesDownload = ({
<div className="grid grid-cols-2 gap-x-6 gap-y-2 font-mono text-sm"> <div className="grid grid-cols-2 gap-x-6 gap-y-2 font-mono text-sm">
{recoveryCodes.map((code, index) => ( {recoveryCodes.map((code, index) => (
<div key={code} className="flex items-center text-mineshaft-200"> <div key={code} className="flex items-center text-mineshaft-200">
<span className="w-6 text-mineshaft-400">{index + 1}.</span> <span className="w-8 text-right text-mineshaft-400">{index + 1}.</span>
<span>{code}</span> <span className="pl-2">{code}</span>
</div> </div>
))} ))}
</div> </div>