From 242c669de4f2b9c7203799f77cb9cd2b6b7994cd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 7 Jun 2024 12:00:29 +0530 Subject: [PATCH] XCP --- web/apps/accounts/src/pages/_app.tsx | 6 +-- web/apps/accounts/src/services/passkey.ts | 3 ++ web/apps/auth/src/pages/_app.tsx | 2 + web/apps/photos/src/pages/_app.tsx | 2 + web/packages/accounts/services/logout.ts | 6 +++ .../new/photos/services/feature-flags.ts | 7 +-- web/packages/next/http.ts | 44 +++++++++++++++++++ web/packages/next/local-user.ts | 11 +++++ 8 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 web/packages/next/http.ts diff --git a/web/apps/accounts/src/pages/_app.tsx b/web/apps/accounts/src/pages/_app.tsx index 25008063dc..c05d41c894 100644 --- a/web/apps/accounts/src/pages/_app.tsx +++ b/web/apps/accounts/src/pages/_app.tsx @@ -12,7 +12,7 @@ import EnteSpinner from "@ente/shared/components/EnteSpinner"; 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"; +import { LS_KEYS } from "@ente/shared/storage/localStorage"; import { getTheme } from "@ente/shared/themes"; import { THEME_COLOR } from "@ente/shared/themes/constants"; import { CssBaseline, useMediaQuery } from "@mui/material"; @@ -64,10 +64,10 @@ export default function App({ Component, pageProps }: AppProps) { }, []); const setupPackageName = () => { - const pkg = getData(LS_KEYS.CLIENT_PACKAGE); + const pkg = localStorage.getItem("clientPackage"); if (!pkg) return; HTTPService.setHeaders({ - "X-Client-Package": pkg.name, + "X-Client-Package": pkg, }); }; diff --git a/web/apps/accounts/src/services/passkey.ts b/web/apps/accounts/src/services/passkey.ts index 0c6ebb396b..ba0e1f402c 100644 --- a/web/apps/accounts/src/services/passkey.ts +++ b/web/apps/accounts/src/services/passkey.ts @@ -96,6 +96,9 @@ export const registerPasskey = async (name: string) => { }; export const getPasskeyRegistrationOptions = async () => { + const clientPackage = localStorage.getItem("clientPackage") + const token = ensure(getToken()); + try { const token = getToken(); if (!token) return; diff --git a/web/apps/auth/src/pages/_app.tsx b/web/apps/auth/src/pages/_app.tsx index 50397b63ed..319d4227a2 100644 --- a/web/apps/auth/src/pages/_app.tsx +++ b/web/apps/auth/src/pages/_app.tsx @@ -33,6 +33,7 @@ import { useRouter } from "next/router"; import { createContext, useContext, useEffect, useRef, useState } from "react"; import LoadingBar, { type LoadingBarRef } from "react-top-loading-bar"; import "../../public/css/global.css"; +import { setAppNameForAuthenticatedRequests } from "@/next/http"; /** * Properties available via the {@link AppContext} to the Auth app's React tree. @@ -78,6 +79,7 @@ export default function App({ Component, pageProps }: AppProps) { const userId = (getData(LS_KEYS.USER) as User)?.id; logStartupBanner(appName, userId); logUnhandledErrorsAndRejections(true); + setAppNameForAuthenticatedRequests(appName); HTTPService.setHeaders({ "X-Client-Package": clientPackageName[appName], }); diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 406136948c..9c3e199cc0 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -1,5 +1,6 @@ import { WhatsNew } from "@/new/photos/components/WhatsNew"; import { CustomHead } from "@/next/components/Head"; +import { setAppNameForAuthenticatedRequests } from "@/next/http"; import { setupI18n } from "@/next/i18n"; import log from "@/next/log"; import { @@ -155,6 +156,7 @@ export default function App({ Component, pageProps }: AppProps) { const userId = (getData(LS_KEYS.USER) as User)?.id; logStartupBanner(appName, userId); logUnhandledErrorsAndRejections(true); + setAppNameForAuthenticatedRequests(appName); HTTPService.setHeaders({ "X-Client-Package": clientPackageName[appName], }); diff --git a/web/packages/accounts/services/logout.ts b/web/packages/accounts/services/logout.ts index 7a150384db..59371e1e7c 100644 --- a/web/packages/accounts/services/logout.ts +++ b/web/packages/accounts/services/logout.ts @@ -1,4 +1,5 @@ import { clearBlobCaches } from "@/next/blob-cache"; +import { clearHTTPState } from "@/next/http"; import log from "@/next/log"; import InMemoryStore from "@ente/shared/storage/InMemoryStore"; import localForage from "@ente/shared/storage/localForage"; @@ -50,4 +51,9 @@ export const accountLogout = async () => { } catch (e) { ignoreError("cache", e); } + try { + clearHTTPState(); + } catch (e) { + ignoreError("http", e); + } }; diff --git a/web/packages/new/photos/services/feature-flags.ts b/web/packages/new/photos/services/feature-flags.ts index 419c6baf26..ab7787b75e 100644 --- a/web/packages/new/photos/services/feature-flags.ts +++ b/web/packages/new/photos/services/feature-flags.ts @@ -1,10 +1,9 @@ import { isDevBuild } from "@/next/env"; +import { authenticatedRequestHeaders } from "@/next/http"; import { localUser } from "@/next/local-user"; import log from "@/next/log"; -import { ensure } from "@/utils/ensure"; import { nullToUndefined } from "@/utils/transform"; import { apiOrigin } from "@ente/shared/network/api"; -import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { z } from "zod"; let _fetchTimeout: ReturnType | undefined; @@ -68,9 +67,7 @@ const fetchAndSaveFeatureFlags = () => const fetchFeatureFlags = async () => { const url = `${apiOrigin()}/remote-store/feature-flags`; const res = await fetch(url, { - headers: { - "X-Auth-Token": ensure(getToken()), - }, + headers: authenticatedRequestHeaders(), }); if (!res.ok) throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`); return res; diff --git a/web/packages/next/http.ts b/web/packages/next/http.ts new file mode 100644 index 0000000000..26f98ac2df --- /dev/null +++ b/web/packages/next/http.ts @@ -0,0 +1,44 @@ +import { ensureAuthToken } from "./local-user"; +import { clientPackageName, type AppName } from "./types/app"; + +/** + * The client package name to include as the "X-Client-Package" header in + * authenticated requests. + */ +let _clientPackageName: string | undefined; + +/** + * Set the client package name (corresponding to the given {@link appName}) that + * should be included as the "X-Client-Package" header in authenticated + * requests. + * + * This state is persisted in memory, and can be cleared using + * {@link clearHTTPState}. + * + * @param appName The {@link AppName} of the current app. + */ +export const setAppNameForAuthenticatedRequests = (appName: AppName) => { + _clientPackageName = clientPackageName[appName]; +}; + +/** + * Forget the effects of a previous {@link setAppNameForAuthenticatedRequests}. + */ +export const clearHTTPState = () => { + _clientPackageName = undefined; +}; + +/** + * Return headers that should be passed alongwith (almost) all authenticated + * `fetch` calls that we make to our API servers. + * + * This uses in-memory state initialized using + * {@link setAppNameForAuthenticatedRequests}. + */ +export const authenticatedRequestHeaders = (): Record => { + const headers: Record = { + "X-Auth-Token": ensureAuthToken(), + }; + if (_clientPackageName) headers["X-Client-Package"] = _clientPackageName; + return headers; +}; diff --git a/web/packages/next/local-user.ts b/web/packages/next/local-user.ts index 2a351a421b..d657264287 100644 --- a/web/packages/next/local-user.ts +++ b/web/packages/next/local-user.ts @@ -38,3 +38,14 @@ export const ensureLocalUser = (): LocalUser => { if (!user) throw new Error("Not logged in"); return user; }; + +/** + * Return the user's auth token, or throw an error. + * + * The user's auth token is stored in local storage after they have successfully + * logged in. This function returns that saved auth token. + * + * If no such token is found (which should only happen if the user is not logged + * in), then it throws an error. + */ +export const ensureAuthToken = (): string => ensureLocalUser().token;