diff --git a/web/apps/accounts/src/pages/passkeys/handoff.tsx b/web/apps/accounts/src/pages/passkeys/handoff.tsx deleted file mode 100644 index 0f14455291..0000000000 --- a/web/apps/accounts/src/pages/passkeys/handoff.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { setClientPackageForAuthenticatedRequests } from "@/next/http"; -import log from "@/next/log"; -import { VerticallyCentered } from "@ente/shared/components/Container"; -import EnteSpinner from "@ente/shared/components/EnteSpinner"; -import HTTPService from "@ente/shared/network/HTTPService"; -import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage"; -import { useRouter } from "next/router"; -import React, { useEffect } from "react"; - -/** - * Parse credentials passed as query parameters by one of our client apps, save - * them to local storage, and then redirect to the passkeys listing. - */ -const Page: React.FC = () => { - const router = useRouter(); - - useEffect(() => { - const urlParams = new URLSearchParams(window.location.search); - - const clientPackage = urlParams.get("client"); - if (clientPackage) { - // TODO-PK: mobile is not passing it. is that expected? - localStorage.setItem("clientPackage", clientPackage); - setClientPackageForAuthenticatedRequests(clientPackage); - HTTPService.setHeaders({ - "X-Client-Package": clientPackage, - }); - } - - const token = urlParams.get("token"); - if (!token) { - log.error("Missing accounts token"); - router.push("/login"); - return; - } - - const user = getData(LS_KEYS.USER) || {}; - user.token = token; - setData(LS_KEYS.USER, user); - - router.push("/passkeys"); - }, []); - - return ( - - - - ); -}; - -export default Page; diff --git a/web/apps/accounts/src/pages/passkeys/index.tsx b/web/apps/accounts/src/pages/passkeys/index.tsx index 618d2db0d6..47d6e3d2da 100644 --- a/web/apps/accounts/src/pages/passkeys/index.tsx +++ b/web/apps/accounts/src/pages/passkeys/index.tsx @@ -41,20 +41,26 @@ const Page: React.FC = () => { const router = useRouter(); - const refreshPasskeys = async () => { - try { - setPasskeys(await getPasskeys()); - } catch (e) { - log.error("Failed to fetch passkeys", e); - setDialogBoxAttributesV2({ - title: t("ERROR"), - content: t("passkey_fetch_failed"), - close: {}, + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + + const clientPackage = urlParams.get("client"); + if (clientPackage) { + // TODO-PK: mobile is not passing it. is that expected? + localStorage.setItem("clientPackage", clientPackage); + setClientPackageForAuthenticatedRequests(clientPackage); + HTTPService.setHeaders({ + "X-Client-Package": clientPackage, }); } - }; - useEffect(() => { + const token = urlParams.get("token"); + if (!token) { + log.error("Missing accounts token"); + router.push("/login"); + return; + } + if (!getToken()) { router.push("/login"); return; @@ -64,6 +70,23 @@ const Page: React.FC = () => { void refreshPasskeys(); }, []); + const refreshPasskeys = async () => { + try { + setPasskeys(await getPasskeys()); + } catch (e) { + log.error("Failed to fetch passkeys", e); + showPasskeyFetchFailedErrorDialog(); + } + }; + + const showPasskeyFetchFailedErrorDialog = () => { + setDialogBoxAttributesV2({ + title: t("ERROR"), + content: t("passkey_fetch_failed"), + close: {}, + }); + }; + const handleSelectPasskey = (passkey: Passkey) => { setSelectedPasskey(passkey); setShowPasskeyDrawer(true); diff --git a/web/apps/photos/src/components/Sidebar/index.tsx b/web/apps/photos/src/components/Sidebar/index.tsx index 34f8c78dd8..d2d7aa1e83 100644 --- a/web/apps/photos/src/components/Sidebar/index.tsx +++ b/web/apps/photos/src/components/Sidebar/index.tsx @@ -428,7 +428,6 @@ const UtilitySection: React.FC = ({ closeSidebar }) => { const router = useRouter(); const appContext = useContext(AppContext); const { - appName, setDialogMessage, startLoading, watchFolderView, @@ -473,7 +472,7 @@ const UtilitySection: React.FC = ({ closeSidebar }) => { closeSidebar(); try { - await openAccountsManagePasskeysPage(appName); + await openAccountsManagePasskeysPage(); } catch (e) { log.error("failed to redirect to accounts page", e); } diff --git a/web/docs/webauthn-passkeys.md b/web/docs/webauthn-passkeys.md index 465af26726..b80b61240a 100644 --- a/web/docs/webauthn-passkeys.md +++ b/web/docs/webauthn-passkeys.md @@ -21,9 +21,9 @@ some operating system restrictions. ## Getting to the passkeys manager -As of Feb 2024, Ente clients have a button to navigate to a WebView of Ente +As of Jun 2024, Ente clients have a button to navigate to a WebView of Ente Accounts. Ente Accounts allows users to add and manage their registered -passkeys. +passkeys, and later authenticate with them as a second factor. > [!NOTE] > @@ -55,12 +55,10 @@ used.** This restriction is a byproduct of the enablement for automatic login. ### Automatically logging into Ente Accounts Clients open a WebView with the URL -`https://accounts.ente.io/passkeys/handoff?token=`. This page -will parse the token for usage in subsequent Accounts-related API calls. +`https://accounts.ente.io/passkeys?token=`. -If the token is valid, the user will be automatically redirected to the passkeys -management page. Otherwise, they will be required to login with their Ente -credentials. +If the token is valid, the user will be show a list of their passkeys, and they +can edit / delete them, or add new ones. ## Registering a WebAuthn credential @@ -128,7 +126,7 @@ func (u *PasskeyUser) WebAuthnCredentials() []webauthn.Credential { "publicKey": { "rp": { "name": "Ente", - "id": "accounts.ente.io" + "id": "ente.io" }, "user": { "name": "james@example.org", @@ -335,15 +333,12 @@ if (passkeySessionID) { } ``` -The client should redirect the user to Accounts with this session ID to prompt -credential authentication. We use Accounts as the central WebAuthn hub since it -is needed anyways to service credential authentication from mobile clients, so -we use the same flow for other (web, desktop) clients too. +The client should redirect the user to the Ente Accounts web app with this +session ID to prompt credential authentication. We use Accounts as the central +WebAuthn hub since it allows us to handle mobile and desktop clients too. ```tsx -window.location.href = `${accountsAppURL()}/passkeys/verify?passkeySessionID=${passkeySessionID}&clientPackage=io.ente.photos.web&redirect=${ - window.location.origin -}/passkeys/finish`; +window.location.href = `${accountsAppURL()}/passkeys/verify?passkeySessionID=${passkeySessionID}&clientPackage=io.ente.photos.web&redirect=${window.location.origin}/passkeys/finish`; ``` ### Requesting publicKey options (begin) @@ -367,7 +362,7 @@ window.location.href = `${accountsAppURL()}/passkeys/verify?passkeySessionID=${p "publicKey": { "challenge": "dF-mmdZSBxP6Z7OhZrmQ4h-k-BkuuX6ERnW_ckYdkvc", "timeout": 300000, - "rpId": "accounts.ente.io", + "rpId": "ente.io", "allowCredentials": [ { "type": "public-key", diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 32884c358f..c4e2b1429e 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -69,11 +69,12 @@ export const redirectUserToPasskeyVerificationFlow = ( * * @param appName The {@link AppName} of the app which is calling this function. */ -export const openAccountsManagePasskeysPage = async (appName: AppName) => { - // check if the user has passkey recovery enabled +export const openAccountsManagePasskeysPage = async () => { + // Check if the user has passkey recovery enabled const recoveryEnabled = await isPasskeyRecoveryEnabled(); if (!recoveryEnabled) { - // let's create the necessary recovery information + // If not, enable it for them by creating the necessary recovery + // information to prevent them from getting locked out. const recoveryKey = await getRecoveryKey(); const resetSecret = await generateEncryptionKey(); @@ -91,11 +92,12 @@ export const openAccountsManagePasskeysPage = async (appName: AppName) => { ); } + // Redirect to the Ente Accounts app where they can view and add and manage + // their passkeys. const token = await getAccountsToken(); - const client = clientPackageName[appName]; - const params = new URLSearchParams({ token, client }); + const params = new URLSearchParams({ token }); - window.open(`${accountsAppURL()}/passkeys/handoff?${params.toString()}`); + window.open(`${accountsAppURL()}/passkeys?${params.toString()}`); }; export const isPasskeyRecoveryEnabled = async () => {