/*
GoToSocial
Copyright (C) GoToSocial Authors admin@gotosocial.org
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
import React, { ReactNode, useEffect, useMemo, useState } from "react";
import { TextInput } from "../../../components/form/inputs";
import MutationButton from "../../../components/form/mutation-button";
import useFormSubmit from "../../../lib/form/submit";
import {
useTwoFactorQRCodeURIMutation,
useTwoFactorDisableMutation,
useTwoFactorEnableMutation,
useTwoFactorQRCodePngMutation,
} from "../../../lib/query/user/twofactor";
import { useTextInput } from "../../../lib/form";
import Loading from "../../../components/loading";
import { Error } from "../../../components/error";
import { HighlightedCode } from "../../../components/highlightedcode";
import { useDispatch } from "react-redux";
import { gtsApi } from "../../../lib/query/gts-api";
interface TwoFactorProps {
twoFactorEnabledAt?: string,
oidcEnabled?: boolean,
}
export default function TwoFactor({ twoFactorEnabledAt, oidcEnabled }: TwoFactorProps) {
switch (true) {
case oidcEnabled:
// Can't enable if OIDC is in place.
return ;
case twoFactorEnabledAt !== undefined:
// Already enabled. Show the disable form.
return ;
default:
// Not enabled. Show the enable form.
return ;
}
}
function CannotEnable() {
return (
);
}
function EnableForm() {
const form = { code: useTextInput("code") };
const [ recoveryCodes, setRecoveryCodes ] = useState();
const dispatch = useDispatch();
// Prepare trigger to submit the code and enable 2FA.
// If the enable call is a success, set the recovery
// codes state to a nice newline-separated text.
const [submitForm, result] = useFormSubmit(form, useTwoFactorEnableMutation(), {
changedOnly: true,
onFinish: (res) => {
const codes = res.data as string[];
if (!codes) {
return;
}
setRecoveryCodes(codes.join("\n"));
},
});
// When the component is unmounted, clear the user
// cache if 2FA was just enabled. This will prevent
// the recovery codes from being shown again.
useEffect(() => {
return () => {
if (recoveryCodes) {
dispatch(gtsApi.util.invalidateTags(["User"]));
}
};
}, [recoveryCodes, dispatch]);
return (
);
}
// Load and show QR code png only when
// the "Show QR Code" button is clicked.
function CodePng() {
const [
getPng, {
isUninitialized,
isLoading,
isSuccess,
data,
error,
reset,
}
] = useTwoFactorQRCodePngMutation();
const [ content, setContent ] = useState();
useEffect(() => {
if (isLoading) {
setContent();
} else if (isSuccess && data) {
setContent();
} else {
setContent();
}
}, [isLoading, isSuccess, data, error]);
return (
<>
{ isUninitialized
?
:
}
{ content }
>
);
}
// Get 2fa secret from server and
// load it into clipboard on click.
function Secret() {
const [
getURI,
{
isUninitialized,
isSuccess,
data,
error,
reset,
},
] = useTwoFactorQRCodeURIMutation();
const [ buttonContents, setButtonContents ] = useState();
useEffect(() => {
if (isUninitialized) {
setButtonContents("Copy 2FA secret to clipboard");
} else if (isSuccess && data) {
const url = new URL(data);
const secret = url.searchParams.get("secret");
if (!secret) {
throw "null secret";
}
navigator.clipboard.writeText(secret);
setButtonContents("Copied!");
setTimeout(() => { reset(); }, 3000);
} else {
setButtonContents();
}
}, [isUninitialized, isSuccess, data, reset, error]);
return (
);
}
function DisableForm({ twoFactorEnabledAt }: { twoFactorEnabledAt: string }) {
const enabledAt = useMemo(() => {
const enabledAt = new Date(twoFactorEnabledAt);
return ;
}, [twoFactorEnabledAt]);
const form = {
password: useTextInput("password"),
};
const [submitForm, result] = useFormSubmit(form, useTwoFactorDisableMutation());
return (
);
}
function TwoFactorHeader({ blurb }: { blurb: ReactNode }) {
return (