From 68648d2f6c8748a266609d06565e7b1d93573a7c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 11:32:17 +0530 Subject: [PATCH 01/15] Remove nesting --- web/apps/auth/src/pages/{auth/index.tsx => auth.tsx} | 0 .../auth/src/pages/{change-email/index.tsx => change-email.tsx} | 0 .../pages/{change-password/index.tsx => change-password.tsx} | 0 .../auth/src/pages/{credentials/index.tsx => credentials.tsx} | 0 web/apps/auth/src/pages/{generate/index.tsx => generate.tsx} | 0 web/apps/auth/src/pages/{login/index.tsx => login.tsx} | 0 .../auth/src/pages/passkeys/{finish/index.tsx => finish.tsx} | 0 web/apps/auth/src/pages/{recover/index.tsx => recover.tsx} | 0 web/apps/auth/src/pages/{signup/index.tsx => signup.tsx} | 0 .../src/pages/two-factor/{recover/index.tsx => recover.tsx} | 0 .../auth/src/pages/two-factor/{setup/index.tsx => setup.tsx} | 0 .../auth/src/pages/two-factor/{verify/index.tsx => verify.tsx} | 2 +- web/apps/auth/src/pages/{verify/index.tsx => verify.tsx} | 0 13 files changed, 1 insertion(+), 1 deletion(-) rename web/apps/auth/src/pages/{auth/index.tsx => auth.tsx} (100%) rename web/apps/auth/src/pages/{change-email/index.tsx => change-email.tsx} (100%) rename web/apps/auth/src/pages/{change-password/index.tsx => change-password.tsx} (100%) rename web/apps/auth/src/pages/{credentials/index.tsx => credentials.tsx} (100%) rename web/apps/auth/src/pages/{generate/index.tsx => generate.tsx} (100%) rename web/apps/auth/src/pages/{login/index.tsx => login.tsx} (100%) rename web/apps/auth/src/pages/passkeys/{finish/index.tsx => finish.tsx} (100%) rename web/apps/auth/src/pages/{recover/index.tsx => recover.tsx} (100%) rename web/apps/auth/src/pages/{signup/index.tsx => signup.tsx} (100%) rename web/apps/auth/src/pages/two-factor/{recover/index.tsx => recover.tsx} (100%) rename web/apps/auth/src/pages/two-factor/{setup/index.tsx => setup.tsx} (100%) rename web/apps/auth/src/pages/two-factor/{verify/index.tsx => verify.tsx} (89%) rename web/apps/auth/src/pages/{verify/index.tsx => verify.tsx} (100%) diff --git a/web/apps/auth/src/pages/auth/index.tsx b/web/apps/auth/src/pages/auth.tsx similarity index 100% rename from web/apps/auth/src/pages/auth/index.tsx rename to web/apps/auth/src/pages/auth.tsx diff --git a/web/apps/auth/src/pages/change-email/index.tsx b/web/apps/auth/src/pages/change-email.tsx similarity index 100% rename from web/apps/auth/src/pages/change-email/index.tsx rename to web/apps/auth/src/pages/change-email.tsx diff --git a/web/apps/auth/src/pages/change-password/index.tsx b/web/apps/auth/src/pages/change-password.tsx similarity index 100% rename from web/apps/auth/src/pages/change-password/index.tsx rename to web/apps/auth/src/pages/change-password.tsx diff --git a/web/apps/auth/src/pages/credentials/index.tsx b/web/apps/auth/src/pages/credentials.tsx similarity index 100% rename from web/apps/auth/src/pages/credentials/index.tsx rename to web/apps/auth/src/pages/credentials.tsx diff --git a/web/apps/auth/src/pages/generate/index.tsx b/web/apps/auth/src/pages/generate.tsx similarity index 100% rename from web/apps/auth/src/pages/generate/index.tsx rename to web/apps/auth/src/pages/generate.tsx diff --git a/web/apps/auth/src/pages/login/index.tsx b/web/apps/auth/src/pages/login.tsx similarity index 100% rename from web/apps/auth/src/pages/login/index.tsx rename to web/apps/auth/src/pages/login.tsx diff --git a/web/apps/auth/src/pages/passkeys/finish/index.tsx b/web/apps/auth/src/pages/passkeys/finish.tsx similarity index 100% rename from web/apps/auth/src/pages/passkeys/finish/index.tsx rename to web/apps/auth/src/pages/passkeys/finish.tsx diff --git a/web/apps/auth/src/pages/recover/index.tsx b/web/apps/auth/src/pages/recover.tsx similarity index 100% rename from web/apps/auth/src/pages/recover/index.tsx rename to web/apps/auth/src/pages/recover.tsx diff --git a/web/apps/auth/src/pages/signup/index.tsx b/web/apps/auth/src/pages/signup.tsx similarity index 100% rename from web/apps/auth/src/pages/signup/index.tsx rename to web/apps/auth/src/pages/signup.tsx diff --git a/web/apps/auth/src/pages/two-factor/recover/index.tsx b/web/apps/auth/src/pages/two-factor/recover.tsx similarity index 100% rename from web/apps/auth/src/pages/two-factor/recover/index.tsx rename to web/apps/auth/src/pages/two-factor/recover.tsx diff --git a/web/apps/auth/src/pages/two-factor/setup/index.tsx b/web/apps/auth/src/pages/two-factor/setup.tsx similarity index 100% rename from web/apps/auth/src/pages/two-factor/setup/index.tsx rename to web/apps/auth/src/pages/two-factor/setup.tsx diff --git a/web/apps/auth/src/pages/two-factor/verify/index.tsx b/web/apps/auth/src/pages/two-factor/verify.tsx similarity index 89% rename from web/apps/auth/src/pages/two-factor/verify/index.tsx rename to web/apps/auth/src/pages/two-factor/verify.tsx index 2243a4354..85eb7ff1b 100644 --- a/web/apps/auth/src/pages/two-factor/verify/index.tsx +++ b/web/apps/auth/src/pages/two-factor/verify.tsx @@ -1,7 +1,7 @@ import TwoFactorVerifyPage from "@ente/accounts/pages/two-factor/verify"; import { APPS } from "@ente/shared/apps/constants"; import { useContext } from "react"; -import { AppContext } from "../../_app"; +import { AppContext } from "../_app"; export default function TwoFactorVerify() { const appContext = useContext(AppContext); diff --git a/web/apps/auth/src/pages/verify/index.tsx b/web/apps/auth/src/pages/verify.tsx similarity index 100% rename from web/apps/auth/src/pages/verify/index.tsx rename to web/apps/auth/src/pages/verify.tsx From bf707ae02dbd98acc7506446227ebd8bb1902e7a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 11:37:47 +0530 Subject: [PATCH 02/15] Inline --- web/apps/auth/src/components/AuthFooter.tsx | 23 -------------------- web/apps/auth/src/pages/auth.tsx | 24 +++++++++++++++++++-- 2 files changed, 22 insertions(+), 25 deletions(-) delete mode 100644 web/apps/auth/src/components/AuthFooter.tsx diff --git a/web/apps/auth/src/components/AuthFooter.tsx b/web/apps/auth/src/components/AuthFooter.tsx deleted file mode 100644 index 029103125..000000000 --- a/web/apps/auth/src/components/AuthFooter.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Button } from "@mui/material"; -import { t } from "i18next"; - -export const AuthFooter = () => { - return ( -
-

{t("AUTH_DOWNLOAD_MOBILE_APP")}

- - - -
- ); -}; diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index 55dc33ce6..dc68114a1 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -3,8 +3,7 @@ import EnteSpinner from "@ente/shared/components/EnteSpinner"; import { AUTH_PAGES as PAGES } from "@ente/shared/constants/pages"; import { CustomError } from "@ente/shared/error"; import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore"; -import { TextField } from "@mui/material"; -import { AuthFooter } from "components/AuthFooter"; +import { Button, TextField } from "@mui/material"; import AuthNavbar from "components/Navbar"; import OTPDisplay from "components/OTPDisplay"; import { t } from "i18next"; @@ -127,3 +126,24 @@ const AuthenticatorCodesPage = () => { }; export default AuthenticatorCodesPage; + +const AuthFooter: React.FC = () => { + return ( +
+

{t("AUTH_DOWNLOAD_MOBILE_APP")}

+ + + +
+ ); +}; From b26afdcf2ef2209b9a9b4c3851966ad9af35bfa2 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 11:38:52 +0530 Subject: [PATCH 03/15] Inline --- web/apps/auth/src/components/Navbar.tsx | 35 ---------------- web/apps/auth/src/components/OTPDisplay.tsx | 39 +++++++++++++++++- .../auth/src/components/TimerProgress.tsx | 41 ------------------- web/apps/auth/src/pages/auth.tsx | 40 ++++++++++++++++-- web/apps/auth/src/services/index.ts | 16 +++++++- web/apps/auth/src/types/api.ts | 13 ------ 6 files changed, 90 insertions(+), 94 deletions(-) delete mode 100644 web/apps/auth/src/components/Navbar.tsx delete mode 100644 web/apps/auth/src/components/TimerProgress.tsx delete mode 100644 web/apps/auth/src/types/api.ts diff --git a/web/apps/auth/src/components/Navbar.tsx b/web/apps/auth/src/components/Navbar.tsx deleted file mode 100644 index 87614d643..000000000 --- a/web/apps/auth/src/components/Navbar.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { HorizontalFlex } from "@ente/shared/components/Container"; -import { EnteLogo } from "@ente/shared/components/EnteLogo"; -import NavbarBase from "@ente/shared/components/Navbar/base"; -import OverflowMenu from "@ente/shared/components/OverflowMenu/menu"; -import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option"; -import LogoutOutlined from "@mui/icons-material/LogoutOutlined"; -import MoreHoriz from "@mui/icons-material/MoreHoriz"; -import { t } from "i18next"; -import { AppContext } from "pages/_app"; -import React from "react"; - -export default function AuthNavbar() { - const { isMobile, logout } = React.useContext(AppContext); - return ( - - - - - - } - > - } - onClick={logout} - > - {t("LOGOUT")} - - - - - ); -} diff --git a/web/apps/auth/src/components/OTPDisplay.tsx b/web/apps/auth/src/components/OTPDisplay.tsx index 38de665aa..1168fe8cb 100644 --- a/web/apps/auth/src/components/OTPDisplay.tsx +++ b/web/apps/auth/src/components/OTPDisplay.tsx @@ -3,7 +3,6 @@ import { t } from "i18next"; import { HOTP, TOTP } from "otpauth"; import { useEffect, useState } from "react"; import { Code } from "types/code"; -import TimerProgress from "./TimerProgress"; const TOTPDisplay = ({ issuer, account, code, nextCode, period }) => { return ( @@ -235,3 +234,41 @@ const OTPDisplay = (props: OTPDisplayProps) => { }; export default OTPDisplay; + +const TimerProgress = ({ period }) => { + const [progress, setProgress] = useState(0); + const [ticker, setTicker] = useState(null); + const microSecondsInPeriod = period * 1000000; + + const startTicker = () => { + const ticker = setInterval(() => { + updateTimeRemaining(); + }, 10); + setTicker(ticker); + }; + + const updateTimeRemaining = () => { + const timeRemaining = + microSecondsInPeriod - + ((new Date().getTime() * 1000) % microSecondsInPeriod); + setProgress(timeRemaining / microSecondsInPeriod); + }; + + useEffect(() => { + startTicker(); + return () => clearInterval(ticker); + }, []); + + const color = progress > 0.4 ? "green" : "orange"; + + return ( +
+ ); +}; diff --git a/web/apps/auth/src/components/TimerProgress.tsx b/web/apps/auth/src/components/TimerProgress.tsx deleted file mode 100644 index d1f3726f6..000000000 --- a/web/apps/auth/src/components/TimerProgress.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useEffect, useState } from "react"; - -const TimerProgress = ({ period }) => { - const [progress, setProgress] = useState(0); - const [ticker, setTicker] = useState(null); - const microSecondsInPeriod = period * 1000000; - - const startTicker = () => { - const ticker = setInterval(() => { - updateTimeRemaining(); - }, 10); - setTicker(ticker); - }; - - const updateTimeRemaining = () => { - const timeRemaining = - microSecondsInPeriod - - ((new Date().getTime() * 1000) % microSecondsInPeriod); - setProgress(timeRemaining / microSecondsInPeriod); - }; - - useEffect(() => { - startTicker(); - return () => clearInterval(ticker); - }, []); - - const color = progress > 0.4 ? "green" : "orange"; - - return ( -
- ); -}; - -export default TimerProgress; diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index dc68114a1..14135e20d 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -1,15 +1,23 @@ -import { VerticallyCentered } from "@ente/shared/components/Container"; +import { + HorizontalFlex, + VerticallyCentered, +} from "@ente/shared/components/Container"; +import { EnteLogo } from "@ente/shared/components/EnteLogo"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; +import NavbarBase from "@ente/shared/components/Navbar/base"; +import OverflowMenu from "@ente/shared/components/OverflowMenu/menu"; +import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option"; import { AUTH_PAGES as PAGES } from "@ente/shared/constants/pages"; 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, TextField } from "@mui/material"; -import AuthNavbar from "components/Navbar"; import OTPDisplay from "components/OTPDisplay"; import { t } from "i18next"; import { useRouter } from "next/router"; import { AppContext } from "pages/_app"; -import { useContext, useEffect, useState } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { getAuthCodes } from "services"; const AuthenticatorCodesPage = () => { @@ -127,6 +135,32 @@ const AuthenticatorCodesPage = () => { export default AuthenticatorCodesPage; +const AuthNavbar: React.FC = () => { + const { isMobile, logout } = useContext(AppContext); + + return ( + + + + + + } + > + } + onClick={logout} + > + {t("LOGOUT")} + + + + + ); +}; + const AuthFooter: React.FC = () => { return (
=> { const masterKey = await getActualKey(); try { @@ -65,6 +65,20 @@ export const getAuthCodes = async (): Promise => { } }; +interface AuthEntity { + id: string; + encryptedData: string | null; + header: string | null; + isDeleted: boolean; + createdAt: number; + updatedAt: number; +} + +interface AuthKey { + encryptedKey: string; + header: string; +} + export const getAuthKey = async (): Promise => { try { const resp = await HTTPService.get( diff --git a/web/apps/auth/src/types/api.ts b/web/apps/auth/src/types/api.ts deleted file mode 100644 index 569df8185..000000000 --- a/web/apps/auth/src/types/api.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface AuthEntity { - id: string; - encryptedData: string | null; - header: string | null; - isDeleted: boolean; - createdAt: number; - updatedAt: number; -} - -export interface AuthKey { - encryptedKey: string; - header: string; -} From a104f36561198f4b7df92c19d8d31233523fb53b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 12:06:54 +0530 Subject: [PATCH 04/15] Inline --- web/apps/auth/src/components/OTPDisplay.tsx | 274 ------------------- web/apps/auth/src/pages/auth.tsx | 275 +++++++++++++++++++- 2 files changed, 273 insertions(+), 276 deletions(-) diff --git a/web/apps/auth/src/components/OTPDisplay.tsx b/web/apps/auth/src/components/OTPDisplay.tsx index 1168fe8cb..e69de29bb 100644 --- a/web/apps/auth/src/components/OTPDisplay.tsx +++ b/web/apps/auth/src/components/OTPDisplay.tsx @@ -1,274 +0,0 @@ -import { ButtonBase, Snackbar } from "@mui/material"; -import { t } from "i18next"; -import { HOTP, TOTP } from "otpauth"; -import { useEffect, useState } from "react"; -import { Code } from "types/code"; - -const TOTPDisplay = ({ issuer, account, code, nextCode, period }) => { - return ( -
- -
-
-

- {issuer} -

-

- {account} -

-

- {code} -

-
-
-
-

- {t("AUTH_NEXT")} -

-

- {nextCode} -

-
-
-
- ); -}; - -function BadCodeInfo({ codeInfo, codeErr }) { - const [showRawData, setShowRawData] = useState(false); - - return ( -
-
{codeInfo.title}
-
{codeErr}
-
- {showRawData ? ( -
setShowRawData(false)}> - {codeInfo.rawData ?? "no raw data"} -
- ) : ( -
setShowRawData(true)}>Show rawData
- )} -
-
- ); -} - -interface OTPDisplayProps { - codeInfo: Code; -} - -const OTPDisplay = (props: OTPDisplayProps) => { - const { codeInfo } = props; - const [code, setCode] = useState(""); - const [nextCode, setNextCode] = useState(""); - const [codeErr, setCodeErr] = useState(""); - const [hasCopied, setHasCopied] = useState(false); - - const generateCodes = () => { - try { - const currentTime = new Date().getTime(); - if (codeInfo.type.toLowerCase() === "totp") { - const totp = new TOTP({ - secret: codeInfo.secret, - algorithm: codeInfo.algorithm ?? Code.defaultAlgo, - period: codeInfo.period ?? Code.defaultPeriod, - digits: codeInfo.digits ?? Code.defaultDigits, - }); - setCode(totp.generate()); - setNextCode( - totp.generate({ - timestamp: currentTime + codeInfo.period * 1000, - }), - ); - } else if (codeInfo.type.toLowerCase() === "hotp") { - const hotp = new HOTP({ - secret: codeInfo.secret, - counter: 0, - algorithm: codeInfo.algorithm, - }); - setCode(hotp.generate()); - setNextCode(hotp.generate({ counter: 1 })); - } - } catch (err) { - setCodeErr(err.message); - } - }; - - const copyCode = () => { - navigator.clipboard.writeText(code); - setHasCopied(true); - setTimeout(() => { - setHasCopied(false); - }, 2000); - }; - - useEffect(() => { - // this is to set the initial code and nextCode on component mount - generateCodes(); - const codeType = codeInfo.type; - const codePeriodInMs = codeInfo.period * 1000; - const timeToNextCode = - codePeriodInMs - (new Date().getTime() % codePeriodInMs); - const intervalId = null; - // wait until we are at the start of the next code period, - // and then start the interval loop - setTimeout(() => { - // we need to call generateCodes() once before the interval loop - // to set the initial code and nextCode - generateCodes(); - codeType.toLowerCase() === "totp" || - codeType.toLowerCase() === "hotp" - ? setInterval(() => { - generateCodes(); - }, codePeriodInMs) - : null; - }, timeToNextCode); - - return () => { - if (intervalId) clearInterval(intervalId); - }; - }, [codeInfo]); - - return ( -
- {codeErr === "" ? ( - { - copyCode(); - }} - > - - - - ) : ( - - )} -
- ); -}; - -export default OTPDisplay; - -const TimerProgress = ({ period }) => { - const [progress, setProgress] = useState(0); - const [ticker, setTicker] = useState(null); - const microSecondsInPeriod = period * 1000000; - - const startTicker = () => { - const ticker = setInterval(() => { - updateTimeRemaining(); - }, 10); - setTicker(ticker); - }; - - const updateTimeRemaining = () => { - const timeRemaining = - microSecondsInPeriod - - ((new Date().getTime() * 1000) % microSecondsInPeriod); - setProgress(timeRemaining / microSecondsInPeriod); - }; - - useEffect(() => { - startTicker(); - return () => clearInterval(ticker); - }, []); - - const color = progress > 0.4 ? "green" : "orange"; - - return ( -
- ); -}; diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index 14135e20d..ad614c2f4 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -12,13 +12,14 @@ 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, TextField } from "@mui/material"; -import OTPDisplay from "components/OTPDisplay"; +import { Button, ButtonBase, Snackbar, TextField } from "@mui/material"; import { t } from "i18next"; import { useRouter } from "next/router"; +import { HOTP, TOTP } from "otpauth"; import { AppContext } from "pages/_app"; import React, { useContext, useEffect, useState } from "react"; import { getAuthCodes } from "services"; +import { Code } from "types/code"; const AuthenticatorCodesPage = () => { const appContext = useContext(AppContext); @@ -181,3 +182,273 @@ const AuthFooter: React.FC = () => {
); }; + +const TOTPDisplay = ({ issuer, account, code, nextCode, period }) => { + return ( +
+ +
+
+

+ {issuer} +

+

+ {account} +

+

+ {code} +

+
+
+
+

+ {t("AUTH_NEXT")} +

+

+ {nextCode} +

+
+
+
+ ); +}; + +function BadCodeInfo({ codeInfo, codeErr }) { + const [showRawData, setShowRawData] = useState(false); + + return ( +
+
{codeInfo.title}
+
{codeErr}
+
+ {showRawData ? ( +
setShowRawData(false)}> + {codeInfo.rawData ?? "no raw data"} +
+ ) : ( +
setShowRawData(true)}>Show rawData
+ )} +
+
+ ); +} + +interface OTPDisplayProps { + codeInfo: Code; +} + +const OTPDisplay: React.FC = ({ codeInfo }) => { + const [code, setCode] = useState(""); + const [nextCode, setNextCode] = useState(""); + const [codeErr, setCodeErr] = useState(""); + const [hasCopied, setHasCopied] = useState(false); + + const generateCodes = () => { + try { + const currentTime = new Date().getTime(); + if (codeInfo.type.toLowerCase() === "totp") { + const totp = new TOTP({ + secret: codeInfo.secret, + algorithm: codeInfo.algorithm ?? Code.defaultAlgo, + period: codeInfo.period ?? Code.defaultPeriod, + digits: codeInfo.digits ?? Code.defaultDigits, + }); + setCode(totp.generate()); + setNextCode( + totp.generate({ + timestamp: currentTime + codeInfo.period * 1000, + }), + ); + } else if (codeInfo.type.toLowerCase() === "hotp") { + const hotp = new HOTP({ + secret: codeInfo.secret, + counter: 0, + algorithm: codeInfo.algorithm, + }); + setCode(hotp.generate()); + setNextCode(hotp.generate({ counter: 1 })); + } + } catch (err) { + setCodeErr(err.message); + } + }; + + const copyCode = () => { + navigator.clipboard.writeText(code); + setHasCopied(true); + setTimeout(() => { + setHasCopied(false); + }, 2000); + }; + + useEffect(() => { + // this is to set the initial code and nextCode on component mount + generateCodes(); + const codeType = codeInfo.type; + const codePeriodInMs = codeInfo.period * 1000; + const timeToNextCode = + codePeriodInMs - (new Date().getTime() % codePeriodInMs); + const intervalId = null; + // wait until we are at the start of the next code period, + // and then start the interval loop + setTimeout(() => { + // we need to call generateCodes() once before the interval loop + // to set the initial code and nextCode + generateCodes(); + codeType.toLowerCase() === "totp" || + codeType.toLowerCase() === "hotp" + ? setInterval(() => { + generateCodes(); + }, codePeriodInMs) + : null; + }, timeToNextCode); + + return () => { + if (intervalId) clearInterval(intervalId); + }; + }, [codeInfo]); + + return ( +
+ {codeErr === "" ? ( + { + copyCode(); + }} + > + + + + ) : ( + + )} +
+ ); +}; + +interface TimerProgressProps { + period: number; +} + +const TimerProgress: React.FC = ({ period }) => { + const [progress, setProgress] = useState(0); + const [ticker, setTicker] = useState(null); + const microSecondsInPeriod = period * 1000000; + + const startTicker = () => { + const ticker = setInterval(() => { + updateTimeRemaining(); + }, 10); + setTicker(ticker); + }; + + const updateTimeRemaining = () => { + const timeRemaining = + microSecondsInPeriod - + ((new Date().getTime() * 1000) % microSecondsInPeriod); + setProgress(timeRemaining / microSecondsInPeriod); + }; + + useEffect(() => { + startTicker(); + return () => clearInterval(ticker); + }, []); + + const color = progress > 0.4 ? "green" : "orange"; + + return ( +
+ ); +}; From 2504046e26b179711c42196d8a234f41801f77e3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 12:10:23 +0530 Subject: [PATCH 05/15] Move --- web/apps/auth/src/components/OTPDisplay.tsx | 0 web/apps/auth/src/pages/auth.tsx | 4 ++-- web/apps/auth/src/{types => services}/code.ts | 0 web/apps/auth/src/services/{index.ts => remote.ts} | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 web/apps/auth/src/components/OTPDisplay.tsx rename web/apps/auth/src/{types => services}/code.ts (100%) rename web/apps/auth/src/services/{index.ts => remote.ts} (99%) diff --git a/web/apps/auth/src/components/OTPDisplay.tsx b/web/apps/auth/src/components/OTPDisplay.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index ad614c2f4..c8e5166cf 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -18,8 +18,8 @@ import { useRouter } from "next/router"; import { HOTP, TOTP } from "otpauth"; import { AppContext } from "pages/_app"; import React, { useContext, useEffect, useState } from "react"; -import { getAuthCodes } from "services"; -import { Code } from "types/code"; +import { Code } from "services/code"; +import { getAuthCodes } from "services/remote"; const AuthenticatorCodesPage = () => { const appContext = useContext(AppContext); diff --git a/web/apps/auth/src/types/code.ts b/web/apps/auth/src/services/code.ts similarity index 100% rename from web/apps/auth/src/types/code.ts rename to web/apps/auth/src/services/code.ts diff --git a/web/apps/auth/src/services/index.ts b/web/apps/auth/src/services/remote.ts similarity index 99% rename from web/apps/auth/src/services/index.ts rename to web/apps/auth/src/services/remote.ts index 8ef166bcd..633d5f9d0 100644 --- a/web/apps/auth/src/services/index.ts +++ b/web/apps/auth/src/services/remote.ts @@ -6,7 +6,7 @@ import { getEndpoint } from "@ente/shared/network/api"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { getActualKey } from "@ente/shared/user"; import { HttpStatusCode } from "axios"; -import { Code } from "types/code"; +import { Code } from "services/code"; const ENDPOINT = getEndpoint(); From d2743f4121e89c869ab74f432c1877e64f858151 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 12:16:02 +0530 Subject: [PATCH 06/15] Unclass --- web/apps/auth/src/services/code.ts | 230 ++++++++++++++--------------- 1 file changed, 112 insertions(+), 118 deletions(-) diff --git a/web/apps/auth/src/services/code.ts b/web/apps/auth/src/services/code.ts index d61a2dcd6..af3408f20 100644 --- a/web/apps/auth/src/services/code.ts +++ b/web/apps/auth/src/services/code.ts @@ -47,136 +47,130 @@ export class Code { this.rawData = rawData; this.id = id; } +} - static fromRawData(id: string, rawData: string): Code { - let santizedRawData = rawData - .replace(/\+/g, "%2B") - .replace(/:/g, "%3A") - .replaceAll("\r", ""); - if (santizedRawData.startsWith('"')) { - santizedRawData = santizedRawData.substring(1); - } - if (santizedRawData.endsWith('"')) { - santizedRawData = santizedRawData.substring( - 0, - santizedRawData.length - 1, - ); - } - - const uriParams = {}; - const searchParamsString = - decodeURIComponent(santizedRawData).split("?")[1]; - searchParamsString.split("&").forEach((pair) => { - const [key, value] = pair.split("="); - uriParams[key] = value; - }); - - const uri = URI.parse(santizedRawData); - let uriPath = decodeURIComponent(uri.path); - if ( - uriPath.startsWith("/otpauth://") || - uriPath.startsWith("otpauth://") - ) { - uriPath = uriPath.split("otpauth://")[1]; - } else if (uriPath.startsWith("otpauth%3A//")) { - uriPath = uriPath.split("otpauth%3A//")[1]; - } - - return new Code( - Code._getAccount(uriPath), - Code._getIssuer(uriPath, uriParams), - Code._getDigits(uriParams), - Code._getPeriod(uriParams), - Code.getSanitizedSecret(uriParams), - Code._getAlgorithm(uriParams), - Code._getType(uriPath), - rawData, - id, +const codeFromRawData = (id: string, rawData: string): Code => { + let santizedRawData = rawData + .replace(/\+/g, "%2B") + .replace(/:/g, "%3A") + .replaceAll("\r", ""); + if (santizedRawData.startsWith('"')) { + santizedRawData = santizedRawData.substring(1); + } + if (santizedRawData.endsWith('"')) { + santizedRawData = santizedRawData.substring( + 0, + santizedRawData.length - 1, ); } - private static _getAccount(uriPath: string): string { - try { - const path = decodeURIComponent(uriPath); - if (path.includes(":")) { - return path.split(":")[1]; - } else if (path.includes("/")) { - return path.split("/")[1]; + const uriParams = {}; + const searchParamsString = + decodeURIComponent(santizedRawData).split("?")[1]; + searchParamsString.split("&").forEach((pair) => { + const [key, value] = pair.split("="); + uriParams[key] = value; + }); + + const uri = URI.parse(santizedRawData); + let uriPath = decodeURIComponent(uri.path); + if (uriPath.startsWith("/otpauth://") || uriPath.startsWith("otpauth://")) { + uriPath = uriPath.split("otpauth://")[1]; + } else if (uriPath.startsWith("otpauth%3A//")) { + uriPath = uriPath.split("otpauth%3A//")[1]; + } + + return new Code( + _getAccount(uriPath), + _getIssuer(uriPath, uriParams), + _getDigits(uriParams), + _getPeriod(uriParams), + getSanitizedSecret(uriParams), + _getAlgorithm(uriParams), + _getType(uriPath), + rawData, + id, + ); +}; + +const _getAccount = (uriPath: string): string => { + try { + const path = decodeURIComponent(uriPath); + if (path.includes(":")) { + return path.split(":")[1]; + } else if (path.includes("/")) { + return path.split("/")[1]; + } + } catch (e) { + return ""; + } +}; + +const _getIssuer = (uriPath: string, uriParams: { get?: any }): string => { + try { + if (uriParams["issuer"] !== undefined) { + let issuer = uriParams["issuer"]; + // This is to handle bug in the ente auth app + if (issuer.endsWith("period")) { + issuer = issuer.substring(0, issuer.length - 6); } - } catch (e) { - return ""; + return issuer; } - } - - private static _getIssuer( - uriPath: string, - uriParams: { get?: any }, - ): string { - try { - if (uriParams["issuer"] !== undefined) { - let issuer = uriParams["issuer"]; - // This is to handle bug in the ente auth app - if (issuer.endsWith("period")) { - issuer = issuer.substring(0, issuer.length - 6); - } - return issuer; - } - let path = decodeURIComponent(uriPath); - if (path.startsWith("totp/") || path.startsWith("hotp/")) { - path = path.substring(5); - } - if (path.includes(":")) { - return path.split(":")[0]; - } else if (path.includes("-")) { - return path.split("-")[0]; - } - return path; - } catch (e) { - return ""; + let path = decodeURIComponent(uriPath); + if (path.startsWith("totp/") || path.startsWith("hotp/")) { + path = path.substring(5); } - } - - private static _getDigits(uriParams): number { - try { - return parseInt(uriParams["digits"], 10) || Code.defaultDigits; - } catch (e) { - return Code.defaultDigits; + if (path.includes(":")) { + return path.split(":")[0]; + } else if (path.includes("-")) { + return path.split("-")[0]; } + return path; + } catch (e) { + return ""; } +}; - private static _getPeriod(uriParams): number { - try { - return parseInt(uriParams["period"], 10) || Code.defaultPeriod; - } catch (e) { - return Code.defaultPeriod; +const _getDigits = (uriParams): number => { + try { + return parseInt(uriParams["digits"], 10) || Code.defaultDigits; + } catch (e) { + return Code.defaultDigits; + } +}; + +const _getPeriod = (uriParams): number => { + try { + return parseInt(uriParams["period"], 10) || Code.defaultPeriod; + } catch (e) { + return Code.defaultPeriod; + } +}; + +const _getAlgorithm = (uriParams): AlgorithmType => { + try { + const algorithm = uriParams["algorithm"].toLowerCase(); + if (algorithm === "sha256") { + return algorithm; + } else if (algorithm === "sha512") { + return algorithm; } + } catch (e) { + // nothing } + return "sha1"; +}; - private static _getAlgorithm(uriParams): AlgorithmType { - try { - const algorithm = uriParams["algorithm"].toLowerCase(); - if (algorithm === "sha256") { - return algorithm; - } else if (algorithm === "sha512") { - return algorithm; - } - } catch (e) { - // nothing - } - return "sha1"; +const _getType = (uriPath: string): Type => { + const oauthType = uriPath.split("/")[0].substring(0); + if (oauthType.toLowerCase() === "totp") { + return "totp"; + } else if (oauthType.toLowerCase() === "hotp") { + return "hotp"; } + throw new Error(`Unsupported format with host ${oauthType}`); +}; - private static _getType(uriPath: string): Type { - const oauthType = uriPath.split("/")[0].substring(0); - if (oauthType.toLowerCase() === "totp") { - return "totp"; - } else if (oauthType.toLowerCase() === "hotp") { - return "hotp"; - } - throw new Error(`Unsupported format with host ${oauthType}`); - } - - static getSanitizedSecret(uriParams): string { - return uriParams["secret"].replace(/ /g, "").toUpperCase(); - } -} +const getSanitizedSecret = (uriParams): string => { + return uriParams["secret"].replace(/ /g, "").toUpperCase(); +}; From 51568e6c56162606b55ead2d39db1851ba707af1 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 12:20:04 +0530 Subject: [PATCH 07/15] non optional --- web/apps/auth/src/pages/auth.tsx | 2 +- web/apps/auth/src/services/code.ts | 13 ++++++------- web/apps/auth/src/services/remote.ts | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index c8e5166cf..a76b687fc 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -326,7 +326,7 @@ const OTPDisplay: React.FC = ({ codeInfo }) => { secret: codeInfo.secret, algorithm: codeInfo.algorithm ?? Code.defaultAlgo, period: codeInfo.period ?? Code.defaultPeriod, - digits: codeInfo.digits ?? Code.defaultDigits, + digits: codeInfo.digits, }); setCode(totp.generate()); setNextCode( diff --git a/web/apps/auth/src/services/code.ts b/web/apps/auth/src/services/code.ts index af3408f20..e7afe691e 100644 --- a/web/apps/auth/src/services/code.ts +++ b/web/apps/auth/src/services/code.ts @@ -11,7 +11,6 @@ type AlgorithmType = | "SHA512"; export class Code { - static readonly defaultDigits = 6; static readonly defaultAlgo = "sha1"; static readonly defaultPeriod = 30; @@ -19,7 +18,7 @@ export class Code { id?: String; account: string; issuer: string; - digits?: number; + digits: number; period: number; secret: string; algorithm: AlgorithmType; @@ -49,7 +48,7 @@ export class Code { } } -const codeFromRawData = (id: string, rawData: string): Code => { +export const codeFromRawData = (id: string, rawData: string): Code => { let santizedRawData = rawData .replace(/\+/g, "%2B") .replace(/:/g, "%3A") @@ -83,7 +82,7 @@ const codeFromRawData = (id: string, rawData: string): Code => { return new Code( _getAccount(uriPath), _getIssuer(uriPath, uriParams), - _getDigits(uriParams), + _getDigits(uriParams) ?? 6, _getPeriod(uriParams), getSanitizedSecret(uriParams), _getAlgorithm(uriParams), @@ -131,11 +130,11 @@ const _getIssuer = (uriPath: string, uriParams: { get?: any }): string => { } }; -const _getDigits = (uriParams): number => { +const _getDigits = (uriParams): number | undefined => { try { - return parseInt(uriParams["digits"], 10) || Code.defaultDigits; + return parseInt(uriParams["digits"], 10); } catch (e) { - return Code.defaultDigits; + return undefined; } }; diff --git a/web/apps/auth/src/services/remote.ts b/web/apps/auth/src/services/remote.ts index 633d5f9d0..325aff2d7 100644 --- a/web/apps/auth/src/services/remote.ts +++ b/web/apps/auth/src/services/remote.ts @@ -6,7 +6,7 @@ import { getEndpoint } from "@ente/shared/network/api"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { getActualKey } from "@ente/shared/user"; import { HttpStatusCode } from "axios"; -import { Code } from "services/code"; +import { Code, codeFromRawData } from "services/code"; const ENDPOINT = getEndpoint(); @@ -33,7 +33,7 @@ export const getAuthCodes = async (): Promise => { entity.header, authenticatorKey, ); - return Code.fromRawData(entity.id, decryptedCode); + return codeFromRawData(entity.id, decryptedCode); } catch (e) { log.error(`failed to parse codeId = ${entity.id}`); return null; From 51dc8d1de6bc69bcf3dd1f263a9695bb278d7089 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 12:40:35 +0530 Subject: [PATCH 08/15] Rearrange --- web/apps/auth/src/pages/auth.tsx | 347 +++++++++++++++---------------- 1 file changed, 171 insertions(+), 176 deletions(-) diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index a76b687fc..c5c328a42 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -122,7 +122,7 @@ const AuthenticatorCodesPage = () => {
) : ( filteredCodes.map((code) => ( - + )) )}
@@ -162,159 +162,13 @@ const AuthNavbar: React.FC = () => { ); }; -const AuthFooter: React.FC = () => { - return ( -
-

{t("AUTH_DOWNLOAD_MOBILE_APP")}

- - - -
- ); -}; - -const TOTPDisplay = ({ issuer, account, code, nextCode, period }) => { - return ( -
- -
-
-

- {issuer} -

-

- {account} -

-

- {code} -

-
-
-
-

- {t("AUTH_NEXT")} -

-

- {nextCode} -

-
-
-
- ); -}; - -function BadCodeInfo({ codeInfo, codeErr }) { - const [showRawData, setShowRawData] = useState(false); - - return ( -
-
{codeInfo.title}
-
{codeErr}
-
- {showRawData ? ( -
setShowRawData(false)}> - {codeInfo.rawData ?? "no raw data"} -
- ) : ( -
setShowRawData(true)}>Show rawData
- )} -
-
- ); -} - -interface OTPDisplayProps { +interface CodeDisplay { codeInfo: Code; } -const OTPDisplay: React.FC = ({ codeInfo }) => { - const [code, setCode] = useState(""); - const [nextCode, setNextCode] = useState(""); +const CodeDisplay: React.FC = ({ codeInfo }) => { + const [otp, setOTP] = useState(""); + const [nextOTP, setNextOTP] = useState(""); const [codeErr, setCodeErr] = useState(""); const [hasCopied, setHasCopied] = useState(false); @@ -328,8 +182,8 @@ const OTPDisplay: React.FC = ({ codeInfo }) => { period: codeInfo.period ?? Code.defaultPeriod, digits: codeInfo.digits, }); - setCode(totp.generate()); - setNextCode( + setOTP(totp.generate()); + setNextOTP( totp.generate({ timestamp: currentTime + codeInfo.period * 1000, }), @@ -340,8 +194,8 @@ const OTPDisplay: React.FC = ({ codeInfo }) => { counter: 0, algorithm: codeInfo.algorithm, }); - setCode(hotp.generate()); - setNextCode(hotp.generate({ counter: 1 })); + setOTP(hotp.generate()); + setNextOTP(hotp.generate({ counter: 1 })); } } catch (err) { setCodeErr(err.message); @@ -349,7 +203,7 @@ const OTPDisplay: React.FC = ({ codeInfo }) => { }; const copyCode = () => { - navigator.clipboard.writeText(code); + navigator.clipboard.writeText(otp); setHasCopied(true); setTimeout(() => { setHasCopied(false); @@ -392,13 +246,7 @@ const OTPDisplay: React.FC = ({ codeInfo }) => { copyCode(); }} > - + = ({ codeInfo }) => { ); }; +interface OTPDisplayProps { + code: Code; + otp: string; + nextOTP: string; +} + +const OTPDisplay: React.FC = ({ code, otp, nextOTP }) => { + return ( +
+ +
+
+

+ {code.issuer} +

+

+ {code.account} +

+

+ {otp} +

+
+
+
+

+ {t("AUTH_NEXT")} +

+

+ {nextOTP} +

+
+
+
+ ); +}; + interface TimerProgressProps { period: number; } const TimerProgress: React.FC = ({ period }) => { const [progress, setProgress] = useState(0); - const [ticker, setTicker] = useState(null); const microSecondsInPeriod = period * 1000000; - const startTicker = () => { + useEffect(() => { + const updateTimeRemaining = () => { + const timeRemaining = + microSecondsInPeriod - + ((new Date().getTime() * 1000) % microSecondsInPeriod); + setProgress(timeRemaining / microSecondsInPeriod); + }; + const ticker = setInterval(() => { updateTimeRemaining(); }, 10); - setTicker(ticker); - }; - const updateTimeRemaining = () => { - const timeRemaining = - microSecondsInPeriod - - ((new Date().getTime() * 1000) % microSecondsInPeriod); - setProgress(timeRemaining / microSecondsInPeriod); - }; - - useEffect(() => { - startTicker(); return () => clearInterval(ticker); }, []); @@ -452,3 +406,44 @@ const TimerProgress: React.FC = ({ period }) => { /> ); }; + +function BadCodeInfo({ codeInfo, codeErr }) { + const [showRawData, setShowRawData] = useState(false); + + return ( +
+
{codeInfo.title}
+
{codeErr}
+
+ {showRawData ? ( +
setShowRawData(false)}> + {codeInfo.rawData ?? "no raw data"} +
+ ) : ( +
setShowRawData(true)}>Show rawData
+ )} +
+
+ ); +} + +const AuthFooter: React.FC = () => { + return ( +
+

{t("AUTH_DOWNLOAD_MOBILE_APP")}

+ + + +
+ ); +}; From 14655e5633d50b555fa6982b5e461d4585f4c8b3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 12:47:29 +0530 Subject: [PATCH 09/15] Fix --- web/apps/auth/src/services/code.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/auth/src/services/code.ts b/web/apps/auth/src/services/code.ts index e7afe691e..a77660d45 100644 --- a/web/apps/auth/src/services/code.ts +++ b/web/apps/auth/src/services/code.ts @@ -82,7 +82,7 @@ export const codeFromRawData = (id: string, rawData: string): Code => { return new Code( _getAccount(uriPath), _getIssuer(uriPath, uriParams), - _getDigits(uriParams) ?? 6, + _getDigits(uriParams) || 6, _getPeriod(uriParams), getSanitizedSecret(uriParams), _getAlgorithm(uriParams), From 26436f116fc51c6a395d579cad537787a6352498 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 12:57:58 +0530 Subject: [PATCH 10/15] Nonopt --- web/apps/auth/src/pages/auth.tsx | 5 +++-- web/apps/auth/src/services/code.ts | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index c5c328a42..02d32edc7 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -176,10 +176,11 @@ const CodeDisplay: React.FC = ({ codeInfo }) => { try { const currentTime = new Date().getTime(); if (codeInfo.type.toLowerCase() === "totp") { + console.log({ codeInfo }); const totp = new TOTP({ secret: codeInfo.secret, algorithm: codeInfo.algorithm ?? Code.defaultAlgo, - period: codeInfo.period ?? Code.defaultPeriod, + period: codeInfo.period, digits: codeInfo.digits, }); setOTP(totp.generate()); @@ -274,7 +275,7 @@ const OTPDisplay: React.FC = ({ code, otp, nextOTP }) => { overflow: "hidden", }} > - +
{ _getAccount(uriPath), _getIssuer(uriPath, uriParams), _getDigits(uriParams) || 6, - _getPeriod(uriParams), + _getPeriod(uriParams) || 30, getSanitizedSecret(uriParams), _getAlgorithm(uriParams), _getType(uriPath), @@ -138,11 +137,11 @@ const _getDigits = (uriParams): number | undefined => { } }; -const _getPeriod = (uriParams): number => { +const _getPeriod = (uriParams): number | undefined => { try { - return parseInt(uriParams["period"], 10) || Code.defaultPeriod; + return parseInt(uriParams["period"], 10); } catch (e) { - return Code.defaultPeriod; + return undefined; } }; From 99f47dc1ae3ce454b47c7bb58d4c2c3b226774de Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 13:03:31 +0530 Subject: [PATCH 11/15] Move into the function --- web/apps/auth/src/pages/auth.tsx | 2 +- web/apps/auth/src/services/code.ts | 24 +++++++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index 02d32edc7..86015247e 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -179,7 +179,7 @@ const CodeDisplay: React.FC = ({ codeInfo }) => { console.log({ codeInfo }); const totp = new TOTP({ secret: codeInfo.secret, - algorithm: codeInfo.algorithm ?? Code.defaultAlgo, + algorithm: codeInfo.algorithm, period: codeInfo.period, digits: codeInfo.digits, }); diff --git a/web/apps/auth/src/services/code.ts b/web/apps/auth/src/services/code.ts index 226d8b7a7..b80c4b398 100644 --- a/web/apps/auth/src/services/code.ts +++ b/web/apps/auth/src/services/code.ts @@ -11,8 +11,6 @@ type AlgorithmType = | "SHA512"; export class Code { - static readonly defaultAlgo = "sha1"; - // id for the corresponding auth entity id?: String; account: string; @@ -81,8 +79,8 @@ export const codeFromRawData = (id: string, rawData: string): Code => { return new Code( _getAccount(uriPath), _getIssuer(uriPath, uriParams), - _getDigits(uriParams) || 6, - _getPeriod(uriParams) || 30, + _getDigits(uriParams), + _getPeriod(uriParams), getSanitizedSecret(uriParams), _getAlgorithm(uriParams), _getType(uriPath), @@ -129,23 +127,23 @@ const _getIssuer = (uriPath: string, uriParams: { get?: any }): string => { } }; -const _getDigits = (uriParams): number | undefined => { +const _getDigits = (uriParams): number => { try { - return parseInt(uriParams["digits"], 10); - } catch (e) { - return undefined; + return parseInt(uriParams["digits"], 10) || 6; + } catch { + return 6; } }; -const _getPeriod = (uriParams): number | undefined => { +const _getPeriod = (uriParams): number => { try { - return parseInt(uriParams["period"], 10); - } catch (e) { - return undefined; + return parseInt(uriParams["period"], 10) || 30; + } catch { + return 30; } }; -const _getAlgorithm = (uriParams): AlgorithmType => { +const _getAlgorithm = (uriParams): AlgorithmType | undefined => { try { const algorithm = uriParams["algorithm"].toLowerCase(); if (algorithm === "sha256") { From 171af35d85854c26aece3729dd9cc6492ec77942 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 13:06:27 +0530 Subject: [PATCH 12/15] Reword --- web/apps/auth/src/services/code.ts | 43 +++++++++++------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/web/apps/auth/src/services/code.ts b/web/apps/auth/src/services/code.ts index b80c4b398..8e30a7f16 100644 --- a/web/apps/auth/src/services/code.ts +++ b/web/apps/auth/src/services/code.ts @@ -79,10 +79,10 @@ export const codeFromRawData = (id: string, rawData: string): Code => { return new Code( _getAccount(uriPath), _getIssuer(uriPath, uriParams), - _getDigits(uriParams), - _getPeriod(uriParams), + parseDigits(uriParams), + parsePeriod(uriParams), getSanitizedSecret(uriParams), - _getAlgorithm(uriParams), + parseAlgorithm(uriParams), _getType(uriPath), rawData, id, @@ -127,34 +127,21 @@ const _getIssuer = (uriPath: string, uriParams: { get?: any }): string => { } }; -const _getDigits = (uriParams): number => { - try { - return parseInt(uriParams["digits"], 10) || 6; - } catch { - return 6; - } -}; +const parseDigits = (uriParams): number => + parseInt(uriParams["digits"] ?? "", 10) || 6; -const _getPeriod = (uriParams): number => { - try { - return parseInt(uriParams["period"], 10) || 30; - } catch { - return 30; - } -}; +const parsePeriod = (uriParams): number => + parseInt(uriParams["period"] ?? "", 10) || 30; -const _getAlgorithm = (uriParams): AlgorithmType | undefined => { - try { - const algorithm = uriParams["algorithm"].toLowerCase(); - if (algorithm === "sha256") { - return algorithm; - } else if (algorithm === "sha512") { - return algorithm; - } - } catch (e) { - // nothing +const parseAlgorithm = (uriParams): AlgorithmType => { + switch (uriParams["algorithm"]?.toLowerCase()) { + case "sha256": + return "sha256"; + case "sha512": + return "sha512"; + default: + return "sha1"; } - return "sha1"; }; const _getType = (uriPath: string): Type => { From 41c87efc5ad51ddb432d8554a6fb6a63fd91f0d7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 13:07:33 +0530 Subject: [PATCH 13/15] Use the union --- web/apps/auth/src/services/code.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/web/apps/auth/src/services/code.ts b/web/apps/auth/src/services/code.ts index 8e30a7f16..4fe134835 100644 --- a/web/apps/auth/src/services/code.ts +++ b/web/apps/auth/src/services/code.ts @@ -2,13 +2,7 @@ import { URI } from "vscode-uri"; type Type = "totp" | "TOTP" | "hotp" | "HOTP"; -type AlgorithmType = - | "sha1" - | "SHA1" - | "sha256" - | "SHA256" - | "sha512" - | "SHA512"; +type Algorithm = "sha1" | "sha256" | "sha512"; export class Code { // id for the corresponding auth entity @@ -18,7 +12,7 @@ export class Code { digits: number; period: number; secret: string; - algorithm: AlgorithmType; + algorithm: Algorithm; type: Type; rawData?: string; @@ -28,7 +22,7 @@ export class Code { digits: number | undefined, period: number, secret: string, - algorithm: AlgorithmType, + algorithm: Algorithm, type: Type, rawData?: string, id?: string, @@ -133,7 +127,7 @@ const parseDigits = (uriParams): number => const parsePeriod = (uriParams): number => parseInt(uriParams["period"] ?? "", 10) || 30; -const parseAlgorithm = (uriParams): AlgorithmType => { +const parseAlgorithm = (uriParams): Algorithm => { switch (uriParams["algorithm"]?.toLowerCase()) { case "sha256": return "sha256"; From 206be5c16f000f54f6f7cf69746920b96583dedb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 13:19:05 +0530 Subject: [PATCH 14/15] Document --- web/apps/auth/src/pages/auth.tsx | 1 - web/apps/auth/src/services/code.ts | 10 +++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index 86015247e..e1c69f1da 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -176,7 +176,6 @@ const CodeDisplay: React.FC = ({ codeInfo }) => { try { const currentTime = new Date().getTime(); if (codeInfo.type.toLowerCase() === "totp") { - console.log({ codeInfo }); const totp = new TOTP({ secret: codeInfo.secret, algorithm: codeInfo.algorithm, diff --git a/web/apps/auth/src/services/code.ts b/web/apps/auth/src/services/code.ts index 4fe134835..ddac5a920 100644 --- a/web/apps/auth/src/services/code.ts +++ b/web/apps/auth/src/services/code.ts @@ -19,7 +19,7 @@ export class Code { constructor( account: string, issuer: string, - digits: number | undefined, + digits: number, period: number, secret: string, algorithm: Algorithm, @@ -39,6 +39,14 @@ export class Code { } } +/** + * Convert a "raw" OTP secret URL into its parse representation, a {@link Code}. + * + * An example {@link rawData}: + * + * otpauth://totp/account:user@example.org?algorithm=SHA1&digits=6&issuer=issuer&period=30&secret=ALPHANUM + * + */ export const codeFromRawData = (id: string, rawData: string): Code => { let santizedRawData = rawData .replace(/\+/g, "%2B") From 5e2261f793aa4f69841e0d664af4d49e3f08a23e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 23 May 2024 13:36:16 +0530 Subject: [PATCH 15/15] Unclass --- web/apps/auth/src/pages/auth.tsx | 6 +- web/apps/auth/src/services/code.ts | 89 +++++++++++++--------------- web/apps/auth/src/services/remote.ts | 4 +- 3 files changed, 46 insertions(+), 53 deletions(-) diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index e1c69f1da..e628050ea 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -175,7 +175,7 @@ const CodeDisplay: React.FC = ({ codeInfo }) => { const generateCodes = () => { try { const currentTime = new Date().getTime(); - if (codeInfo.type.toLowerCase() === "totp") { + if (codeInfo.type === "totp") { const totp = new TOTP({ secret: codeInfo.secret, algorithm: codeInfo.algorithm, @@ -188,7 +188,7 @@ const CodeDisplay: React.FC = ({ codeInfo }) => { timestamp: currentTime + codeInfo.period * 1000, }), ); - } else if (codeInfo.type.toLowerCase() === "hotp") { + } else if (codeInfo.type === "hotp") { const hotp = new HOTP({ secret: codeInfo.secret, counter: 0, @@ -417,7 +417,7 @@ function BadCodeInfo({ codeInfo, codeErr }) {
{showRawData ? (
setShowRawData(false)}> - {codeInfo.rawData ?? "no raw data"} + {codeInfo.uriString ?? "(no raw data)"}
) : (
setShowRawData(true)}>Show rawData
diff --git a/web/apps/auth/src/services/code.ts b/web/apps/auth/src/services/code.ts index ddac5a920..ca9ba1642 100644 --- a/web/apps/auth/src/services/code.ts +++ b/web/apps/auth/src/services/code.ts @@ -1,54 +1,47 @@ import { URI } from "vscode-uri"; -type Type = "totp" | "TOTP" | "hotp" | "HOTP"; - -type Algorithm = "sha1" | "sha256" | "sha512"; - -export class Code { - // id for the corresponding auth entity +/** + * A parsed representation of an xOTP code URI. + * + * This is all the data we need to drive a OTP generator. + */ +export interface Code { + /** The uniquue id for the corresponding auth entity. */ id?: String; + /** The type of the code. */ + type: "totp" | "hotp"; + /** The user's account or email for which this code is used. */ account: string; + /** The name of the entity that issued this code. */ issuer: string; + /** Number of digits in the code. */ digits: number; + /** + * The time period (in seconds) for which a single OTP generated from this + * code remains valid. + */ period: number; + /** The secret that is used to drive the OTP generator. */ secret: string; - algorithm: Algorithm; - type: Type; - rawData?: string; - - constructor( - account: string, - issuer: string, - digits: number, - period: number, - secret: string, - algorithm: Algorithm, - type: Type, - rawData?: string, - id?: string, - ) { - this.account = account; - this.issuer = issuer; - this.digits = digits; - this.period = period; - this.secret = secret; - this.algorithm = algorithm; - this.type = type; - this.rawData = rawData; - this.id = id; - } + /** The (hashing) algorithim used by the OTP generator. */ + algorithm: "sha1" | "sha256" | "sha512"; + /** The original string from which this code was generated. */ + uriString?: string; } /** - * Convert a "raw" OTP secret URL into its parse representation, a {@link Code}. + * Convert a OTP code URI into its parse representation, a {@link Code}. * - * An example {@link rawData}: + * @param id A unique ID of this code within the auth app. * - * otpauth://totp/account:user@example.org?algorithm=SHA1&digits=6&issuer=issuer&period=30&secret=ALPHANUM + * @param uriString A string specifying how to generate a TOTP/HOTP/Steam OTP + * code. These strings are of the form: * + * - (TOTP) + * otpauth://totp/account:user@example.org?algorithm=SHA1&digits=6&issuer=issuer&period=30&secret=ALPHANUM */ -export const codeFromRawData = (id: string, rawData: string): Code => { - let santizedRawData = rawData +export const codeFromURIString = (id: string, uriString: string): Code => { + let santizedRawData = uriString .replace(/\+/g, "%2B") .replace(/:/g, "%3A") .replaceAll("\r", ""); @@ -78,17 +71,17 @@ export const codeFromRawData = (id: string, rawData: string): Code => { uriPath = uriPath.split("otpauth%3A//")[1]; } - return new Code( - _getAccount(uriPath), - _getIssuer(uriPath, uriParams), - parseDigits(uriParams), - parsePeriod(uriParams), - getSanitizedSecret(uriParams), - parseAlgorithm(uriParams), - _getType(uriPath), - rawData, + return { id, - ); + type: _getType(uriPath), + account: _getAccount(uriPath), + issuer: _getIssuer(uriPath, uriParams), + digits: parseDigits(uriParams), + period: parsePeriod(uriParams), + secret: getSanitizedSecret(uriParams), + algorithm: parseAlgorithm(uriParams), + uriString, + }; }; const _getAccount = (uriPath: string): string => { @@ -135,7 +128,7 @@ const parseDigits = (uriParams): number => const parsePeriod = (uriParams): number => parseInt(uriParams["period"] ?? "", 10) || 30; -const parseAlgorithm = (uriParams): Algorithm => { +const parseAlgorithm = (uriParams): Code["algorithm"] => { switch (uriParams["algorithm"]?.toLowerCase()) { case "sha256": return "sha256"; @@ -146,7 +139,7 @@ const parseAlgorithm = (uriParams): Algorithm => { } }; -const _getType = (uriPath: string): Type => { +const _getType = (uriPath: string): Code["type"] => { const oauthType = uriPath.split("/")[0].substring(0); if (oauthType.toLowerCase() === "totp") { return "totp"; diff --git a/web/apps/auth/src/services/remote.ts b/web/apps/auth/src/services/remote.ts index 325aff2d7..07b15d7d7 100644 --- a/web/apps/auth/src/services/remote.ts +++ b/web/apps/auth/src/services/remote.ts @@ -6,7 +6,7 @@ import { getEndpoint } from "@ente/shared/network/api"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { getActualKey } from "@ente/shared/user"; import { HttpStatusCode } from "axios"; -import { Code, codeFromRawData } from "services/code"; +import { codeFromURIString, type Code } from "services/code"; const ENDPOINT = getEndpoint(); @@ -33,7 +33,7 @@ export const getAuthCodes = async (): Promise => { entity.header, authenticatorKey, ); - return codeFromRawData(entity.id, decryptedCode); + return codeFromURIString(entity.id, decryptedCode); } catch (e) { log.error(`failed to parse codeId = ${entity.id}`); return null;