mirror of
https://github.com/ente-io/ente.git
synced 2025-08-07 23:18:10 +00:00
Remove indirection and rename
This commit is contained in:
parent
633e006b73
commit
229f7cc676
@ -1,59 +1,15 @@
|
||||
import log from "@/next/log";
|
||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { ACCOUNTS_PAGES } from "@ente/shared/constants/pages";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const AccountHandoff = () => {
|
||||
/** Legacy alias, remove once mobile code is updated (it is still in beta). */
|
||||
const Page = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const retrieveAccountData = () => {
|
||||
try {
|
||||
extractAccountsToken();
|
||||
|
||||
router.push(ACCOUNTS_PAGES.PASSKEYS);
|
||||
} catch (e) {
|
||||
log.error("Failed to deserialize and set passed user data", e);
|
||||
router.push(ACCOUNTS_PAGES.LOGIN);
|
||||
}
|
||||
};
|
||||
|
||||
const getClientPackageName = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const pkg = urlParams.get("package");
|
||||
if (!pkg) return;
|
||||
setData(LS_KEYS.CLIENT_PACKAGE, { name: pkg });
|
||||
HTTPService.setHeaders({
|
||||
"X-Client-Package": pkg,
|
||||
});
|
||||
};
|
||||
|
||||
const extractAccountsToken = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const token = urlParams.get("token");
|
||||
if (!token) {
|
||||
throw new Error("token not found");
|
||||
}
|
||||
|
||||
const user = getData(LS_KEYS.USER) || {};
|
||||
user.token = token;
|
||||
|
||||
setData(LS_KEYS.USER, user);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getClientPackageName();
|
||||
retrieveAccountData();
|
||||
router.push("/passkeys/setup");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<VerticallyCentered>
|
||||
<EnteSpinner />
|
||||
</VerticallyCentered>
|
||||
);
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default AccountHandoff;
|
||||
export default Page;
|
||||
|
@ -1,305 +1,15 @@
|
||||
import log from "@/next/log";
|
||||
import { clientPackageName } from "@/next/types/app";
|
||||
import { nullToUndefined } from "@/utils/transform";
|
||||
import {
|
||||
CenteredFlex,
|
||||
VerticallyCentered,
|
||||
} from "@ente/shared/components/Container";
|
||||
import EnteButton from "@ente/shared/components/EnteButton";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||
import { fromB64URLSafeNoPadding } from "@ente/shared/crypto/internal/libsodium";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { LS_KEYS, setData } from "@ente/shared/storage/localStorage";
|
||||
import InfoIcon from "@mui/icons-material/Info";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import _sodium from "libsodium-wrappers";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
beginPasskeyAuthentication,
|
||||
finishPasskeyAuthentication,
|
||||
isWhitelistedRedirect,
|
||||
type BeginPasskeyAuthenticationResponse,
|
||||
} from "services/passkey";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const PasskeysFlow = () => {
|
||||
const [errored, setErrored] = useState(false);
|
||||
|
||||
const [invalidInfo, setInvalidInfo] = useState(false);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const init = async () => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Extract redirect from the query params.
|
||||
const redirect = nullToUndefined(searchParams.get("redirect"));
|
||||
const redirectURL = redirect ? new URL(redirect) : undefined;
|
||||
|
||||
// Ensure that redirectURL is whitelisted, otherwise show an invalid
|
||||
// "login" URL error to the user.
|
||||
if (!redirectURL || !isWhitelistedRedirect(redirectURL)) {
|
||||
setInvalidInfo(true);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let pkg = clientPackageName["photos"];
|
||||
if (redirectURL.protocol === "enteauth:") {
|
||||
pkg = clientPackageName["auth"];
|
||||
} else if (redirectURL.hostname.startsWith("accounts")) {
|
||||
pkg = clientPackageName["accounts"];
|
||||
}
|
||||
|
||||
setData(LS_KEYS.CLIENT_PACKAGE, { name: pkg });
|
||||
// The server needs to know the app on whose behalf we're trying to log in
|
||||
HTTPService.setHeaders({
|
||||
"X-Client-Package": pkg,
|
||||
});
|
||||
|
||||
// get passkeySessionID from the query params
|
||||
const passkeySessionID = searchParams.get("passkeySessionID") as string;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
let beginData: BeginPasskeyAuthenticationResponse;
|
||||
|
||||
try {
|
||||
beginData = await beginAuthentication(passkeySessionID);
|
||||
} catch (e) {
|
||||
log.error("Couldn't begin passkey authentication", e);
|
||||
setErrored(true);
|
||||
return;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
let credential: Credential | null = null;
|
||||
|
||||
let tries = 0;
|
||||
const maxTries = 3;
|
||||
|
||||
while (tries < maxTries) {
|
||||
try {
|
||||
credential = await getCredential(beginData.options.publicKey);
|
||||
} catch (e) {
|
||||
log.error("Couldn't get credential", e);
|
||||
continue;
|
||||
} finally {
|
||||
tries++;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!credential) {
|
||||
if (!isWebAuthnSupported()) {
|
||||
alert("WebAuthn is not supported in this browser");
|
||||
}
|
||||
setErrored(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
let finishData;
|
||||
|
||||
try {
|
||||
finishData = await finishAuthentication(
|
||||
credential,
|
||||
passkeySessionID,
|
||||
beginData.ceremonySessionID,
|
||||
);
|
||||
} catch (e) {
|
||||
log.error("Couldn't finish passkey authentication", e);
|
||||
setErrored(true);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const encodedResponse = _sodium.to_base64(JSON.stringify(finishData));
|
||||
|
||||
// TODO-PK: Shouldn't this be URL encoded?
|
||||
window.location.href = `${redirect}?response=${encodedResponse}`;
|
||||
};
|
||||
|
||||
const beginAuthentication = async (sessionId: string) => {
|
||||
const data = await beginPasskeyAuthentication(sessionId);
|
||||
return data;
|
||||
};
|
||||
|
||||
function isWebAuthnSupported(): boolean {
|
||||
if (!navigator.credentials) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const getCredential = async (
|
||||
publicKey: any,
|
||||
timeoutMillis: number = 60000, // Default timeout of 60 seconds
|
||||
): Promise<Credential | null> => {
|
||||
publicKey.challenge = await fromB64URLSafeNoPadding(
|
||||
publicKey.challenge,
|
||||
);
|
||||
for (const listItem of publicKey.allowCredentials ?? []) {
|
||||
listItem.id = await fromB64URLSafeNoPadding(listItem.id);
|
||||
// note: we are orverwriting the transports array with all possible values.
|
||||
// This is because the browser will only prompt the user for the transport that is available.
|
||||
// Warning: In case of invalid transport value, the webauthn will fail on Safari & iOS browsers
|
||||
listItem.transports = ["usb", "nfc", "ble", "internal"];
|
||||
}
|
||||
publicKey.timeout = timeoutMillis;
|
||||
const publicKeyCredentialCreationOptions: CredentialRequestOptions = {
|
||||
publicKey: publicKey,
|
||||
};
|
||||
const credential = await navigator.credentials.get(
|
||||
publicKeyCredentialCreationOptions,
|
||||
);
|
||||
return credential;
|
||||
};
|
||||
|
||||
const finishAuthentication = async (
|
||||
credential: Credential,
|
||||
sessionId: string,
|
||||
ceremonySessionId: string,
|
||||
) => {
|
||||
const data = await finishPasskeyAuthentication(
|
||||
credential,
|
||||
sessionId,
|
||||
ceremonySessionId,
|
||||
);
|
||||
return data;
|
||||
};
|
||||
/** Legacy alias, remove once mobile code is updated (it is still in beta). */
|
||||
const Page = () => {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
router.push("/passkeys/verify");
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<VerticallyCentered>
|
||||
<EnteSpinner />
|
||||
</VerticallyCentered>
|
||||
);
|
||||
}
|
||||
|
||||
if (invalidInfo) {
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%"
|
||||
>
|
||||
<Box maxWidth="30rem">
|
||||
<FormPaper
|
||||
style={{
|
||||
padding: "1rem",
|
||||
}}
|
||||
>
|
||||
<InfoIcon />
|
||||
<Typography fontWeight="bold" variant="h1">
|
||||
{t("PASSKEY_LOGIN_FAILED")}
|
||||
</Typography>
|
||||
<Typography marginTop="1rem">
|
||||
{t("PASSKEY_LOGIN_URL_INVALID")}
|
||||
</Typography>
|
||||
</FormPaper>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (errored) {
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%"
|
||||
>
|
||||
<Box maxWidth="30rem">
|
||||
<FormPaper
|
||||
style={{
|
||||
padding: "1rem",
|
||||
}}
|
||||
>
|
||||
<InfoIcon />
|
||||
<Typography fontWeight="bold" variant="h1">
|
||||
{t("PASSKEY_LOGIN_FAILED")}
|
||||
</Typography>
|
||||
<Typography marginTop="1rem">
|
||||
{t("PASSKEY_LOGIN_ERRORED")}
|
||||
</Typography>
|
||||
<EnteButton
|
||||
onClick={() => {
|
||||
setErrored(false);
|
||||
init();
|
||||
}}
|
||||
fullWidth
|
||||
style={{
|
||||
marginTop: "1rem",
|
||||
}}
|
||||
color="primary"
|
||||
type="button"
|
||||
variant="contained"
|
||||
>
|
||||
{t("TRY_AGAIN")}
|
||||
</EnteButton>
|
||||
<EnteButton
|
||||
href="/passkeys/recover"
|
||||
fullWidth
|
||||
style={{
|
||||
marginTop: "1rem",
|
||||
}}
|
||||
color="primary"
|
||||
type="button"
|
||||
variant="text"
|
||||
>
|
||||
{t("RECOVER_TWO_FACTOR")}
|
||||
</EnteButton>
|
||||
</FormPaper>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%"
|
||||
>
|
||||
<Box maxWidth="30rem">
|
||||
<FormPaper
|
||||
style={{
|
||||
padding: "1rem",
|
||||
}}
|
||||
>
|
||||
<InfoIcon />
|
||||
<Typography fontWeight="bold" variant="h1">
|
||||
{t("LOGIN_WITH_PASSKEY")}
|
||||
</Typography>
|
||||
<Typography marginTop="1rem">
|
||||
{t("PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER")}
|
||||
</Typography>
|
||||
<CenteredFlex marginTop="1rem">
|
||||
<img
|
||||
alt="ente Logo Circular"
|
||||
height={150}
|
||||
width={150}
|
||||
src="/images/ente-circular.png"
|
||||
/>
|
||||
</CenteredFlex>
|
||||
</FormPaper>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default PasskeysFlow;
|
||||
export default Page;
|
||||
|
@ -11,7 +11,6 @@ import MenuItemDivider from "@ente/shared/components/Menu/MenuItemDivider";
|
||||
import { MenuItemGroup } from "@ente/shared/components/Menu/MenuItemGroup";
|
||||
import SingleInputForm from "@ente/shared/components/SingleInputForm";
|
||||
import Titlebar from "@ente/shared/components/Titlebar";
|
||||
import { ACCOUNTS_PAGES } from "@ente/shared/constants/pages";
|
||||
import { getToken } from "@ente/shared/storage/localStorage/helpers";
|
||||
import { formatDateTimeFull } from "@ente/shared/time/format";
|
||||
import CalendarTodayIcon from "@mui/icons-material/CalendarToday";
|
||||
@ -65,7 +64,7 @@ const Passkeys = () => {
|
||||
const checkLoggedIn = () => {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
router.push(ACCOUNTS_PAGES.LOGIN);
|
||||
router.push("/login");
|
||||
}
|
||||
};
|
||||
|
||||
|
59
web/apps/accounts/src/pages/passkeys/setup.tsx
Normal file
59
web/apps/accounts/src/pages/passkeys/setup.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
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 { useEffect } from "react";
|
||||
|
||||
const AccountHandoff = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const retrieveAccountData = () => {
|
||||
try {
|
||||
extractAccountsToken();
|
||||
|
||||
router.push("/passkeys");
|
||||
} catch (e) {
|
||||
log.error("Failed to deserialize and set passed user data", e);
|
||||
// Not much we can do here, but this redirect might be misleading.
|
||||
router.push("/login");
|
||||
}
|
||||
};
|
||||
|
||||
const getClientPackageName = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const pkg = urlParams.get("package");
|
||||
if (!pkg) return;
|
||||
setData(LS_KEYS.CLIENT_PACKAGE, { name: pkg });
|
||||
HTTPService.setHeaders({
|
||||
"X-Client-Package": pkg,
|
||||
});
|
||||
};
|
||||
|
||||
const extractAccountsToken = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const token = urlParams.get("token");
|
||||
if (!token) {
|
||||
throw new Error("token not found");
|
||||
}
|
||||
|
||||
const user = getData(LS_KEYS.USER) || {};
|
||||
user.token = token;
|
||||
|
||||
setData(LS_KEYS.USER, user);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getClientPackageName();
|
||||
retrieveAccountData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<VerticallyCentered>
|
||||
<EnteSpinner />
|
||||
</VerticallyCentered>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountHandoff;
|
305
web/apps/accounts/src/pages/passkeys/verify.tsx
Normal file
305
web/apps/accounts/src/pages/passkeys/verify.tsx
Normal file
@ -0,0 +1,305 @@
|
||||
import log from "@/next/log";
|
||||
import { clientPackageName } from "@/next/types/app";
|
||||
import { nullToUndefined } from "@/utils/transform";
|
||||
import {
|
||||
CenteredFlex,
|
||||
VerticallyCentered,
|
||||
} from "@ente/shared/components/Container";
|
||||
import EnteButton from "@ente/shared/components/EnteButton";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||
import { fromB64URLSafeNoPadding } from "@ente/shared/crypto/internal/libsodium";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { LS_KEYS, setData } from "@ente/shared/storage/localStorage";
|
||||
import InfoIcon from "@mui/icons-material/Info";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import _sodium from "libsodium-wrappers";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
beginPasskeyAuthentication,
|
||||
finishPasskeyAuthentication,
|
||||
isWhitelistedRedirect,
|
||||
type BeginPasskeyAuthenticationResponse,
|
||||
} from "services/passkey";
|
||||
|
||||
const PasskeysFlow = () => {
|
||||
const [errored, setErrored] = useState(false);
|
||||
|
||||
const [invalidInfo, setInvalidInfo] = useState(false);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const init = async () => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Extract redirect from the query params.
|
||||
const redirect = nullToUndefined(searchParams.get("redirect"));
|
||||
const redirectURL = redirect ? new URL(redirect) : undefined;
|
||||
|
||||
// Ensure that redirectURL is whitelisted, otherwise show an invalid
|
||||
// "login" URL error to the user.
|
||||
if (!redirectURL || !isWhitelistedRedirect(redirectURL)) {
|
||||
setInvalidInfo(true);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let pkg = clientPackageName["photos"];
|
||||
if (redirectURL.protocol === "enteauth:") {
|
||||
pkg = clientPackageName["auth"];
|
||||
} else if (redirectURL.hostname.startsWith("accounts")) {
|
||||
pkg = clientPackageName["accounts"];
|
||||
}
|
||||
|
||||
setData(LS_KEYS.CLIENT_PACKAGE, { name: pkg });
|
||||
// The server needs to know the app on whose behalf we're trying to log in
|
||||
HTTPService.setHeaders({
|
||||
"X-Client-Package": pkg,
|
||||
});
|
||||
|
||||
// get passkeySessionID from the query params
|
||||
const passkeySessionID = searchParams.get("passkeySessionID") as string;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
let beginData: BeginPasskeyAuthenticationResponse;
|
||||
|
||||
try {
|
||||
beginData = await beginAuthentication(passkeySessionID);
|
||||
} catch (e) {
|
||||
log.error("Couldn't begin passkey authentication", e);
|
||||
setErrored(true);
|
||||
return;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
let credential: Credential | null = null;
|
||||
|
||||
let tries = 0;
|
||||
const maxTries = 3;
|
||||
|
||||
while (tries < maxTries) {
|
||||
try {
|
||||
credential = await getCredential(beginData.options.publicKey);
|
||||
} catch (e) {
|
||||
log.error("Couldn't get credential", e);
|
||||
continue;
|
||||
} finally {
|
||||
tries++;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!credential) {
|
||||
if (!isWebAuthnSupported()) {
|
||||
alert("WebAuthn is not supported in this browser");
|
||||
}
|
||||
setErrored(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
let finishData;
|
||||
|
||||
try {
|
||||
finishData = await finishAuthentication(
|
||||
credential,
|
||||
passkeySessionID,
|
||||
beginData.ceremonySessionID,
|
||||
);
|
||||
} catch (e) {
|
||||
log.error("Couldn't finish passkey authentication", e);
|
||||
setErrored(true);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const encodedResponse = _sodium.to_base64(JSON.stringify(finishData));
|
||||
|
||||
// TODO-PK: Shouldn't this be URL encoded?
|
||||
window.location.href = `${redirect}?response=${encodedResponse}`;
|
||||
};
|
||||
|
||||
const beginAuthentication = async (sessionId: string) => {
|
||||
const data = await beginPasskeyAuthentication(sessionId);
|
||||
return data;
|
||||
};
|
||||
|
||||
function isWebAuthnSupported(): boolean {
|
||||
if (!navigator.credentials) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const getCredential = async (
|
||||
publicKey: any,
|
||||
timeoutMillis: number = 60000, // Default timeout of 60 seconds
|
||||
): Promise<Credential | null> => {
|
||||
publicKey.challenge = await fromB64URLSafeNoPadding(
|
||||
publicKey.challenge,
|
||||
);
|
||||
for (const listItem of publicKey.allowCredentials ?? []) {
|
||||
listItem.id = await fromB64URLSafeNoPadding(listItem.id);
|
||||
// note: we are orverwriting the transports array with all possible values.
|
||||
// This is because the browser will only prompt the user for the transport that is available.
|
||||
// Warning: In case of invalid transport value, the webauthn will fail on Safari & iOS browsers
|
||||
listItem.transports = ["usb", "nfc", "ble", "internal"];
|
||||
}
|
||||
publicKey.timeout = timeoutMillis;
|
||||
const publicKeyCredentialCreationOptions: CredentialRequestOptions = {
|
||||
publicKey: publicKey,
|
||||
};
|
||||
const credential = await navigator.credentials.get(
|
||||
publicKeyCredentialCreationOptions,
|
||||
);
|
||||
return credential;
|
||||
};
|
||||
|
||||
const finishAuthentication = async (
|
||||
credential: Credential,
|
||||
sessionId: string,
|
||||
ceremonySessionId: string,
|
||||
) => {
|
||||
const data = await finishPasskeyAuthentication(
|
||||
credential,
|
||||
sessionId,
|
||||
ceremonySessionId,
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<VerticallyCentered>
|
||||
<EnteSpinner />
|
||||
</VerticallyCentered>
|
||||
);
|
||||
}
|
||||
|
||||
if (invalidInfo) {
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%"
|
||||
>
|
||||
<Box maxWidth="30rem">
|
||||
<FormPaper
|
||||
style={{
|
||||
padding: "1rem",
|
||||
}}
|
||||
>
|
||||
<InfoIcon />
|
||||
<Typography fontWeight="bold" variant="h1">
|
||||
{t("PASSKEY_LOGIN_FAILED")}
|
||||
</Typography>
|
||||
<Typography marginTop="1rem">
|
||||
{t("PASSKEY_LOGIN_URL_INVALID")}
|
||||
</Typography>
|
||||
</FormPaper>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (errored) {
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%"
|
||||
>
|
||||
<Box maxWidth="30rem">
|
||||
<FormPaper
|
||||
style={{
|
||||
padding: "1rem",
|
||||
}}
|
||||
>
|
||||
<InfoIcon />
|
||||
<Typography fontWeight="bold" variant="h1">
|
||||
{t("PASSKEY_LOGIN_FAILED")}
|
||||
</Typography>
|
||||
<Typography marginTop="1rem">
|
||||
{t("PASSKEY_LOGIN_ERRORED")}
|
||||
</Typography>
|
||||
<EnteButton
|
||||
onClick={() => {
|
||||
setErrored(false);
|
||||
init();
|
||||
}}
|
||||
fullWidth
|
||||
style={{
|
||||
marginTop: "1rem",
|
||||
}}
|
||||
color="primary"
|
||||
type="button"
|
||||
variant="contained"
|
||||
>
|
||||
{t("TRY_AGAIN")}
|
||||
</EnteButton>
|
||||
<EnteButton
|
||||
href="/passkeys/recover"
|
||||
fullWidth
|
||||
style={{
|
||||
marginTop: "1rem",
|
||||
}}
|
||||
color="primary"
|
||||
type="button"
|
||||
variant="text"
|
||||
>
|
||||
{t("RECOVER_TWO_FACTOR")}
|
||||
</EnteButton>
|
||||
</FormPaper>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%"
|
||||
>
|
||||
<Box maxWidth="30rem">
|
||||
<FormPaper
|
||||
style={{
|
||||
padding: "1rem",
|
||||
}}
|
||||
>
|
||||
<InfoIcon />
|
||||
<Typography fontWeight="bold" variant="h1">
|
||||
{t("LOGIN_WITH_PASSKEY")}
|
||||
</Typography>
|
||||
<Typography marginTop="1rem">
|
||||
{t("PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER")}
|
||||
</Typography>
|
||||
<CenteredFlex marginTop="1rem">
|
||||
<img
|
||||
alt="ente Logo Circular"
|
||||
height={150}
|
||||
width={150}
|
||||
src="/images/ente-circular.png"
|
||||
/>
|
||||
</CenteredFlex>
|
||||
</FormPaper>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasskeysFlow;
|
@ -11,10 +11,7 @@ import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem";
|
||||
import RecoveryKey from "@ente/shared/components/RecoveryKey";
|
||||
import ThemeSwitcher from "@ente/shared/components/ThemeSwitcher";
|
||||
import {
|
||||
ACCOUNTS_PAGES,
|
||||
PHOTOS_PAGES as PAGES,
|
||||
} from "@ente/shared/constants/pages";
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import ComlinkCryptoWorker from "@ente/shared/crypto";
|
||||
import { getRecoveryKey } from "@ente/shared/crypto/helpers";
|
||||
import {
|
||||
@ -509,11 +506,10 @@ const UtilitySection: React.FC<UtilitySectionProps> = ({ closeSidebar }) => {
|
||||
|
||||
// Ente Accounts specific JWT token.
|
||||
const accountsToken = await getAccountsToken();
|
||||
const pkg = clientPackageName["photos"];
|
||||
|
||||
window.open(
|
||||
`${accountsAppURL()}${
|
||||
ACCOUNTS_PAGES.ACCOUNT_HANDOFF
|
||||
}?package=${clientPackageName["photos"]}&token=${accountsToken}`,
|
||||
`${accountsAppURL()}/passkeys/setup?package=${pkg}&token=${accountsToken}`,
|
||||
);
|
||||
} catch (e) {
|
||||
log.error("failed to redirect to accounts page", e);
|
||||
|
@ -1,9 +1,5 @@
|
||||
import type { AppName } from "@/next/types/app";
|
||||
import {
|
||||
ACCOUNTS_PAGES,
|
||||
AUTH_PAGES,
|
||||
PHOTOS_PAGES,
|
||||
} from "@ente/shared/constants/pages";
|
||||
import { AUTH_PAGES, PHOTOS_PAGES } from "@ente/shared/constants/pages";
|
||||
|
||||
/**
|
||||
* The default page ("home route") for each of our apps.
|
||||
@ -13,7 +9,7 @@ import {
|
||||
export const appHomeRoute = (appName: AppName): string => {
|
||||
switch (appName) {
|
||||
case "accounts":
|
||||
return ACCOUNTS_PAGES.PASSKEYS;
|
||||
return "/passkeys";
|
||||
case "auth":
|
||||
return AUTH_PAGES.AUTH;
|
||||
case "photos":
|
||||
|
Loading…
x
Reference in New Issue
Block a user