mirror of
https://github.com/ente-io/ente.git
synced 2025-07-03 05:56:17 +00:00
165 lines
5.2 KiB
TypeScript
165 lines
5.2 KiB
TypeScript
import { CodeBlock } from "@/accounts/components/CodeBlock";
|
|
import { Verify2FACodeForm } from "@/accounts/components/Verify2FACodeForm";
|
|
import { appHomeRoute } from "@/accounts/services/redirect";
|
|
import type { TwoFactorSecret } from "@/accounts/services/user";
|
|
import { enableTwoFactor, setupTwoFactor } from "@/accounts/services/user";
|
|
import { CenteredFill } from "@/base/components/containers";
|
|
import { LinkButton } from "@/base/components/LinkButton";
|
|
import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator";
|
|
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
|
|
import { encryptWithRecoveryKey } from "@ente/shared/crypto/helpers";
|
|
import { getData, LS_KEYS, setLSUser } from "@ente/shared/storage/localStorage";
|
|
import { Paper, Stack, styled, Typography } from "@mui/material";
|
|
import { t } from "i18next";
|
|
import { useRouter } from "next/router";
|
|
import React, { useEffect, useState } from "react";
|
|
|
|
const Page: React.FC = () => {
|
|
const [twoFactorSecret, setTwoFactorSecret] = useState<
|
|
TwoFactorSecret | undefined
|
|
>();
|
|
|
|
const router = useRouter();
|
|
|
|
useEffect(() => {
|
|
void setupTwoFactor().then(setTwoFactorSecret);
|
|
}, []);
|
|
|
|
const handleSubmit = async (otp: string) => {
|
|
const {
|
|
encryptedData: encryptedTwoFactorSecret,
|
|
nonce: twoFactorSecretDecryptionNonce,
|
|
} = await encryptWithRecoveryKey(twoFactorSecret!.secretCode);
|
|
await enableTwoFactor({
|
|
code: otp,
|
|
encryptedTwoFactorSecret,
|
|
twoFactorSecretDecryptionNonce,
|
|
});
|
|
await setLSUser({
|
|
...getData(LS_KEYS.USER),
|
|
isTwoFactorEnabled: true,
|
|
});
|
|
await router.push(appHomeRoute);
|
|
};
|
|
|
|
return (
|
|
<Stack sx={{ minHeight: "100svh" }}>
|
|
<CenteredFill>
|
|
<ContentsPaper>
|
|
<Typography variant="h5" sx={{ textAlign: "center" }}>
|
|
{t("two_factor")}
|
|
</Typography>
|
|
<Instructions twoFactorSecret={twoFactorSecret} />
|
|
<Verify2FACodeForm
|
|
onSubmit={handleSubmit}
|
|
submitButtonText={t("enable")}
|
|
/>
|
|
<Stack sx={{ alignItems: "center" }}>
|
|
<FocusVisibleButton
|
|
variant="text"
|
|
onClick={router.back}
|
|
>
|
|
{t("go_back")}
|
|
</FocusVisibleButton>
|
|
</Stack>
|
|
</ContentsPaper>
|
|
</CenteredFill>
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
export default Page;
|
|
|
|
const ContentsPaper = styled(Paper)(({ theme }) => ({
|
|
marginBlock: theme.spacing(2),
|
|
padding: theme.spacing(4, 2),
|
|
// Wide enough to fit the QR code secret in one line under default settings.
|
|
width: "min(440px, 95vw)",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: theme.spacing(4),
|
|
}));
|
|
|
|
interface InstructionsProps {
|
|
twoFactorSecret: TwoFactorSecret | undefined;
|
|
}
|
|
|
|
const Instructions: React.FC<InstructionsProps> = ({ twoFactorSecret }) => {
|
|
const [setupMode, setSetupMode] = useState<"qr" | "manual">("qr");
|
|
|
|
return (
|
|
<Stack sx={{ gap: 3, alignItems: "center" }}>
|
|
{setupMode == "qr" ? (
|
|
<SetupQRMode
|
|
twoFactorSecret={twoFactorSecret}
|
|
onChangeMode={() => setSetupMode("manual")}
|
|
/>
|
|
) : (
|
|
<SetupManualMode
|
|
twoFactorSecret={twoFactorSecret}
|
|
onChangeMode={() => setSetupMode("qr")}
|
|
/>
|
|
)}
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
interface SetupManualModeProps {
|
|
twoFactorSecret: TwoFactorSecret | undefined;
|
|
onChangeMode: () => void;
|
|
}
|
|
|
|
const SetupManualMode: React.FC<SetupManualModeProps> = ({
|
|
twoFactorSecret,
|
|
onChangeMode,
|
|
}) => (
|
|
<>
|
|
<Typography sx={{ color: "text.muted", textAlign: "center", px: 2 }}>
|
|
{t("two_factor_manual_entry_message")}
|
|
</Typography>
|
|
<CodeBlock code={twoFactorSecret?.secretCode} />
|
|
<LinkButton onClick={onChangeMode}>{t("scan_qr_title")}</LinkButton>
|
|
</>
|
|
);
|
|
|
|
interface SetupQRModeProps {
|
|
twoFactorSecret?: TwoFactorSecret;
|
|
onChangeMode: () => void;
|
|
}
|
|
|
|
const SetupQRMode: React.FC<SetupQRModeProps> = ({
|
|
twoFactorSecret,
|
|
onChangeMode,
|
|
}) => (
|
|
<>
|
|
<Typography sx={{ color: "text.muted", textAlign: "center" }}>
|
|
{t("two_factor_qr_help")}
|
|
</Typography>
|
|
{!twoFactorSecret ? (
|
|
<LoadingQRCode>
|
|
<ActivityIndicator />
|
|
</LoadingQRCode>
|
|
) : (
|
|
<QRCode src={`data:image/png;base64,${twoFactorSecret?.qrCode}`} />
|
|
)}
|
|
<LinkButton onClick={onChangeMode}>
|
|
{t("two_factor_manual_entry_title")}
|
|
</LinkButton>
|
|
</>
|
|
);
|
|
|
|
const QRCode = styled("img")(`
|
|
width: 200px;
|
|
height: 200px;
|
|
`);
|
|
|
|
const LoadingQRCode = styled(Stack)(
|
|
({ theme }) => `
|
|
width: 200px;
|
|
height: 200px;
|
|
border: 1px solid ${theme.vars.palette.stroke.muted};
|
|
align-items: center;
|
|
justify-content: center;
|
|
`,
|
|
);
|