// Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt import { useState, useEffect, ReactElement } from "react"; import { Modal, Spin, Alert } from "antd"; import { ModalStaticFunctions } from "antd/lib/modal/confirm"; import { useTranslation } from "react-i18next"; import { Wallet, decryptWallet, useMasterPasswordOnly } from "@wallets"; import * as api from "./"; // Used to carry around information on which address failed auth export class AuthFailedError extends api.APIError { constructor(message: string, public address?: string) { super(message); } } interface AuthFailedModalHookResponse { authFailedModal: Omit<ModalStaticFunctions, "warn">; authFailedContextHolder: ReactElement; showAuthFailed: (wallet: Wallet) => void; } /** * Hook that returns a Modal instance and a contextHolder for it. When the * modal is shown, it performs an alert lookup for the given private key, * showing the alert if possible, and otherwise a generic 'auth failed' * message. */ export function useAuthFailedModal(): AuthFailedModalHookResponse { const { t } = useTranslation(); const [modal, contextHolder] = Modal.useModal(); // Create the auth failed modal and show it function showAuthFailed(wallet: Wallet) { modal.error({ title: t("authFailed.title"), content: <ModalContents wallet={wallet} /> }); } return { authFailedModal: modal, authFailedContextHolder: contextHolder, showAuthFailed }; } interface AlertAPIResponse { alert: string; } function ModalContents({ wallet }: { wallet: Wallet }): JSX.Element { const { t } = useTranslation(); // The alert from the sync node const [alert, setAlert] = useState<string | null>(); const [loading, setLoading] = useState(true); // Needed to decrypt the wallet, as the privatekey is required to get alert const masterPassword = useMasterPasswordOnly(); // Fetch the alert from the sync node (this will usually determine if the // address was locked or it's just a collision) useEffect(() => { if (!masterPassword) return; // Errors are generally ignored here, this whole dialog is a very minor // edge case that only a few will see. (async () => { // Decrypt the wallet const decrypted = await decryptWallet(masterPassword, wallet); if (!decrypted) return; // This should never happen const { privatekey } = decrypted; // Perform the fetch api.post<AlertAPIResponse>("/addresses/alert", { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ privatekey }) }) .then(res => setAlert(res.alert)) .catch(console.error) .finally(() => setLoading(false)); })().catch(console.error); }, [wallet, masterPassword]); return <Spin spinning={loading}> {alert // If there is a known alert from the server, this address is locked, show // the alert: ? <> <p>{t("authFailed.messageLocked")}</p> <Alert type="error" message={t("authFailed.alert")} description={alert} /> </> // Otherwise, show a generic "You do not own this address." message. : t("authFailed.message")} </Spin>; }