This commit is contained in:
Manav Rathi
2024-06-11 15:39:40 +05:30
parent a27da1de31
commit 3285ccdb3a
5 changed files with 54 additions and 86 deletions

View File

@@ -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 (
<VerticallyCentered>
<EnteSpinner />
</VerticallyCentered>
);
};
export default Page;

View File

@@ -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);

View File

@@ -428,7 +428,6 @@ const UtilitySection: React.FC<UtilitySectionProps> = ({ closeSidebar }) => {
const router = useRouter();
const appContext = useContext(AppContext);
const {
appName,
setDialogMessage,
startLoading,
watchFolderView,
@@ -473,7 +472,7 @@ const UtilitySection: React.FC<UtilitySectionProps> = ({ closeSidebar }) => {
closeSidebar();
try {
await openAccountsManagePasskeysPage(appName);
await openAccountsManagePasskeysPage();
} catch (e) {
log.error("failed to redirect to accounts page", e);
}

View File

@@ -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=<accountsToken>`. This page
will parse the token for usage in subsequent Accounts-related API calls.
`https://accounts.ente.io/passkeys?token=<accountsToken>`.
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",

View File

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