Skip to content

Commit

Permalink
Adds MFA flow in the frontend
Browse files Browse the repository at this point in the history
- Fixes redirect to correct 2fa page
- Shows user's current MFA Status
- Retrieves QR code to enable MFA
- Adds toast to warn invalid code
  • Loading branch information
julianochoi committed Sep 9, 2023
1 parent c690ed9 commit a85e445
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 36 deletions.
2 changes: 1 addition & 1 deletion frontend/src/app/auth/redirect/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function RootLoginRedirectPage() {
useEffect(() => {
if (user.sub) {
if (user.mfaEnabled) {
router.push("/login/2fa");
router.push("/auth/2fa");
} else {
router.push("/game");
}
Expand Down
41 changes: 30 additions & 11 deletions frontend/src/components/MFAModal/MFACode.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,58 @@
"use client";
import { toast } from "react-hot-toast";
import toast, { Toaster } from "react-hot-toast";
import MFAForm from "../MFAForm";
import { api } from "@/services/apiClient";
import { useState } from "react";
import { useContext, useState } from "react";
import { AuthContext } from "@/contexts/AuthContext";

type MFACodeProps = {
handleStep: (step: number) => void;
};

export default function MFACode({ handleStep }: MFACodeProps) {
const [code, setCode] = useState(["", "", "", "", "", ""]);
const { user } = useContext(AuthContext);

const handleSubmit = async () => {
// Handle submission logic here
console.log("Submitted code:", code.join(""));
const endpoint = user.mfaEnabled ? "/auth/2fa/disable" : "/auth/2fa/enable";
await api
.post(
"/auth/2fa/authenticate",
{ code: code.join("") },
{ withCredentials: true }
)
.post(endpoint, { code: code.join("") }, { withCredentials: true })
.then((r) => {
if (r.status == 201) handleStep(2);
})
.catch((e) => {
toast.error("Código inválido", {
id: "code-error",
});
console.log(e);
toast.error("Código inválido");
});
};

// TODO fix hardcoded URL
return (
<>
<div className="mt-2"></div>
<Toaster />
<div className="flex justify-center">
{user.mfaEnabled ? (
""
) : (
<img
src="http://localhost:3000/auth/2fa/generate"
width={200}
height={200}
alt="QR Code"
/>
)}
</div>

<div className="mt-2">
<p className="text-sm text-white py-4">
Verifique em seu aplicativo de autenticação o código de 6 dígitos
{user.mfaEnabled
? "Verifique o aplicativo de autenticação pelo código de 6 dígitos."
: "Escaneie o QR code acima com seu aplicativo de autenticação."}
</p>
</div>

Expand All @@ -43,9 +62,9 @@ export default function MFACode({ handleStep }: MFACodeProps) {
<button
type="submit"
className="bg-purple42-300 text-white rounded-lg p-2 w-full"
onClick={() => handleStep(2)}
onClick={() => handleSubmit()}
>
Validar
Validar e {user.mfaEnabled ? "desativar" : "ativar"} 2FA
</button>
</div>
</>
Expand Down
28 changes: 12 additions & 16 deletions frontend/src/components/MFAModal/MFAStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
export default function MFAStatus({
status
}: {
status: "enabled" | "disabled";
}) {
let color = "";
export default function MFAStatus({ status }: { status: boolean }) {
let color = "";

switch (status) {
case "enabled":
case true:
color = "bg-green-500";
break;
case "disabled":
case false:
color = "bg-red-500";
break;
default:
color = "bg-gray-500";
break;
}

return(
<span
className={`inline-block px-2 py-0.5 text-xs font-semibold text-white ${color} rounded-full`}
>
Autenticação { status === "enabled" ? "ativada" : "desativada" }
</span>
)
}
return (
<span
className={`inline-block px-2 py-0.5 text-xs font-semibold text-white ${color} rounded-full`}
>
Autenticação {status === true ? "ativada" : "desativada"}
</span>
);
}
9 changes: 8 additions & 1 deletion frontend/src/components/MFAModal/MFASuccess.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { AuthContext } from "@/contexts/AuthContext";
import { useContext } from "react";

type MFASuccessProps = {
handleStep: (step: number) => void;
};

export default function MFASuccess({ handleStep }: MFASuccessProps) {
const { user, setUser } = useContext(AuthContext);
// TODO solve infinite loop
setUser({ ...user, mfaEnabled: !user.mfaEnabled });
return (
<>
<div className="mt-2">
<p className="text-sm text-white">
Autenticação de dois fatores ativada com sucesso!
Autenticação de dois fatores {user.mfaEnabled ? "des" : ""}ativada com
sucesso!
</p>
</div>
</>
Expand Down
16 changes: 11 additions & 5 deletions frontend/src/components/MFAModal/MFAWelcome.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { Dialog } from "@headlessui/react";
import MFAStatus from "./MFAStatus";
import { useContext } from "react";
import { AuthContext } from "@/contexts/AuthContext";

type MFAWelcomeProps = {
handleStep: (step: number) => void;
};

export default function MFAWelcome({ handleStep }: MFAWelcomeProps) {
const { user } = useContext(AuthContext);
return (
<>
<div className="mt-2">
<p className="text-sm text-white">
Autenticação de dois fatores é uma camada extra de segurança para sua
conta. Quando ativada, você precisará de um código de autenticação
gerado pelo seu aplicativo de autenticação, além de seu nome de
usuário e senha.
{!user.mfaEnabled
? `Autenticação de dois fatores é uma camada extra de segurança para sua conta.
Quando ativada, você precisará de um código de autenticação gerado pelo seu aplicativo de autenticação,
além de seu nome de usuário e senha.`
: `A autenticação de dois fatores está ativada em sua conta.
Você pode desativá-la a qualquer momento.
`}
</p>
</div>

Expand All @@ -23,7 +29,7 @@ export default function MFAWelcome({ handleStep }: MFAWelcomeProps) {
className="bg-purple42-300 text-white rounded-lg p-2 w-full"
onClick={() => handleStep(1)}
>
Gerar 2FA
{!user.mfaEnabled ? "Gerar 2FA" : "Desativar 2FA"}
</button>
</div>
</>
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/MFAModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Dialog, Transition } from "@headlessui/react";
import { Key } from "@phosphor-icons/react";
import { Fragment, useState } from "react";
import { Fragment, useContext, useState } from "react";
import MFAStatus from "./MFAStatus";
import MFAWelcome from "./MFAWelcome";
import MFACode from "./MFACode";
import MFASuccess from "./MFASuccess";
import { AuthContext } from "@/contexts/AuthContext";

// #TODO: implementar a lógica de autenticação de dois fatores baseada no Figma

export default function MFAModal() {
const [isOpen, setIsOpen] = useState(false);
const [step, setStep] = useState(0); // [0, 1, 2]
const { user } = useContext(AuthContext);

function closeModal() {
setIsOpen(false);
Expand Down Expand Up @@ -66,7 +68,7 @@ export default function MFAModal() {
>
Autenticação de dois fatores
</Dialog.Title>
<MFAStatus status="disabled" />
<MFAStatus status={user.mfaEnabled} />
{step === 0 ? (
<MFAWelcome handleStep={handleStep} />
) : step === 1 ? (
Expand Down
1 change: 1 addition & 0 deletions frontend/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function setupAPIClient(ctx: Context = undefined) {
if (process.browser) {
signOut();
} else {
console.log("refresh token is invalid");
return Promise.reject(new Error());
}
}
Expand Down

0 comments on commit a85e445

Please sign in to comment.