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 () => {