[web] Enable Typescript's strict mode for auth's code (#1865)
This commit is contained in:
commit
582eb9e1ea
68 changed files with 528 additions and 446 deletions
|
@ -6,12 +6,9 @@ import { accountLogout } from "@ente/accounts/services/logout";
|
|||
import { APPS, APP_TITLES } from "@ente/shared/apps/constants";
|
||||
import { Overlay } from "@ente/shared/components/Container";
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import type {
|
||||
DialogBoxAttributesV2,
|
||||
SetDialogBoxAttributesV2,
|
||||
} from "@ente/shared/components/DialogBoxV2/types";
|
||||
import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import AppNavbar from "@ente/shared/components/Navbar/app";
|
||||
import { AppNavbar } from "@ente/shared/components/Navbar/app";
|
||||
import { useLocalState } from "@ente/shared/hooks/useLocalState";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
|
@ -28,7 +25,7 @@ import "styles/global.css";
|
|||
interface AppContextProps {
|
||||
isMobile: boolean;
|
||||
showNavBar: (show: boolean) => void;
|
||||
setDialogBoxAttributesV2: SetDialogBoxAttributesV2;
|
||||
setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
|
@ -39,8 +36,9 @@ export default function App({ Component, pageProps }: AppProps) {
|
|||
|
||||
const [showNavbar, setShowNavBar] = useState(false);
|
||||
|
||||
const [dialogBoxAttributeV2, setDialogBoxAttributesV2] =
|
||||
useState<DialogBoxAttributesV2>();
|
||||
const [dialogBoxAttributeV2, setDialogBoxAttributesV2] = useState<
|
||||
DialogBoxAttributesV2 | undefined
|
||||
>();
|
||||
|
||||
const [dialogBoxV2View, setDialogBoxV2View] = useState(false);
|
||||
|
||||
|
@ -106,8 +104,7 @@ export default function App({ Component, pageProps }: AppProps) {
|
|||
value={{
|
||||
isMobile,
|
||||
showNavBar,
|
||||
setDialogBoxAttributesV2:
|
||||
setDialogBoxAttributesV2 as any,
|
||||
setDialogBoxAttributesV2,
|
||||
logout,
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -12,20 +12,16 @@ import {
|
|||
} from "@ente/shared/apps/constants";
|
||||
import { Overlay } from "@ente/shared/components/Container";
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import type {
|
||||
DialogBoxAttributesV2,
|
||||
SetDialogBoxAttributesV2,
|
||||
} from "@ente/shared/components/DialogBoxV2/types";
|
||||
import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { MessageContainer } from "@ente/shared/components/MessageContainer";
|
||||
import AppNavbar from "@ente/shared/components/Navbar/app";
|
||||
import { AppNavbar } from "@ente/shared/components/Navbar/app";
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import { useLocalState } from "@ente/shared/hooks/useLocalState";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import { getTheme } from "@ente/shared/themes";
|
||||
import { THEME_COLOR } from "@ente/shared/themes/constants";
|
||||
import type { SetTheme } from "@ente/shared/themes/types";
|
||||
import type { User } from "@ente/shared/user/types";
|
||||
import { CssBaseline, useMediaQuery } from "@mui/material";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
|
@ -33,7 +29,7 @@ import { t } from "i18next";
|
|||
import type { AppProps } from "next/app";
|
||||
import { useRouter } from "next/router";
|
||||
import { createContext, useEffect, useRef, useState } from "react";
|
||||
import LoadingBar from "react-top-loading-bar";
|
||||
import LoadingBar, { type LoadingBarRef } from "react-top-loading-bar";
|
||||
import "../../public/css/global.css";
|
||||
|
||||
type AppContextType = {
|
||||
|
@ -42,13 +38,13 @@ type AppContextType = {
|
|||
finishLoading: () => void;
|
||||
isMobile: boolean;
|
||||
themeColor: THEME_COLOR;
|
||||
setThemeColor: SetTheme;
|
||||
setThemeColor: (themeColor: THEME_COLOR) => void;
|
||||
somethingWentWrong: () => void;
|
||||
setDialogBoxAttributesV2: SetDialogBoxAttributesV2;
|
||||
setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void;
|
||||
logout: () => void;
|
||||
};
|
||||
|
||||
export const AppContext = createContext<AppContextType>(null);
|
||||
export const AppContext = createContext<AppContextType | undefined>(undefined);
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const router = useRouter();
|
||||
|
@ -58,10 +54,11 @@ export default function App({ Component, pageProps }: AppProps) {
|
|||
typeof window !== "undefined" && !window.navigator.onLine,
|
||||
);
|
||||
const [showNavbar, setShowNavBar] = useState(false);
|
||||
const isLoadingBarRunning = useRef(false);
|
||||
const loadingBar = useRef(null);
|
||||
const [dialogBoxAttributeV2, setDialogBoxAttributesV2] =
|
||||
useState<DialogBoxAttributesV2>();
|
||||
const isLoadingBarRunning = useRef<boolean>(false);
|
||||
const loadingBar = useRef<LoadingBarRef>(null);
|
||||
const [dialogBoxAttributeV2, setDialogBoxAttributesV2] = useState<
|
||||
DialogBoxAttributesV2 | undefined
|
||||
>();
|
||||
const [dialogBoxV2View, setDialogBoxV2View] = useState(false);
|
||||
const isMobile = useMediaQuery("(max-width:428px)");
|
||||
const [themeColor, setThemeColor] = useLocalState(
|
||||
|
@ -134,9 +131,10 @@ export default function App({ Component, pageProps }: AppProps) {
|
|||
void accountLogout().then(() => router.push(PAGES.ROOT));
|
||||
};
|
||||
|
||||
// TODO: Refactor this to have a fallback
|
||||
const title = isI18nReady
|
||||
? t("TITLE", { context: APPS.AUTH })
|
||||
: APP_TITLES.get(APPS.AUTH);
|
||||
: APP_TITLES.get(APPS.AUTH) ?? "";
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import {
|
||||
HorizontalFlex,
|
||||
VerticallyCentered,
|
||||
|
@ -12,7 +13,7 @@ import { CustomError } from "@ente/shared/error";
|
|||
import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
|
||||
import LogoutOutlined from "@mui/icons-material/LogoutOutlined";
|
||||
import MoreHoriz from "@mui/icons-material/MoreHoriz";
|
||||
import { Button, ButtonBase, Snackbar, TextField } from "@mui/material";
|
||||
import { Button, ButtonBase, Snackbar, TextField, styled } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
|
@ -20,20 +21,22 @@ import React, { useContext, useEffect, useState } from "react";
|
|||
import { generateOTPs, type Code } from "services/code";
|
||||
import { getAuthCodes } from "services/remote";
|
||||
|
||||
const AuthenticatorCodesPage = () => {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
const router = useRouter();
|
||||
const [codes, setCodes] = useState([]);
|
||||
const [codes, setCodes] = useState<Code[]>([]);
|
||||
const [hasFetched, setHasFetched] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCodes = async () => {
|
||||
try {
|
||||
const res = await getAuthCodes();
|
||||
setCodes(res);
|
||||
} catch (err) {
|
||||
if (err.message === CustomError.KEY_MISSING) {
|
||||
setCodes(await getAuthCodes());
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof Error &&
|
||||
e.message == CustomError.KEY_MISSING
|
||||
) {
|
||||
InMemoryStore.set(MS_KEYS.REDIRECT_URL, PAGES.AUTH);
|
||||
router.push(PAGES.ROOT);
|
||||
} else {
|
||||
|
@ -55,11 +58,9 @@ const AuthenticatorCodesPage = () => {
|
|||
|
||||
if (!hasFetched) {
|
||||
return (
|
||||
<>
|
||||
<VerticallyCentered>
|
||||
<EnteSpinner></EnteSpinner>
|
||||
</VerticallyCentered>
|
||||
</>
|
||||
<VerticallyCentered>
|
||||
<EnteSpinner />
|
||||
</VerticallyCentered>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -77,7 +78,7 @@ const AuthenticatorCodesPage = () => {
|
|||
}}
|
||||
>
|
||||
<div style={{ marginBottom: "1rem" }} />
|
||||
{filteredCodes.length === 0 && searchTerm.length === 0 ? (
|
||||
{filteredCodes.length == 0 && searchTerm.length == 0 ? (
|
||||
<></>
|
||||
) : (
|
||||
<TextField
|
||||
|
@ -101,7 +102,7 @@ const AuthenticatorCodesPage = () => {
|
|||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{filteredCodes.length === 0 ? (
|
||||
{filteredCodes.length == 0 ? (
|
||||
<div
|
||||
style={{
|
||||
alignItems: "center",
|
||||
|
@ -110,10 +111,10 @@ const AuthenticatorCodesPage = () => {
|
|||
marginTop: "32px",
|
||||
}}
|
||||
>
|
||||
{searchTerm.length !== 0 ? (
|
||||
{searchTerm.length > 0 ? (
|
||||
<p>{t("NO_RESULTS")}</p>
|
||||
) : (
|
||||
<div />
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
|
@ -122,18 +123,16 @@ const AuthenticatorCodesPage = () => {
|
|||
))
|
||||
)}
|
||||
</div>
|
||||
<div style={{ marginBottom: "2rem" }} />
|
||||
<Footer />
|
||||
<div style={{ marginBottom: "4rem" }} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthenticatorCodesPage;
|
||||
export default Page;
|
||||
|
||||
const AuthNavbar: React.FC = () => {
|
||||
const { isMobile, logout } = useContext(AppContext);
|
||||
const { isMobile, logout } = ensure(useContext(AppContext));
|
||||
|
||||
return (
|
||||
<NavbarBase isMobile={isMobile}>
|
||||
|
@ -158,11 +157,11 @@ const AuthNavbar: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
interface CodeDisplay {
|
||||
interface CodeDisplayProps {
|
||||
code: Code;
|
||||
}
|
||||
|
||||
const CodeDisplay: React.FC<CodeDisplay> = ({ code }) => {
|
||||
const CodeDisplay: React.FC<CodeDisplayProps> = ({ code }) => {
|
||||
const [otp, setOTP] = useState("");
|
||||
const [nextOTP, setNextOTP] = useState("");
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
|
@ -393,14 +392,7 @@ const UnparseableCode: React.FC<UnparseableCodeProps> = ({
|
|||
|
||||
const Footer: React.FC = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Footer_>
|
||||
<p>{t("AUTH_DOWNLOAD_MOBILE_APP")}</p>
|
||||
<a
|
||||
href="https://github.com/ente-io/ente/tree/main/auth#-download"
|
||||
|
@ -408,6 +400,15 @@ const Footer: React.FC = () => {
|
|||
>
|
||||
<Button color="accent">{t("DOWNLOAD")}</Button>
|
||||
</a>
|
||||
</div>
|
||||
</Footer_>
|
||||
);
|
||||
};
|
||||
|
||||
const Footer_ = styled("div")`
|
||||
margin-block-start: 2rem;
|
||||
margin-block-end: 4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import ChangeEmailPage from "@ente/accounts/pages/change-email";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
export default function ChangeEmail() {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
return <ChangeEmailPage appContext={appContext} appName={APPS.AUTH} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import ChangePasswordPage from "@ente/accounts/pages/change-password";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
export default function ChangePassword() {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
return <ChangePasswordPage appContext={appContext} appName={APPS.AUTH} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import CredentialPage from "@ente/accounts/pages/credentials";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
export default function Credential() {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
return <CredentialPage appContext={appContext} appName={APPS.AUTH} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import GeneratePage from "@ente/accounts/pages/generate";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
export default function Generate() {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
return <GeneratePage appContext={appContext} appName={APPS.AUTH} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
const IndexPage = () => {
|
||||
const Page: React.FC = () => {
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
router.push(PAGES.LOGIN);
|
||||
|
@ -11,4 +11,4 @@ const IndexPage = () => {
|
|||
return <></>;
|
||||
};
|
||||
|
||||
export default IndexPage;
|
||||
export default Page;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import LoginPage from "@ente/accounts/pages/login";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
export default function Login() {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
return <LoginPage appContext={appContext} appName={APPS.AUTH} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
import PasskeysFinishPage from "@ente/accounts/pages/passkeys/finish";
|
||||
import Page from "@ente/accounts/pages/passkeys/finish";
|
||||
|
||||
const PasskeysFinish = () => {
|
||||
return (
|
||||
<>
|
||||
<PasskeysFinishPage />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasskeysFinish;
|
||||
export default Page;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import RecoverPage from "@ente/accounts/pages/recover";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
export default function Recover() {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
return <RecoverPage appContext={appContext} appName={APPS.AUTH} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import SignupPage from "@ente/accounts/pages/signup";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
export default function Sigup() {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
return <SignupPage appContext={appContext} appName={APPS.AUTH} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import TwoFactorRecoverPage from "@ente/accounts/pages/two-factor/recover";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
export default function TwoFactorRecover() {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
return <TwoFactorRecoverPage appContext={appContext} appName={APPS.AUTH} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import TwoFactorSetupPage from "@ente/accounts/pages/two-factor/setup";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
export default function TwoFactorSetup() {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
return <TwoFactorSetupPage appContext={appContext} appName={APPS.AUTH} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import TwoFactorVerifyPage from "@ente/accounts/pages/two-factor/verify";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
import { AppContext } from "../_app";
|
||||
|
||||
export default function TwoFactorVerify() {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
return <TwoFactorVerifyPage appContext={appContext} appName={APPS.AUTH} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import VerifyPage from "@ente/accounts/pages/verify";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
export default function Verify() {
|
||||
const appContext = useContext(AppContext);
|
||||
const Page: React.FC = () => {
|
||||
const appContext = ensure(useContext(AppContext));
|
||||
return <VerifyPage appContext={appContext} appName={APPS.AUTH} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Steam } from "./steam";
|
|||
*/
|
||||
export interface Code {
|
||||
/** A unique id for the corresponding "auth entity" in our system. */
|
||||
id?: String;
|
||||
id: string;
|
||||
/** The type of the code. */
|
||||
type: "totp" | "hotp" | "steam";
|
||||
/** The user's account or email for which this code is used. */
|
||||
|
@ -146,8 +146,8 @@ const parseIssuer = (url: URL, path: string): string => {
|
|||
let p = decodeURIComponent(path);
|
||||
if (p.startsWith("/")) p = p.slice(1);
|
||||
|
||||
if (p.includes(":")) p = p.split(":")[0];
|
||||
else if (p.includes("-")) p = p.split("-")[0];
|
||||
if (p.includes(":")) p = ensure(p.split(":")[0]);
|
||||
else if (p.includes("-")) p = ensure(p.split("-")[0]);
|
||||
|
||||
return p;
|
||||
};
|
||||
|
|
|
@ -26,6 +26,9 @@ export const getAuthCodes = async (): Promise<Code[]> => {
|
|||
authEntity
|
||||
.filter((f) => !f.isDeleted)
|
||||
.map(async (entity) => {
|
||||
if (!entity.id) return undefined;
|
||||
if (!entity.encryptedData) return undefined;
|
||||
if (!entity.header) return undefined;
|
||||
try {
|
||||
const decryptedCode =
|
||||
await cryptoWorker.decryptMetadata(
|
||||
|
@ -36,14 +39,12 @@ export const getAuthCodes = async (): Promise<Code[]> => {
|
|||
return codeFromURIString(entity.id, decryptedCode);
|
||||
} catch (e) {
|
||||
log.error(`Failed to parse codeID ${entity.id}`, e);
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
}),
|
||||
);
|
||||
// Remove null and undefined values
|
||||
const filteredAuthCodes = authCodes.filter(
|
||||
(f) => f !== null && f !== undefined,
|
||||
);
|
||||
// Remove undefined values
|
||||
const filteredAuthCodes = authCodes.filter((f): f is Code => !!f);
|
||||
filteredAuthCodes.sort((a, b) => {
|
||||
if (a.issuer && b.issuer) {
|
||||
return a.issuer.localeCompare(b.issuer);
|
||||
|
@ -58,7 +59,7 @@ export const getAuthCodes = async (): Promise<Code[]> => {
|
|||
});
|
||||
return filteredAuthCodes;
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.AUTH_KEY_NOT_FOUND) {
|
||||
if (e instanceof Error && e.message != CustomError.AUTH_KEY_NOT_FOUND) {
|
||||
log.error("get authenticator entities failed", e);
|
||||
}
|
||||
throw e;
|
||||
|
@ -92,7 +93,7 @@ export const getAuthKey = async (): Promise<AuthKey> => {
|
|||
} catch (e) {
|
||||
if (
|
||||
e instanceof ApiError &&
|
||||
e.httpStatusCode === HttpStatusCode.NotFound
|
||||
e.httpStatusCode == HttpStatusCode.NotFound
|
||||
) {
|
||||
throw Error(CustomError.AUTH_KEY_NOT_FOUND);
|
||||
} else {
|
||||
|
|
|
@ -3,23 +3,18 @@
|
|||
"include": [
|
||||
"src",
|
||||
"next-env.d.ts",
|
||||
"../../packages/next/global-electron.d.ts",
|
||||
"../../packages/shared/themes/mui-theme.d.ts"
|
||||
],
|
||||
// Temporarily disable some things to get the existing code to compile
|
||||
// without warnings.
|
||||
"compilerOptions": {
|
||||
/* Set the base directory from which to resolve bare module names */
|
||||
"baseUrl": "./src",
|
||||
|
||||
"jsxImportSource": "@emotion/react",
|
||||
|
||||
"strict": false,
|
||||
/* Stricter than strict */
|
||||
"noImplicitReturns": false,
|
||||
"noUnusedParameters": false,
|
||||
"noUnusedLocals": false,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
/* e.g. makes array indexing returns undefined */
|
||||
/* This is hard to enforce in certain cases where we do a lot of array
|
||||
indexing, e.g. image/ML ops, and TS doesn't currently have a way to
|
||||
disable this for blocks of code. */
|
||||
"noUncheckedIndexedAccess": false,
|
||||
/* MUI doesn't play great with exactOptionalPropertyTypes currently. */
|
||||
"exactOptionalPropertyTypes": false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,9 +157,9 @@ const UserDetailsSection: React.FC<UserDetailsSectionProps> = ({
|
|||
}) => {
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
|
||||
const [userDetails, setUserDetails] = useLocalState<UserDetails>(
|
||||
LS_KEYS.USER_DETAILS,
|
||||
);
|
||||
const [userDetails, setUserDetails] = useLocalState<
|
||||
UserDetails | undefined
|
||||
>(LS_KEYS.USER_DETAILS, undefined);
|
||||
const [memberSubscriptionManageView, setMemberSubscriptionManageView] =
|
||||
useState(false);
|
||||
|
||||
|
@ -198,6 +198,7 @@ const UserDetailsSection: React.FC<UserDetailsSectionProps> = ({
|
|||
openMemberSubscriptionManage();
|
||||
} else {
|
||||
if (
|
||||
userDetails &&
|
||||
hasStripeSubscription(userDetails.subscription) &&
|
||||
isSubscriptionPastDue(userDetails.subscription)
|
||||
) {
|
||||
|
@ -529,7 +530,7 @@ const UtilitySection: React.FC<UtilitySectionProps> = ({ closeSidebar }) => {
|
|||
});
|
||||
|
||||
const toggleTheme = () => {
|
||||
setThemeColor((themeColor) =>
|
||||
setThemeColor(
|
||||
themeColor === THEME_COLOR.DARK
|
||||
? THEME_COLOR.LIGHT
|
||||
: THEME_COLOR.DARK,
|
||||
|
|
|
@ -22,7 +22,7 @@ export const CollectionMappingChoiceModal: React.FC<
|
|||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose}>
|
||||
<DialogTitleWithCloseButton onClose={handleClose}>
|
||||
<DialogTitleWithCloseButton onClose={onClose}>
|
||||
{t("MULTI_FOLDER_UPLOAD")}
|
||||
</DialogTitleWithCloseButton>
|
||||
<DialogContent>
|
||||
|
|
|
@ -119,12 +119,11 @@ export const WatchFolder: React.FC<WatchFolderProps> = ({ open, onClose }) => {
|
|||
onClose={onClose}
|
||||
PaperProps={{ sx: { height: "448px", maxWidth: "414px" } }}
|
||||
>
|
||||
<DialogTitleWithCloseButton
|
||||
onClose={onClose}
|
||||
sx={{ "&&&": { padding: "32px 16px 16px 24px" } }}
|
||||
>
|
||||
{t("WATCHED_FOLDERS")}
|
||||
</DialogTitleWithCloseButton>
|
||||
<Title_>
|
||||
<DialogTitleWithCloseButton onClose={onClose}>
|
||||
{t("WATCHED_FOLDERS")}
|
||||
</DialogTitleWithCloseButton>
|
||||
</Title_>
|
||||
<DialogContent sx={{ flex: 1 }}>
|
||||
<Stack spacing={1} p={1.5} height={"100%"}>
|
||||
<WatchList {...{ watches, removeWatch }} />
|
||||
|
@ -149,6 +148,10 @@ export const WatchFolder: React.FC<WatchFolderProps> = ({ open, onClose }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const Title_ = styled("div")`
|
||||
padding: 32px 16px 16px 24px;
|
||||
`;
|
||||
|
||||
interface WatchList {
|
||||
watches: FolderWatch[];
|
||||
removeWatch: (watch: FolderWatch) => void;
|
||||
|
|
|
@ -18,13 +18,10 @@ import {
|
|||
SetDialogBoxAttributes,
|
||||
} from "@ente/shared/components/DialogBox/types";
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import type {
|
||||
DialogBoxAttributesV2,
|
||||
SetDialogBoxAttributesV2,
|
||||
} from "@ente/shared/components/DialogBoxV2/types";
|
||||
import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { MessageContainer } from "@ente/shared/components/MessageContainer";
|
||||
import AppNavbar from "@ente/shared/components/Navbar/app";
|
||||
import { AppNavbar } from "@ente/shared/components/Navbar/app";
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import { useLocalState } from "@ente/shared/hooks/useLocalState";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
|
@ -36,7 +33,6 @@ import {
|
|||
} from "@ente/shared/storage/localStorage/helpers";
|
||||
import { getTheme } from "@ente/shared/themes";
|
||||
import { THEME_COLOR } from "@ente/shared/themes/constants";
|
||||
import type { SetTheme } from "@ente/shared/themes/types";
|
||||
import type { User } from "@ente/shared/user/types";
|
||||
import ArrowForward from "@mui/icons-material/ArrowForward";
|
||||
import { CssBaseline, useMediaQuery } from "@mui/material";
|
||||
|
@ -95,9 +91,9 @@ type AppContextType = {
|
|||
setWatchFolderFiles: (files: FileList) => void;
|
||||
isMobile: boolean;
|
||||
themeColor: THEME_COLOR;
|
||||
setThemeColor: SetTheme;
|
||||
setThemeColor: (themeColor: THEME_COLOR) => void;
|
||||
somethingWentWrong: () => void;
|
||||
setDialogBoxAttributesV2: SetDialogBoxAttributesV2;
|
||||
setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void;
|
||||
isCFProxyDisabled: boolean;
|
||||
setIsCFProxyDisabled: (disabled: boolean) => void;
|
||||
logout: () => void;
|
||||
|
@ -119,8 +115,9 @@ export default function App({ Component, pageProps }: AppProps) {
|
|||
const isLoadingBarRunning = useRef(false);
|
||||
const loadingBar = useRef(null);
|
||||
const [dialogMessage, setDialogMessage] = useState<DialogBoxAttributes>();
|
||||
const [dialogBoxAttributeV2, setDialogBoxAttributesV2] =
|
||||
useState<DialogBoxAttributesV2>();
|
||||
const [dialogBoxAttributeV2, setDialogBoxAttributesV2] = useState<
|
||||
DialogBoxAttributesV2 | undefined
|
||||
>();
|
||||
useState<DialogBoxAttributes>(null);
|
||||
const [messageDialogView, setMessageDialogView] = useState(false);
|
||||
const [dialogBoxV2View, setDialogBoxV2View] = useState(false);
|
||||
|
|
|
@ -25,81 +25,6 @@ import { useContext, useEffect, useState } from "react";
|
|||
import { Trans } from "react-i18next";
|
||||
import { AppContext } from "./_app";
|
||||
|
||||
const Container = styled("div")`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #000;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
`;
|
||||
|
||||
const SlideContainer = styled("div")`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-grow: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const DesktopBox = styled("div")`
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #242424;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const MobileBox = styled("div")`
|
||||
display: none;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
max-width: 375px;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
const SideBox = styled("div")`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 320px;
|
||||
`;
|
||||
|
||||
const TextContainer = (props: TypographyProps) => (
|
||||
<Typography color={"text.muted"} mt={2} mb={3} {...props} />
|
||||
);
|
||||
|
||||
const FeatureText = (props: TypographyProps) => (
|
||||
<Typography variant="h3" mt={4} {...props} />
|
||||
);
|
||||
|
||||
const Img = styled("img")`
|
||||
height: 250px;
|
||||
object-fit: contain;
|
||||
|
||||
@media (max-width: 400px) {
|
||||
height: 180px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function LandingPage() {
|
||||
const router = useRouter();
|
||||
const appContext = useContext(AppContext);
|
||||
|
@ -189,7 +114,9 @@ export default function LandingPage() {
|
|||
) : (
|
||||
<>
|
||||
<SlideContainer>
|
||||
<EnteLogo height={24} sx={{ mb: 8 }} />
|
||||
<Logo_>
|
||||
<EnteLogo height={24} />
|
||||
</Logo_>
|
||||
<Slideshow />
|
||||
</SlideContainer>
|
||||
<MobileBox>
|
||||
|
@ -223,6 +150,68 @@ export default function LandingPage() {
|
|||
);
|
||||
}
|
||||
|
||||
const Container = styled("div")`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #000;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
`;
|
||||
|
||||
const SlideContainer = styled("div")`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-grow: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const Logo_ = styled("div")`
|
||||
margin-block-end: 64px;
|
||||
`;
|
||||
|
||||
const DesktopBox = styled("div")`
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #242424;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const MobileBox = styled("div")`
|
||||
display: none;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
max-width: 375px;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
const SideBox = styled("div")`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 320px;
|
||||
`;
|
||||
|
||||
const Slideshow: React.FC = () => {
|
||||
return (
|
||||
<CarouselProvider
|
||||
|
@ -276,6 +265,23 @@ const Slideshow: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const TextContainer = (props: TypographyProps) => (
|
||||
<Typography color={"text.muted"} mt={2} mb={3} {...props} />
|
||||
);
|
||||
|
||||
const FeatureText = (props: TypographyProps) => (
|
||||
<Typography variant="h3" mt={4} {...props} />
|
||||
);
|
||||
|
||||
const Img = styled("img")`
|
||||
height: 250px;
|
||||
object-fit: contain;
|
||||
|
||||
@media (max-width: 400px) {
|
||||
height: 180px;
|
||||
}
|
||||
`;
|
||||
|
||||
const CustomDotGroup = styled(DotGroup)`
|
||||
margin-block-start: 2px;
|
||||
margin-block-end: 24px;
|
||||
|
|
|
@ -127,7 +127,7 @@ export const updateSRPAndKeys = async (
|
|||
const resp = await HTTPService.post(
|
||||
`${ENDPOINT}/users/srp/update`,
|
||||
updateSRPAndKeyRequest,
|
||||
null,
|
||||
undefined,
|
||||
{
|
||||
"X-Auth-Token": token,
|
||||
},
|
||||
|
|
|
@ -66,14 +66,10 @@ export const logout = async () => {
|
|||
};
|
||||
|
||||
export const verifyTwoFactor = async (code: string, sessionID: string) => {
|
||||
const resp = await HTTPService.post(
|
||||
`${ENDPOINT}/users/two-factor/verify`,
|
||||
{
|
||||
code,
|
||||
sessionID,
|
||||
},
|
||||
null,
|
||||
);
|
||||
const resp = await HTTPService.post(`${ENDPOINT}/users/two-factor/verify`, {
|
||||
code,
|
||||
sessionID,
|
||||
});
|
||||
return resp.data as UserVerificationResponse;
|
||||
};
|
||||
|
||||
|
@ -108,7 +104,7 @@ export const changeEmail = async (email: string, ott: string) => {
|
|||
email,
|
||||
ott,
|
||||
},
|
||||
null,
|
||||
undefined,
|
||||
{
|
||||
"X-Auth-Token": getToken(),
|
||||
},
|
||||
|
@ -127,7 +123,7 @@ export const setupTwoFactor = async () => {
|
|||
const resp = await HTTPService.post(
|
||||
`${ENDPOINT}/users/two-factor/setup`,
|
||||
null,
|
||||
null,
|
||||
undefined,
|
||||
{
|
||||
"X-Auth-Token": getToken(),
|
||||
},
|
||||
|
@ -148,7 +144,7 @@ export const enableTwoFactor = async (
|
|||
twoFactorSecretDecryptionNonce:
|
||||
recoveryEncryptedTwoFactorSecret.nonce,
|
||||
},
|
||||
null,
|
||||
undefined,
|
||||
{
|
||||
"X-Auth-Token": getToken(),
|
||||
},
|
||||
|
@ -156,12 +152,17 @@ export const enableTwoFactor = async (
|
|||
};
|
||||
|
||||
export const setRecoveryKey = (token: string, recoveryKey: RecoveryKey) =>
|
||||
HTTPService.put(`${ENDPOINT}/users/recovery-key`, recoveryKey, null, {
|
||||
HTTPService.put(`${ENDPOINT}/users/recovery-key`, recoveryKey, undefined, {
|
||||
"X-Auth-Token": token,
|
||||
});
|
||||
|
||||
export const disableTwoFactor = async () => {
|
||||
await HTTPService.post(`${ENDPOINT}/users/two-factor/disable`, null, null, {
|
||||
"X-Auth-Token": getToken(),
|
||||
});
|
||||
await HTTPService.post(
|
||||
`${ENDPOINT}/users/two-factor/disable`,
|
||||
null,
|
||||
undefined,
|
||||
{
|
||||
"X-Auth-Token": getToken(),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import { wait } from "@/utils/promise";
|
||||
import { changeEmail, sendOTTForEmailChange } from "@ente/accounts/api/user";
|
||||
import { APP_HOMES } from "@ente/shared/apps/constants";
|
||||
|
@ -11,7 +12,7 @@ import { Alert, Box, TextField } from "@mui/material";
|
|||
import { Formik, type FormikHelpers } from "formik";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import * as Yup from "yup";
|
||||
|
||||
|
@ -23,8 +24,7 @@ interface formValues {
|
|||
function ChangeEmailForm({ appName }: PageProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [ottInputVisible, setShowOttInputVisibility] = useState(false);
|
||||
const ottInputRef = useRef(null);
|
||||
const [email, setEmail] = useState(null);
|
||||
const [email, setEmail] = useState<string | null>(null);
|
||||
const [showMessage, setShowMessage] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
|
@ -40,9 +40,11 @@ function ChangeEmailForm({ appName }: PageProps) {
|
|||
setEmail(email);
|
||||
setShowOttInputVisibility(true);
|
||||
setShowMessage(true);
|
||||
setTimeout(() => {
|
||||
ottInputRef.current?.focus();
|
||||
}, 250);
|
||||
// TODO: What was this meant to focus on? The ref referred to an
|
||||
// Form element that was removed. Is this still needed.
|
||||
// setTimeout(() => {
|
||||
// ottInputRef.current?.focus();
|
||||
// }, 250);
|
||||
} catch (e) {
|
||||
setFieldError("email", t("EMAIl_ALREADY_OWNED"));
|
||||
}
|
||||
|
@ -55,7 +57,7 @@ function ChangeEmailForm({ appName }: PageProps) {
|
|||
) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await changeEmail(email, ott);
|
||||
await changeEmail(email, ensure(ott));
|
||||
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), email });
|
||||
setLoading(false);
|
||||
setSuccess(true);
|
||||
|
@ -68,18 +70,27 @@ function ChangeEmailForm({ appName }: PageProps) {
|
|||
};
|
||||
|
||||
const goToApp = () => {
|
||||
router.push(APP_HOMES.get(appName));
|
||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
||||
router.push(APP_HOMES.get(appName) ?? "/");
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik<formValues>
|
||||
initialValues={{ email: "" }}
|
||||
validationSchema={Yup.object().shape({
|
||||
email: Yup.string()
|
||||
.email(t("EMAIL_ERROR"))
|
||||
.required(t("REQUIRED")),
|
||||
ott: ottInputVisible && Yup.string().required(t("REQUIRED")),
|
||||
})}
|
||||
validationSchema={
|
||||
ottInputVisible
|
||||
? Yup.object().shape({
|
||||
email: Yup.string()
|
||||
.email(t("EMAIL_ERROR"))
|
||||
.required(t("REQUIRED")),
|
||||
ott: Yup.string().required(t("REQUIRED")),
|
||||
})
|
||||
: Yup.object().shape({
|
||||
email: Yup.string()
|
||||
.email(t("EMAIL_ERROR"))
|
||||
.required(t("REQUIRED")),
|
||||
})
|
||||
}
|
||||
validateOnChange={false}
|
||||
validateOnBlur={false}
|
||||
onSubmit={!ottInputVisible ? requestOTT : requestEmailChange}
|
||||
|
@ -148,7 +159,9 @@ function ChangeEmailForm({ appName }: PageProps) {
|
|||
|
||||
<FormPaperFooter
|
||||
style={{
|
||||
justifyContent: ottInputVisible && "space-between",
|
||||
justifyContent: ottInputVisible
|
||||
? "space-between"
|
||||
: "normal",
|
||||
}}
|
||||
>
|
||||
{ottInputVisible && (
|
||||
|
|
|
@ -58,7 +58,8 @@ function SetPasswordForm(props: SetPasswordFormProps) {
|
|||
setFieldError("confirm", t("PASSPHRASE_MATCH_ERROR"));
|
||||
}
|
||||
} catch (e) {
|
||||
setFieldError("confirm", `${t("UNKNOWN_ERROR")} ${e.message}`);
|
||||
const message = e instanceof Error ? e.message : "";
|
||||
setFieldError("confirm", `${t("UNKNOWN_ERROR")} ${message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
@ -84,7 +84,8 @@ export default function SignUp({ router, appName, login }: SignUpProps) {
|
|||
setLocalReferralSource(referral);
|
||||
await sendOtt(appName, email);
|
||||
} catch (e) {
|
||||
setFieldError("confirm", `${t("UNKNOWN_ERROR")} ${e.message}`);
|
||||
const message = e instanceof Error ? e.message : "";
|
||||
setFieldError("confirm", `${t("UNKNOWN_ERROR")} ${message}`);
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
|
|
|
@ -26,7 +26,7 @@ export type VerifyTwoFactorCallback = (
|
|||
|
||||
export default function VerifyTwoFactor(props: Props) {
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
const otpInputRef = useRef(null);
|
||||
const otpInputRef = useRef<OtpInput>(null);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
const markSuccessful = async () => {
|
||||
|
@ -47,7 +47,8 @@ export default function VerifyTwoFactor(props: Props) {
|
|||
for (let i = 0; i < 6; i++) {
|
||||
otpInputRef.current?.focusPrevInput();
|
||||
}
|
||||
setFieldError("otp", `${t("UNKNOWN_ERROR")} ${e.message}`);
|
||||
const message = e instanceof Error ? e.message : "";
|
||||
setFieldError("otp", `${t("UNKNOWN_ERROR")} ${message}`);
|
||||
}
|
||||
setWaiting(false);
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ import { t } from "i18next";
|
|||
import LinkButton from "@ente/shared/components/LinkButton";
|
||||
|
||||
interface Iprops {
|
||||
twoFactorSecret: TwoFactorSecret;
|
||||
twoFactorSecret?: TwoFactorSecret;
|
||||
changeToQRMode: () => void;
|
||||
}
|
||||
export default function SetupManualMode({
|
||||
|
@ -16,7 +16,7 @@ export default function SetupManualMode({
|
|||
return (
|
||||
<>
|
||||
<Typography>{t("TWO_FACTOR_MANUAL_CODE_INSTRUCTION")}</Typography>
|
||||
<CodeBlock code={twoFactorSecret?.secretCode} my={2} />
|
||||
<CodeBlock code={twoFactorSecret?.secretCode ?? ""} my={2} />
|
||||
<LinkButton onClick={changeToQRMode}>
|
||||
{t("SCAN_QR_CODE")}
|
||||
</LinkButton>
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Typography } from "@mui/material";
|
|||
import { LoadingQRCode, QRCode } from "../styledComponents";
|
||||
|
||||
interface Iprops {
|
||||
twoFactorSecret: TwoFactorSecret;
|
||||
twoFactorSecret?: TwoFactorSecret;
|
||||
changeToManualMode: () => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { VerticallyCentered } from "@ente/shared/components/Container";
|
|||
import { useState } from "react";
|
||||
|
||||
interface Iprops {
|
||||
twoFactorSecret: TwoFactorSecret;
|
||||
twoFactorSecret?: TwoFactorSecret;
|
||||
}
|
||||
export function TwoFactorSetup({ twoFactorSecret }: Iprops) {
|
||||
const [setupMode, setSetupMode] = useState<SetupMode>(SetupMode.QR_CODE);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import { startSRPSetup, updateSRPAndKeys } from "@ente/accounts/api/srp";
|
||||
import SetPasswordForm, {
|
||||
type SetPasswordFormProps,
|
||||
|
@ -91,7 +92,7 @@ export default function ChangePassword({ appName }: PageProps) {
|
|||
|
||||
const srpA = convertBufferToBase64(srpClient.computeA());
|
||||
|
||||
const { setupID, srpB } = await startSRPSetup(token, {
|
||||
const { setupID, srpB } = await startSRPSetup(ensure(token), {
|
||||
srpUserID,
|
||||
srpSalt,
|
||||
srpVerifier,
|
||||
|
@ -102,7 +103,7 @@ export default function ChangePassword({ appName }: PageProps) {
|
|||
|
||||
const srpM1 = convertBufferToBase64(srpClient.computeM1());
|
||||
|
||||
await updateSRPAndKeys(token, {
|
||||
await updateSRPAndKeys(ensure(token), {
|
||||
setupID,
|
||||
srpM1,
|
||||
updatedKeyAttr: updatedKey,
|
||||
|
@ -121,15 +122,17 @@ export default function ChangePassword({ appName }: PageProps) {
|
|||
|
||||
const redirectToAppHome = () => {
|
||||
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
|
||||
router.push(APP_HOMES.get(appName));
|
||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
||||
router.push(APP_HOMES.get(appName) ?? "/");
|
||||
};
|
||||
|
||||
// TODO: Handle the case where user is not loaded yet.
|
||||
return (
|
||||
<VerticallyCentered>
|
||||
<FormPaper>
|
||||
<FormPaperTitle>{t("CHANGE_PASSWORD")}</FormPaperTitle>
|
||||
<SetPasswordForm
|
||||
userEmail={user?.email}
|
||||
userEmail={user?.email ?? ""}
|
||||
callback={onSubmit}
|
||||
buttonText={t("CHANGE_PASSWORD")}
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { isDevBuild } from "@/next/env";
|
||||
import log from "@/next/log";
|
||||
import { ensure } from "@/utils/ensure";
|
||||
import { APP_HOMES } from "@ente/shared/apps/constants";
|
||||
import type { PageProps } from "@ente/shared/apps/types";
|
||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||
|
@ -86,7 +87,8 @@ export default function Credentials({ appContext, appName }: PageProps) {
|
|||
}
|
||||
const token = getToken();
|
||||
if (key && token) {
|
||||
router.push(APP_HOMES.get(appName));
|
||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
||||
router.push(APP_HOMES.get(appName) ?? "/");
|
||||
return;
|
||||
}
|
||||
const kekEncryptedAttributes: B64EncryptionResult = getKey(
|
||||
|
@ -148,7 +150,7 @@ export default function Credentials({ appContext, appName }: PageProps) {
|
|||
id,
|
||||
twoFactorSessionID,
|
||||
passkeySessionID,
|
||||
} = await loginViaSRP(srpAttributes, kek);
|
||||
} = await loginViaSRP(ensure(srpAttributes), kek);
|
||||
setIsFirstLogin(true);
|
||||
if (passkeySessionID) {
|
||||
const sessionKeyAttributes =
|
||||
|
@ -168,7 +170,7 @@ export default function Credentials({ appContext, appName }: PageProps) {
|
|||
window.location.href = `${getAccountsURL()}/passkeys/flow?passkeySessionID=${passkeySessionID}&redirect=${
|
||||
window.location.origin
|
||||
}/passkeys/finish`;
|
||||
return;
|
||||
return undefined;
|
||||
} else if (twoFactorSessionID) {
|
||||
const sessionKeyAttributes =
|
||||
await cryptoWorker.generateKeyAndEncryptToB64(kek);
|
||||
|
@ -193,11 +195,15 @@ export default function Credentials({ appContext, appName }: PageProps) {
|
|||
id,
|
||||
isTwoFactorEnabled: false,
|
||||
});
|
||||
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
|
||||
if (keyAttributes)
|
||||
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
|
||||
return keyAttributes;
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.TWO_FACTOR_ENABLED) {
|
||||
if (
|
||||
e instanceof Error &&
|
||||
e.message != CustomError.TWO_FACTOR_ENABLED
|
||||
) {
|
||||
log.error("getKeyAttributes failed", e);
|
||||
}
|
||||
throw e;
|
||||
|
@ -221,10 +227,10 @@ export default function Credentials({ appContext, appName }: PageProps) {
|
|||
await saveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, key);
|
||||
await decryptAndStoreToken(keyAttributes, key);
|
||||
try {
|
||||
let srpAttributes: SRPAttributes = getData(
|
||||
let srpAttributes: SRPAttributes | null = getData(
|
||||
LS_KEYS.SRP_ATTRIBUTES,
|
||||
);
|
||||
if (!srpAttributes) {
|
||||
if (!srpAttributes && user) {
|
||||
srpAttributes = await getSRPAttributes(user.email);
|
||||
if (srpAttributes) {
|
||||
setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes);
|
||||
|
@ -258,10 +264,12 @@ export default function Credentials({ appContext, appName }: PageProps) {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: Handle the case when user is not present, or exclude that
|
||||
// possibility using types.
|
||||
return (
|
||||
<VerticallyCentered>
|
||||
<FormPaper style={{ minWidth: "320px" }}>
|
||||
<Header>{user.email}</Header>
|
||||
<Header>{user?.email ?? ""}</Header>
|
||||
|
||||
<VerifyMasterPasswordForm
|
||||
buttonText={t("VERIFY_PASSPHRASE")}
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
import log from "@/next/log";
|
||||
import { ensure } from "@/utils/ensure";
|
||||
import { putAttributes } from "@ente/accounts/api/user";
|
||||
import SetPasswordForm, {
|
||||
type SetPasswordFormProps,
|
||||
} from "@ente/accounts/components/SetPasswordForm";
|
||||
import { PAGES } from "@ente/accounts/constants/pages";
|
||||
import { configureSRP } from "@ente/accounts/services/srp";
|
||||
import { generateKeyAndSRPAttributes } from "@ente/accounts/utils/srp";
|
||||
import {
|
||||
generateAndSaveIntermediateKeyAttributes,
|
||||
saveKeyInSessionStore,
|
||||
} from "@ente/shared/crypto/helpers";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import { SESSION_KEYS, getKey } from "@ente/shared/storage/sessionStorage";
|
||||
import { t } from "i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import SetPasswordForm from "@ente/accounts/components/SetPasswordForm";
|
||||
import { PAGES } from "@ente/accounts/constants/pages";
|
||||
import { APP_HOMES } from "@ente/shared/apps/constants";
|
||||
import type { PageProps } from "@ente/shared/apps/types";
|
||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||
|
@ -22,12 +16,20 @@ import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
|
|||
import FormTitle from "@ente/shared/components/Form/FormPaper/Title";
|
||||
import LinkButton from "@ente/shared/components/LinkButton";
|
||||
import RecoveryKey from "@ente/shared/components/RecoveryKey";
|
||||
import {
|
||||
generateAndSaveIntermediateKeyAttributes,
|
||||
saveKeyInSessionStore,
|
||||
} from "@ente/shared/crypto/helpers";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import {
|
||||
justSignedUp,
|
||||
setJustSignedUp,
|
||||
} from "@ente/shared/storage/localStorage/helpers";
|
||||
import { SESSION_KEYS, getKey } from "@ente/shared/storage/sessionStorage";
|
||||
import type { KeyAttributes, User } from "@ente/shared/user/types";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Generate({ appContext, appName }: PageProps) {
|
||||
const { logout } = appContext;
|
||||
|
@ -54,7 +56,8 @@ export default function Generate({ appContext, appName }: PageProps) {
|
|||
setRecoveryModalView(true);
|
||||
setLoading(false);
|
||||
} else {
|
||||
router.push(APP_HOMES.get(appName));
|
||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
||||
router.push(APP_HOMES.get(appName) ?? "/");
|
||||
}
|
||||
} else if (keyAttributes?.encryptedKey) {
|
||||
router.push(PAGES.CREDENTIALS);
|
||||
|
@ -67,12 +70,16 @@ export default function Generate({ appContext, appName }: PageProps) {
|
|||
appContext.showNavBar(true);
|
||||
}, []);
|
||||
|
||||
const onSubmit = async (passphrase, setFieldError) => {
|
||||
const onSubmit: SetPasswordFormProps["callback"] = async (
|
||||
passphrase,
|
||||
setFieldError,
|
||||
) => {
|
||||
try {
|
||||
const { keyAttributes, masterKey, srpSetupAttributes } =
|
||||
await generateKeyAndSRPAttributes(passphrase);
|
||||
|
||||
await putAttributes(token, keyAttributes);
|
||||
// TODO: Refactor the code to not require this ensure
|
||||
await putAttributes(ensure(token), keyAttributes);
|
||||
await configureSRP(srpSetupAttributes);
|
||||
await generateAndSaveIntermediateKeyAttributes(
|
||||
passphrase,
|
||||
|
@ -90,7 +97,7 @@ export default function Generate({ appContext, appName }: PageProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
{loading ? (
|
||||
{loading || !user ? (
|
||||
<VerticallyCentered>
|
||||
<EnteSpinner />
|
||||
</VerticallyCentered>
|
||||
|
@ -100,16 +107,18 @@ export default function Generate({ appContext, appName }: PageProps) {
|
|||
show={recoverModalView}
|
||||
onHide={() => {
|
||||
setRecoveryModalView(false);
|
||||
router.push(APP_HOMES.get(appName));
|
||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
||||
router.push(APP_HOMES.get(appName) ?? "/");
|
||||
}}
|
||||
somethingWentWrong={() => null}
|
||||
/* TODO: Why is this error being ignored */
|
||||
somethingWentWrong={() => {}}
|
||||
/>
|
||||
) : (
|
||||
<VerticallyCentered>
|
||||
<FormPaper>
|
||||
<FormTitle>{t("SET_PASSPHRASE")}</FormTitle>
|
||||
<SetPasswordForm
|
||||
userEmail={user?.email}
|
||||
userEmail={user.email}
|
||||
callback={onSubmit}
|
||||
buttonText={t("SET_PASSPHRASE")}
|
||||
/>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import log from "@/next/log";
|
||||
import { ensure } from "@/utils/ensure";
|
||||
import { sendOtt } from "@ente/accounts/api/user";
|
||||
import { PAGES } from "@ente/accounts/constants/pages";
|
||||
import { APP_HOMES } from "@ente/shared/apps/constants";
|
||||
|
@ -29,7 +30,9 @@ const bip39 = require("bip39");
|
|||
bip39.setDefaultWordlist("english");
|
||||
|
||||
export default function Recover({ appContext, appName }: PageProps) {
|
||||
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
|
||||
const [keyAttributes, setKeyAttributes] = useState<
|
||||
KeyAttributes | undefined
|
||||
>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -50,7 +53,8 @@ export default function Recover({ appContext, appName }: PageProps) {
|
|||
if (!keyAttributes) {
|
||||
router.push(PAGES.GENERATE);
|
||||
} else if (key) {
|
||||
router.push(APP_HOMES.get(appName));
|
||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
||||
router.push(APP_HOMES.get(appName) ?? "/");
|
||||
} else {
|
||||
setKeyAttributes(keyAttributes);
|
||||
}
|
||||
|
@ -76,13 +80,14 @@ export default function Recover({ appContext, appName }: PageProps) {
|
|||
recoveryKey = bip39.mnemonicToEntropy(recoveryKey);
|
||||
}
|
||||
const cryptoWorker = await ComlinkCryptoWorker.getInstance();
|
||||
const keyAttr = ensure(keyAttributes);
|
||||
const masterKey = await cryptoWorker.decryptB64(
|
||||
keyAttributes.masterKeyEncryptedWithRecoveryKey,
|
||||
keyAttributes.masterKeyDecryptionNonce,
|
||||
keyAttr.masterKeyEncryptedWithRecoveryKey,
|
||||
keyAttr.masterKeyDecryptionNonce,
|
||||
await cryptoWorker.fromHex(recoveryKey),
|
||||
);
|
||||
await saveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, masterKey);
|
||||
await decryptAndStoreToken(keyAttributes, masterKey);
|
||||
await decryptAndStoreToken(keyAttr, masterKey);
|
||||
|
||||
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: false });
|
||||
router.push(PAGES.CHANGE_PASSWORD);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import log from "@/next/log";
|
||||
import { ensure } from "@/utils/ensure";
|
||||
import { recoverTwoFactor, removeTwoFactor } from "@ente/accounts/api/user";
|
||||
import { PAGES } from "@ente/accounts/constants/pages";
|
||||
import { TwoFactorType } from "@ente/accounts/constants/twofactor";
|
||||
|
@ -35,8 +36,8 @@ export default function Recover({
|
|||
const { logout } = appContext;
|
||||
|
||||
const [encryptedTwoFactorSecret, setEncryptedTwoFactorSecret] =
|
||||
useState<B64EncryptionResult>(null);
|
||||
const [sessionID, setSessionID] = useState(null);
|
||||
useState<Omit<B64EncryptionResult, "key"> | null>(null);
|
||||
const [sessionID, setSessionID] = useState<string | null>(null);
|
||||
const [doesHaveEncryptedRecoveryKey, setDoesHaveEncryptedRecoveryKey] =
|
||||
useState(false);
|
||||
|
||||
|
@ -70,7 +71,6 @@ export default function Recover({
|
|||
setEncryptedTwoFactorSecret({
|
||||
encryptedData: resp.encryptedSecret,
|
||||
nonce: resp.secretDecryptionNonce,
|
||||
key: null,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -111,13 +111,14 @@ export default function Recover({
|
|||
recoveryKey = bip39.mnemonicToEntropy(recoveryKey);
|
||||
}
|
||||
const cryptoWorker = await ComlinkCryptoWorker.getInstance();
|
||||
const { encryptedData, nonce } = ensure(encryptedTwoFactorSecret);
|
||||
const twoFactorSecret = await cryptoWorker.decryptB64(
|
||||
encryptedTwoFactorSecret.encryptedData,
|
||||
encryptedTwoFactorSecret.nonce,
|
||||
encryptedData,
|
||||
nonce,
|
||||
await cryptoWorker.fromHex(recoveryKey),
|
||||
);
|
||||
const resp = await removeTwoFactor(
|
||||
sessionID,
|
||||
ensure(sessionID),
|
||||
twoFactorSecret,
|
||||
twoFactorType,
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import log from "@/next/log";
|
||||
import { ensure } from "@/utils/ensure";
|
||||
import { enableTwoFactor, setupTwoFactor } from "@ente/accounts/api/user";
|
||||
import VerifyTwoFactor, {
|
||||
type VerifyTwoFactorCallback,
|
||||
|
@ -23,8 +24,9 @@ export enum SetupMode {
|
|||
}
|
||||
|
||||
export default function SetupTwoFactor({ appName }: PageProps) {
|
||||
const [twoFactorSecret, setTwoFactorSecret] =
|
||||
useState<TwoFactorSecret>(null);
|
||||
const [twoFactorSecret, setTwoFactorSecret] = useState<
|
||||
TwoFactorSecret | undefined
|
||||
>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -48,7 +50,7 @@ export default function SetupTwoFactor({ appName }: PageProps) {
|
|||
markSuccessful,
|
||||
) => {
|
||||
const recoveryEncryptedTwoFactorSecret = await encryptWithRecoveryKey(
|
||||
twoFactorSecret.secretCode,
|
||||
ensure(twoFactorSecret).secretCode,
|
||||
);
|
||||
await enableTwoFactor(otp, recoveryEncryptedTwoFactorSecret);
|
||||
await markSuccessful();
|
||||
|
@ -56,7 +58,8 @@ export default function SetupTwoFactor({ appName }: PageProps) {
|
|||
...getData(LS_KEYS.USER),
|
||||
isTwoFactorEnabled: true,
|
||||
});
|
||||
router.push(APP_HOMES.get(appName));
|
||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
||||
router.push(APP_HOMES.get(appName) ?? "/");
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -4,6 +4,7 @@ import VerifyTwoFactor, {
|
|||
} from "@ente/accounts/components/two-factor/VerifyForm";
|
||||
import { PAGES } from "@ente/accounts/constants/pages";
|
||||
|
||||
import { ensure } from "@/utils/ensure";
|
||||
import type { PageProps } from "@ente/shared/apps/types";
|
||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||
|
@ -55,7 +56,7 @@ export const TwoFactorVerify: React.FC<PageProps> = ({
|
|||
encryptedToken,
|
||||
id,
|
||||
});
|
||||
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
|
||||
setData(LS_KEYS.KEY_ATTRIBUTES, ensure(keyAttributes));
|
||||
const redirectURL = InMemoryStore.get(MS_KEYS.REDIRECT_URL);
|
||||
InMemoryStore.delete(MS_KEYS.REDIRECT_URL);
|
||||
router.push(redirectURL ?? PAGES.CREDENTIALS);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import type { UserVerificationResponse } from "@ente/accounts/types/user";
|
||||
import type { PageProps } from "@ente/shared/apps/types";
|
||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||
|
@ -110,7 +111,7 @@ export default function VerifyPage({ appContext, appName }: PageProps) {
|
|||
} else {
|
||||
if (getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES)) {
|
||||
await putAttributes(
|
||||
token,
|
||||
ensure(token),
|
||||
getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
"extends": "@/build-config/tsconfig-typecheck.json",
|
||||
"compilerOptions": {
|
||||
/* Also indicate expectation of a WebWorker runtime */
|
||||
"lib": ["ESnext", "DOM", "DOM.Iterable", "WebWorker"]
|
||||
}
|
||||
"lib": ["ESnext", "DOM", "DOM.Iterable", "WebWorker"],
|
||||
|
||||
/* Next.js insists on adding these. Sigh. */
|
||||
"allowJs": true,
|
||||
"incremental": true
|
||||
},
|
||||
/* Next.js insists on adding this, even though we don't need it. */
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
@ -67,8 +67,10 @@
|
|||
"noUnusedParameters": true,
|
||||
"noUnusedLocals": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
/* e.g. makes array indexing returns undefined */
|
||||
/* e.g. makes array indexing returns undefined. */
|
||||
"noUncheckedIndexedAccess": true,
|
||||
/* Treat optional (?) properties and properties where undefined is a
|
||||
valid value separately */
|
||||
"exactOptionalPropertyTypes": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { TwoFactorType } from "@ente/accounts/constants/twofactor";
|
||||
import type { SetDialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
|
||||
import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
|
||||
import { APPS } from "./constants";
|
||||
|
||||
export interface PageProps {
|
||||
appContext: {
|
||||
showNavBar: (show: boolean) => void;
|
||||
isMobile: boolean;
|
||||
setDialogBoxAttributesV2: SetDialogBoxAttributesV2;
|
||||
setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void;
|
||||
logout: () => void;
|
||||
};
|
||||
appName: APPS;
|
||||
|
|
|
@ -6,7 +6,7 @@ import CopyButton from "./CopyButton";
|
|||
import { CodeWrapper, CopyButtonWrapper, Wrapper } from "./styledComponents";
|
||||
|
||||
type Iprops = React.PropsWithChildren<{
|
||||
code: string;
|
||||
code: string | null;
|
||||
wordBreak?: "normal" | "break-all" | "keep-all" | "break-word";
|
||||
}>;
|
||||
|
||||
|
|
|
@ -6,12 +6,17 @@ import {
|
|||
Typography,
|
||||
type DialogProps,
|
||||
} from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
const DialogTitleWithCloseButton = (props) => {
|
||||
const { children, onClose, ...other } = props;
|
||||
interface DialogTitleWithCloseButtonProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const DialogTitleWithCloseButton: React.FC<
|
||||
React.PropsWithChildren<DialogTitleWithCloseButtonProps>
|
||||
> = ({ children, onClose }) => {
|
||||
return (
|
||||
<DialogTitle {...other}>
|
||||
<DialogTitle>
|
||||
<SpaceBetweenFlex>
|
||||
<Typography variant="h3" fontWeight={"bold"}>
|
||||
{children}
|
||||
|
|
|
@ -54,9 +54,7 @@ export default function DialogBox({
|
|||
{attributes.title && (
|
||||
<DialogTitleWithCloseButton
|
||||
onClose={
|
||||
titleCloseButton &&
|
||||
!attributes.nonClosable &&
|
||||
handleClose
|
||||
titleCloseButton && !attributes.nonClosable && onClose
|
||||
}
|
||||
>
|
||||
{attributes.title}
|
||||
|
|
|
@ -15,7 +15,7 @@ import type { DialogBoxAttributesV2 } from "./types";
|
|||
type IProps = React.PropsWithChildren<
|
||||
Omit<DialogProps, "onClose"> & {
|
||||
onClose: () => void;
|
||||
attributes: DialogBoxAttributesV2;
|
||||
attributes?: DialogBoxAttributesV2;
|
||||
}
|
||||
>;
|
||||
|
||||
|
@ -96,7 +96,9 @@ export default function DialogBoxV2({
|
|||
size="large"
|
||||
color={attributes.proceed?.variant}
|
||||
onClick={async () => {
|
||||
await attributes.proceed.action(setLoading);
|
||||
await attributes.proceed?.action(
|
||||
setLoading,
|
||||
);
|
||||
|
||||
onClose();
|
||||
}}
|
||||
|
@ -110,7 +112,7 @@ export default function DialogBoxV2({
|
|||
size="large"
|
||||
color={attributes.close?.variant ?? "secondary"}
|
||||
onClick={() => {
|
||||
attributes.close.action &&
|
||||
attributes.close?.action &&
|
||||
attributes.close?.action();
|
||||
onClose();
|
||||
}}
|
||||
|
|
|
@ -39,7 +39,3 @@ export interface DialogBoxAttributesV2 {
|
|||
}[];
|
||||
buttonDirection?: "row" | "column";
|
||||
}
|
||||
|
||||
export type SetDialogBoxAttributesV2 = React.Dispatch<
|
||||
React.SetStateAction<DialogBoxAttributesV2>
|
||||
>;
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import Done from "@mui/icons-material/Done";
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
type ButtonProps,
|
||||
type PaletteColor,
|
||||
} from "@mui/material";
|
||||
import { Button, CircularProgress, type ButtonProps } from "@mui/material";
|
||||
|
||||
interface Iprops extends ButtonProps {
|
||||
loading?: boolean;
|
||||
|
@ -26,11 +22,10 @@ export default function EnteButton({
|
|||
...sx,
|
||||
...((loading || success) && {
|
||||
"&.Mui-disabled": (theme) => ({
|
||||
backgroundColor: (
|
||||
theme.palette[props.color] as PaletteColor
|
||||
).main,
|
||||
color: (theme.palette[props.color] as PaletteColor)
|
||||
.contrastText,
|
||||
// TODO: Refactor to not need this ensure.
|
||||
backgroundColor:
|
||||
theme.palette[ensure(props.color)].main,
|
||||
color: theme.palette[ensure(props.color)].contrastText,
|
||||
}),
|
||||
}),
|
||||
}}
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
import { styled } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
interface EnteLogoProps {
|
||||
/**
|
||||
* The height of the logo image.
|
||||
*
|
||||
* Default: 18
|
||||
*/
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export const EnteLogo: React.FC<EnteLogoProps> = ({ height }) => {
|
||||
return (
|
||||
<LogoImage height={height ?? 18} alt="logo" src="/images/ente.svg" />
|
||||
);
|
||||
};
|
||||
|
||||
const LogoImage = styled("img")`
|
||||
margin: 3px 0;
|
||||
pointer-events: none;
|
||||
vertical-align: middle;
|
||||
`;
|
||||
|
||||
export function EnteLogo(props) {
|
||||
return (
|
||||
<LogoImage height={18} alt="logo" src="/images/ente.svg" {...props} />
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import React from "react";
|
||||
import { CenteredFlex } from "../../components/Container";
|
||||
import { EnteLogo } from "../EnteLogo";
|
||||
import NavbarBase from "./base";
|
||||
|
||||
export default function AppNavbar({ isMobile }) {
|
||||
interface AppNavbarProps {
|
||||
isMobile: boolean;
|
||||
}
|
||||
|
||||
export const AppNavbar: React.FC<AppNavbarProps> = ({ isMobile }) => {
|
||||
return (
|
||||
<NavbarBase isMobile={isMobile}>
|
||||
<CenteredFlex>
|
||||
|
@ -10,4 +15,4 @@ export default function AppNavbar({ isMobile }) {
|
|||
</CenteredFlex>
|
||||
</NavbarBase>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createContext } from "react";
|
||||
|
||||
export const OverflowMenuContext = createContext({
|
||||
close: () => null,
|
||||
close: () => {},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { IconButton, styled, type PaperProps } from "@mui/material";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import Menu, { type MenuProps } from "@mui/material/Menu";
|
||||
import React, { useState } from "react";
|
||||
import { OverflowMenuContext } from "./context";
|
||||
|
||||
|
@ -31,7 +31,9 @@ export default function OverflowMenu({
|
|||
triggerButtonProps,
|
||||
menuPaperProps,
|
||||
}: Iprops) {
|
||||
const [sortByEl, setSortByEl] = useState(null);
|
||||
const [sortByEl, setSortByEl] = useState<MenuProps["anchorEl"] | null>(
|
||||
null,
|
||||
);
|
||||
const handleClose = () => setSortByEl(null);
|
||||
return (
|
||||
<OverflowMenuContext.Provider value={{ close: handleClose }}>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import type { PageProps } from "@ente/shared/apps/types";
|
||||
import CodeBlock from "@ente/shared/components/CodeBlock";
|
||||
import DialogTitleWithCloseButton from "@ente/shared/components/DialogBox/TitleWithCloseButton";
|
||||
|
@ -28,7 +29,7 @@ interface Props {
|
|||
}
|
||||
|
||||
function RecoveryKey({ somethingWentWrong, appContext, ...props }: Props) {
|
||||
const [recoveryKey, setRecoveryKey] = useState(null);
|
||||
const [recoveryKey, setRecoveryKey] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.show) {
|
||||
|
@ -47,7 +48,7 @@ function RecoveryKey({ somethingWentWrong, appContext, ...props }: Props) {
|
|||
}, [props.show]);
|
||||
|
||||
function onSaveClick() {
|
||||
downloadAsFile(RECOVERY_KEY_FILE_NAME, recoveryKey);
|
||||
downloadAsFile(RECOVERY_KEY_FILE_NAME, ensure(recoveryKey));
|
||||
props.onHide();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@ import { THEME_COLOR } from "@ente/shared/themes/constants";
|
|||
import DarkModeIcon from "@mui/icons-material/DarkMode";
|
||||
import LightModeIcon from "@mui/icons-material/LightMode";
|
||||
import { ToggleButton, ToggleButtonGroup } from "@mui/material";
|
||||
|
||||
interface Iprops {
|
||||
themeColor: THEME_COLOR;
|
||||
setThemeColor: (theme: THEME_COLOR) => void;
|
||||
setThemeColor: (themeColor: THEME_COLOR) => void;
|
||||
}
|
||||
|
||||
export default function ThemeSwitcher({ themeColor, setThemeColor }: Iprops) {
|
||||
const handleChange = (event, themeColor: THEME_COLOR) => {
|
||||
if (themeColor !== null) {
|
||||
|
|
|
@ -10,8 +10,8 @@ import { CustomError } from "../error";
|
|||
import type { KeyAttributes, User } from "../user/types";
|
||||
|
||||
export interface VerifyMasterPasswordFormProps {
|
||||
user: User;
|
||||
keyAttributes: KeyAttributes;
|
||||
user: User | undefined;
|
||||
keyAttributes: KeyAttributes | undefined;
|
||||
callback: (
|
||||
key: string,
|
||||
kek: string,
|
||||
|
@ -20,7 +20,7 @@ export interface VerifyMasterPasswordFormProps {
|
|||
) => void;
|
||||
buttonText: string;
|
||||
submitButtonProps?: ButtonProps;
|
||||
getKeyAttributes?: (kek: string) => Promise<KeyAttributes>;
|
||||
getKeyAttributes?: (kek: string) => Promise<KeyAttributes | undefined>;
|
||||
srpAttributes?: SRPAttributes;
|
||||
}
|
||||
|
||||
|
@ -48,14 +48,15 @@ export default function VerifyMasterPasswordForm({
|
|||
srpAttributes.opsLimit,
|
||||
srpAttributes.memLimit,
|
||||
);
|
||||
} else {
|
||||
} else if (keyAttributes) {
|
||||
kek = await cryptoWorker.deriveKey(
|
||||
passphrase,
|
||||
keyAttributes.kekSalt,
|
||||
keyAttributes.opsLimit,
|
||||
keyAttributes.memLimit,
|
||||
);
|
||||
}
|
||||
} else
|
||||
throw new Error("Both SRP and key attributes are missing");
|
||||
} catch (e) {
|
||||
log.error("failed to derive key", e);
|
||||
throw Error(CustomError.WEAK_DEVICE);
|
||||
|
|
|
@ -116,7 +116,6 @@ export async function encryptWithRecoveryKey(key: string) {
|
|||
}
|
||||
|
||||
export const getRecoveryKey = async () => {
|
||||
let recoveryKey: string = null;
|
||||
try {
|
||||
const cryptoWorker = await ComlinkCryptoWorker.getInstance();
|
||||
|
||||
|
@ -126,6 +125,7 @@ export const getRecoveryKey = async () => {
|
|||
recoveryKeyDecryptionNonce,
|
||||
} = keyAttributes;
|
||||
const masterKey = await getActualKey();
|
||||
let recoveryKey: string;
|
||||
if (recoveryKeyEncryptedWithMasterKey) {
|
||||
recoveryKey = await cryptoWorker.decryptB64(
|
||||
recoveryKeyEncryptedWithMasterKey,
|
||||
|
|
|
@ -308,6 +308,7 @@ export async function deriveSensitiveKey(passphrase: string, salt: string) {
|
|||
memLimit /= 2;
|
||||
}
|
||||
}
|
||||
throw new Error("Failed to derive key: Memory limit exceeded");
|
||||
}
|
||||
|
||||
export async function deriveInteractiveKey(passphrase: string, salt: string) {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function useLocalState<T>(
|
||||
key: LS_KEYS,
|
||||
initialValue?: T,
|
||||
): [T, Dispatch<SetStateAction<T>>] {
|
||||
initialValue: T,
|
||||
): [T, (newValue: T) => void] {
|
||||
const [value, setValue] = useState<T>(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -29,19 +29,10 @@ export enum LS_KEYS {
|
|||
CLIENT_PACKAGE = "clientPackage",
|
||||
}
|
||||
|
||||
export const setData = (key: LS_KEYS, value: object) => {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
export const setData = (key: LS_KEYS, value: object) =>
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
};
|
||||
|
||||
export const removeData = (key: LS_KEYS) => {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
localStorage.removeItem(key);
|
||||
};
|
||||
export const removeData = (key: LS_KEYS) => localStorage.removeItem(key);
|
||||
|
||||
export const getData = (key: LS_KEYS) => {
|
||||
try {
|
||||
|
@ -60,9 +51,4 @@ export const getData = (key: LS_KEYS) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const clearData = () => {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
localStorage.clear();
|
||||
};
|
||||
export const clearData = () => localStorage.clear();
|
||||
|
|
|
@ -3,31 +3,14 @@ export enum SESSION_KEYS {
|
|||
KEY_ENCRYPTION_KEY = "keyEncryptionKey",
|
||||
}
|
||||
|
||||
export const setKey = (key: SESSION_KEYS, value: object) => {
|
||||
if (typeof sessionStorage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
export const setKey = (key: SESSION_KEYS, value: object) =>
|
||||
sessionStorage.setItem(key, JSON.stringify(value));
|
||||
};
|
||||
|
||||
export const getKey = (key: SESSION_KEYS) => {
|
||||
if (typeof sessionStorage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
const value = sessionStorage.getItem(key);
|
||||
return value && JSON.parse(value);
|
||||
};
|
||||
|
||||
export const removeKey = (key: SESSION_KEYS) => {
|
||||
if (typeof sessionStorage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
sessionStorage.removeItem(key);
|
||||
};
|
||||
export const removeKey = (key: SESSION_KEYS) => sessionStorage.removeItem(key);
|
||||
|
||||
export const clearKeys = () => {
|
||||
if (typeof sessionStorage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
sessionStorage.clear();
|
||||
};
|
||||
export const clearKeys = () => sessionStorage.clear();
|
||||
|
|
|
@ -33,7 +33,7 @@ export const getComponents = (
|
|||
styleOverrides: {
|
||||
root: {
|
||||
".MuiBackdrop-root": {
|
||||
backgroundColor: colors.backdrop.faint,
|
||||
backgroundColor: colors.backdrop?.faint,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -42,10 +42,10 @@ export const getComponents = (
|
|||
styleOverrides: {
|
||||
root: {
|
||||
".MuiBackdrop-root": {
|
||||
backgroundColor: colors.backdrop.faint,
|
||||
backgroundColor: colors.backdrop?.faint,
|
||||
},
|
||||
"& .MuiDialog-paper": {
|
||||
filter: getDropShadowStyle(colors.shadows.float),
|
||||
filter: getDropShadowStyle(colors.shadows?.float),
|
||||
},
|
||||
"& .MuiDialogTitle-root": {
|
||||
padding: "16px",
|
||||
|
@ -72,14 +72,14 @@ export const getComponents = (
|
|||
},
|
||||
MuiLink: {
|
||||
defaultProps: {
|
||||
color: colors.accent.A500,
|
||||
color: colors.accent?.A500,
|
||||
underline: "none",
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
"&:hover": {
|
||||
underline: "always",
|
||||
color: colors.accent.A500,
|
||||
color: colors.accent?.A500,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -95,8 +95,8 @@ export const getComponents = (
|
|||
borderRadius: "4px",
|
||||
textTransform: "none",
|
||||
fontWeight: "bold",
|
||||
fontSize: typography.body.fontSize,
|
||||
lineHeight: typography.body.lineHeight,
|
||||
fontSize: typography.body?.fontSize,
|
||||
lineHeight: typography.body?.lineHeight,
|
||||
},
|
||||
startIcon: {
|
||||
marginRight: "12px",
|
||||
|
@ -191,8 +191,8 @@ export const getComponents = (
|
|||
},
|
||||
});
|
||||
|
||||
const getDropShadowStyle = (shadows: Shadow[]) => {
|
||||
return shadows
|
||||
const getDropShadowStyle = (shadows: Shadow[] | undefined) => {
|
||||
return (shadows ?? [])
|
||||
.map(
|
||||
(shadow) =>
|
||||
`drop-shadow(${shadow.x}px ${shadow.y}px ${shadow.blur}px ${shadow.color})`,
|
||||
|
@ -200,20 +200,29 @@ const getDropShadowStyle = (shadows: Shadow[]) => {
|
|||
.join(" ");
|
||||
};
|
||||
|
||||
function getIconColor(ownerState, colors: ThemeColorsOptions) {
|
||||
interface IconColorableOwnerState {
|
||||
color?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
function getIconColor(
|
||||
ownerState: IconColorableOwnerState,
|
||||
colors: ThemeColorsOptions,
|
||||
) {
|
||||
switch (ownerState.color) {
|
||||
case "primary":
|
||||
return {
|
||||
color: colors.stroke.base,
|
||||
color: colors.stroke?.base,
|
||||
};
|
||||
case "secondary":
|
||||
return {
|
||||
color: colors.stroke.muted,
|
||||
color: colors.stroke?.muted,
|
||||
};
|
||||
}
|
||||
if (ownerState.disabled) {
|
||||
return {
|
||||
color: colors.stroke.faint,
|
||||
color: colors.stroke?.faint,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
1
web/packages/shared/themes/mui-theme.d.ts
vendored
1
web/packages/shared/themes/mui-theme.d.ts
vendored
|
@ -70,6 +70,7 @@ declare module "@mui/material/Button" {
|
|||
success: false;
|
||||
info: false;
|
||||
warning: false;
|
||||
inherit: false;
|
||||
}
|
||||
}
|
||||
declare module "@mui/material/Checkbox" {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import type { PaletteOptions, ThemeColorsOptions } from "@mui/material";
|
||||
import { THEME_COLOR } from "../constants";
|
||||
|
||||
|
@ -20,38 +21,39 @@ export const getPalletteOptions = (
|
|||
): PaletteOptions => {
|
||||
return {
|
||||
primary: {
|
||||
main: colors.fill.base,
|
||||
dark: colors.fill.basePressed,
|
||||
// TODO: Refactor this code to not require this ensure
|
||||
main: ensure(colors.fill?.base),
|
||||
dark: colors.fill?.basePressed,
|
||||
contrastText:
|
||||
themeColor === "dark" ? colors.black.base : colors.white.base,
|
||||
themeColor === "dark" ? colors.black?.base : colors.white?.base,
|
||||
},
|
||||
secondary: {
|
||||
main: colors.fill.faint,
|
||||
dark: colors.fill.faintPressed,
|
||||
contrastText: colors.text.base,
|
||||
main: ensure(colors.fill?.faint),
|
||||
dark: colors.fill?.faintPressed,
|
||||
contrastText: colors.text?.base,
|
||||
},
|
||||
accent: {
|
||||
main: colors.accent.A500,
|
||||
dark: colors.accent.A700,
|
||||
contrastText: colors.white.base,
|
||||
main: ensure(colors.accent?.A500),
|
||||
dark: colors.accent?.A700,
|
||||
contrastText: colors.white?.base,
|
||||
},
|
||||
critical: {
|
||||
main: colors.danger.A700,
|
||||
dark: colors.danger.A800,
|
||||
contrastText: colors.white.base,
|
||||
main: ensure(colors.danger?.A700),
|
||||
dark: colors.danger?.A800,
|
||||
contrastText: colors.white?.base,
|
||||
},
|
||||
background: {
|
||||
default: colors.background.base,
|
||||
paper: colors.background.elevated,
|
||||
default: colors.background?.base,
|
||||
paper: colors.background?.elevated,
|
||||
},
|
||||
text: {
|
||||
primary: colors.text.base,
|
||||
secondary: colors.text.muted,
|
||||
disabled: colors.text.faint,
|
||||
base: colors.text.base,
|
||||
muted: colors.text.muted,
|
||||
faint: colors.text.faint,
|
||||
primary: colors.text?.base,
|
||||
secondary: colors.text?.muted,
|
||||
disabled: colors.text?.faint,
|
||||
base: colors.text?.base,
|
||||
muted: colors.text?.muted,
|
||||
faint: colors.text?.faint,
|
||||
},
|
||||
divider: colors.stroke.faint,
|
||||
divider: colors.stroke?.faint,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import { THEME_COLOR } from "./constants";
|
||||
|
||||
export type SetTheme = React.Dispatch<React.SetStateAction<THEME_COLOR>>;
|
|
@ -17,6 +17,7 @@
|
|||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"themes/mui-theme.d.ts",
|
||||
"../next/log-web.ts"
|
||||
"../next/log-web.ts",
|
||||
"../next/global-electron.d.ts"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -26,6 +26,12 @@ export function isPromise<T>(obj: T | Promise<T>): obj is Promise<T> {
|
|||
export async function retryAsyncFunction<T>(
|
||||
request: (abort?: () => void) => Promise<T>,
|
||||
waitTimeBeforeNextTry?: number[],
|
||||
// Need to use @ts-ignore since this same file is currently included with
|
||||
// varying tsconfigs, and the error is only surfaced in the stricter ones of
|
||||
// them.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore TSC fails to detect that the exit of the loop is unreachable
|
||||
): Promise<T> {
|
||||
if (!waitTimeBeforeNextTry) waitTimeBeforeNextTry = [2000, 5000, 10000];
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue