credentials.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import { useEffect, useState } from "react";
  2. import { t } from "i18next";
  3. import {
  4. decryptAndStoreToken,
  5. generateAndSaveIntermediateKeyAttributes,
  6. generateLoginSubKey,
  7. saveKeyInSessionStore,
  8. } from "@ente/shared/crypto/helpers";
  9. import {
  10. LS_KEYS,
  11. clearData,
  12. getData,
  13. setData,
  14. } from "@ente/shared/storage/localStorage";
  15. import {
  16. SESSION_KEYS,
  17. getKey,
  18. removeKey,
  19. setKey,
  20. } from "@ente/shared/storage/sessionStorage";
  21. import { PAGES } from "../constants/pages";
  22. import { generateSRPSetupAttributes } from "../services/srp";
  23. import { logoutUser } from "../services/user";
  24. import { VerticallyCentered } from "@ente/shared/components/Container";
  25. import EnteSpinner from "@ente/shared/components/EnteSpinner";
  26. import FormPaper from "@ente/shared/components/Form/FormPaper";
  27. import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
  28. import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
  29. import LinkButton from "@ente/shared/components/LinkButton";
  30. import VerifyMasterPasswordForm, {
  31. VerifyMasterPasswordFormProps,
  32. } from "@ente/shared/components/VerifyMasterPasswordForm";
  33. import { getAccountsURL } from "@ente/shared/network/api";
  34. import {
  35. isFirstLogin,
  36. setIsFirstLogin,
  37. } from "@ente/shared/storage/localStorage/helpers";
  38. import { KeyAttributes, User } from "@ente/shared/user/types";
  39. import isElectron from "is-electron";
  40. import { getSRPAttributes } from "../api/srp";
  41. import { configureSRP, loginViaSRP } from "../services/srp";
  42. import { SRPAttributes } from "../types/srp";
  43. // import { APPS, getAppName } from '@ente/shared/apps';
  44. import { APP_HOMES } from "@ente/shared/apps/constants";
  45. import { PageProps } from "@ente/shared/apps/types";
  46. import ComlinkCryptoWorker from "@ente/shared/crypto";
  47. import { B64EncryptionResult } from "@ente/shared/crypto/types";
  48. import ElectronAPIs from "@ente/shared/electron";
  49. import { CustomError } from "@ente/shared/error";
  50. import { addLocalLog } from "@ente/shared/logging";
  51. import { logError } from "@ente/shared/sentry";
  52. import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
  53. export default function Credentials({
  54. appContext,
  55. router,
  56. appName,
  57. }: PageProps) {
  58. const [srpAttributes, setSrpAttributes] = useState<SRPAttributes>();
  59. const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
  60. const [user, setUser] = useState<User>();
  61. useEffect(() => {
  62. const main = async () => {
  63. const user: User = getData(LS_KEYS.USER);
  64. if (!user?.email) {
  65. router.push(PAGES.ROOT);
  66. return;
  67. }
  68. setUser(user);
  69. let key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
  70. if (!key && isElectron()) {
  71. try {
  72. key = await ElectronAPIs.getEncryptionKey();
  73. } catch (e) {
  74. logError(e, "getEncryptionKey failed");
  75. }
  76. if (key) {
  77. await saveKeyInSessionStore(
  78. SESSION_KEYS.ENCRYPTION_KEY,
  79. key,
  80. true,
  81. );
  82. }
  83. }
  84. if (key) {
  85. router.push(APP_HOMES.get(appName));
  86. return;
  87. }
  88. const kekEncryptedAttributes: B64EncryptionResult = getKey(
  89. SESSION_KEYS.KEY_ENCRYPTION_KEY,
  90. );
  91. const keyAttributes: KeyAttributes = getData(
  92. LS_KEYS.KEY_ATTRIBUTES,
  93. );
  94. if (kekEncryptedAttributes && keyAttributes) {
  95. removeKey(SESSION_KEYS.KEY_ENCRYPTION_KEY);
  96. const cryptoWorker = await ComlinkCryptoWorker.getInstance();
  97. const kek = await cryptoWorker.decryptB64(
  98. kekEncryptedAttributes.encryptedData,
  99. kekEncryptedAttributes.nonce,
  100. kekEncryptedAttributes.key,
  101. );
  102. const key = await cryptoWorker.decryptB64(
  103. keyAttributes.encryptedKey,
  104. keyAttributes.keyDecryptionNonce,
  105. kek,
  106. );
  107. useMasterPassword(key, kek, keyAttributes);
  108. return;
  109. }
  110. if (keyAttributes) {
  111. if (
  112. (!user?.token && !user?.encryptedToken) ||
  113. (keyAttributes && !keyAttributes.memLimit)
  114. ) {
  115. clearData();
  116. router.push(PAGES.ROOT);
  117. return;
  118. }
  119. setKeyAttributes(keyAttributes);
  120. return;
  121. }
  122. const srpAttributes: SRPAttributes = getData(
  123. LS_KEYS.SRP_ATTRIBUTES,
  124. );
  125. if (srpAttributes) {
  126. setSrpAttributes(srpAttributes);
  127. } else {
  128. router.push(PAGES.ROOT);
  129. }
  130. };
  131. main();
  132. appContext.showNavBar(true);
  133. }, []);
  134. const getKeyAttributes: VerifyMasterPasswordFormProps["getKeyAttributes"] =
  135. async (kek: string) => {
  136. try {
  137. const cryptoWorker = await ComlinkCryptoWorker.getInstance();
  138. const {
  139. keyAttributes,
  140. encryptedToken,
  141. token,
  142. id,
  143. twoFactorSessionID,
  144. passkeySessionID,
  145. } = await loginViaSRP(srpAttributes, kek);
  146. setIsFirstLogin(true);
  147. if (passkeySessionID) {
  148. const sessionKeyAttributes =
  149. await cryptoWorker.generateKeyAndEncryptToB64(kek);
  150. setKey(
  151. SESSION_KEYS.KEY_ENCRYPTION_KEY,
  152. sessionKeyAttributes,
  153. );
  154. const user = getData(LS_KEYS.USER);
  155. setData(LS_KEYS.USER, {
  156. ...user,
  157. passkeySessionID,
  158. isTwoFactorEnabled: true,
  159. isTwoFactorPasskeysEnabled: true,
  160. });
  161. InMemoryStore.set(MS_KEYS.REDIRECT_URL, PAGES.ROOT);
  162. window.location.href = `${getAccountsURL()}/passkeys/flow?passkeySessionID=${passkeySessionID}&redirect=${
  163. window.location.origin
  164. }/passkeys/finish`;
  165. return;
  166. } else if (twoFactorSessionID) {
  167. const sessionKeyAttributes =
  168. await cryptoWorker.generateKeyAndEncryptToB64(kek);
  169. setKey(
  170. SESSION_KEYS.KEY_ENCRYPTION_KEY,
  171. sessionKeyAttributes,
  172. );
  173. const user = getData(LS_KEYS.USER);
  174. setData(LS_KEYS.USER, {
  175. ...user,
  176. twoFactorSessionID,
  177. isTwoFactorEnabled: true,
  178. });
  179. router.push(PAGES.TWO_FACTOR_VERIFY);
  180. throw Error(CustomError.TWO_FACTOR_ENABLED);
  181. } else {
  182. const user = getData(LS_KEYS.USER);
  183. setData(LS_KEYS.USER, {
  184. ...user,
  185. token,
  186. encryptedToken,
  187. id,
  188. isTwoFactorEnabled: false,
  189. });
  190. setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
  191. return keyAttributes;
  192. }
  193. } catch (e) {
  194. if (e.message !== CustomError.TWO_FACTOR_ENABLED) {
  195. logError(e, "getKeyAttributes failed");
  196. }
  197. throw e;
  198. }
  199. };
  200. const useMasterPassword: VerifyMasterPasswordFormProps["callback"] = async (
  201. key,
  202. kek,
  203. keyAttributes,
  204. passphrase,
  205. ) => {
  206. try {
  207. if (isFirstLogin() && passphrase) {
  208. await generateAndSaveIntermediateKeyAttributes(
  209. passphrase,
  210. keyAttributes,
  211. key,
  212. );
  213. }
  214. await saveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, key);
  215. await decryptAndStoreToken(keyAttributes, key);
  216. try {
  217. let srpAttributes: SRPAttributes = getData(
  218. LS_KEYS.SRP_ATTRIBUTES,
  219. );
  220. if (!srpAttributes) {
  221. srpAttributes = await getSRPAttributes(user.email);
  222. if (srpAttributes) {
  223. setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes);
  224. }
  225. }
  226. addLocalLog(() => `userSRPSetupPending ${!srpAttributes}`);
  227. if (!srpAttributes) {
  228. const loginSubKey = await generateLoginSubKey(kek);
  229. const srpSetupAttributes =
  230. await generateSRPSetupAttributes(loginSubKey);
  231. await configureSRP(srpSetupAttributes);
  232. }
  233. } catch (e) {
  234. logError(e, "migrate to srp failed");
  235. }
  236. const redirectURL = InMemoryStore.get(MS_KEYS.REDIRECT_URL);
  237. InMemoryStore.delete(MS_KEYS.REDIRECT_URL);
  238. router.push(redirectURL ?? APP_HOMES.get(appName));
  239. } catch (e) {
  240. logError(e, "useMasterPassword failed");
  241. }
  242. };
  243. const redirectToRecoverPage = () => router.push(PAGES.RECOVER);
  244. if (!keyAttributes && !srpAttributes) {
  245. return (
  246. <VerticallyCentered>
  247. <EnteSpinner />
  248. </VerticallyCentered>
  249. );
  250. }
  251. return (
  252. <VerticallyCentered>
  253. <FormPaper style={{ minWidth: "320px" }}>
  254. <FormPaperTitle>{t("PASSWORD")}</FormPaperTitle>
  255. <VerifyMasterPasswordForm
  256. buttonText={t("VERIFY_PASSPHRASE")}
  257. callback={useMasterPassword}
  258. user={user}
  259. keyAttributes={keyAttributes}
  260. getKeyAttributes={getKeyAttributes}
  261. srpAttributes={srpAttributes}
  262. />
  263. <FormPaperFooter style={{ justifyContent: "space-between" }}>
  264. <LinkButton onClick={redirectToRecoverPage}>
  265. {t("FORGOT_PASSWORD")}
  266. </LinkButton>
  267. <LinkButton onClick={logoutUser}>
  268. {t("CHANGE_EMAIL")}
  269. </LinkButton>
  270. </FormPaperFooter>
  271. </FormPaper>
  272. </VerticallyCentered>
  273. );
  274. }