mirror of
https://github.com/ente-io/ente.git
synced 2025-05-24 12:09:17 +00:00
117 lines
4.0 KiB
TypeScript
117 lines
4.0 KiB
TypeScript
import { fromB64URLSafeNoPaddingString } from "@/base/crypto/libsodium";
|
|
import log from "@/base/log";
|
|
import { nullToUndefined } from "@/utils/transform";
|
|
import { VerticallyCentered } from "@ente/shared/components/Container";
|
|
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
|
import {
|
|
LS_KEYS,
|
|
getData,
|
|
setData,
|
|
setLSUser,
|
|
} from "@ente/shared/storage/localStorage";
|
|
import { useRouter } from "next/router";
|
|
import React, { useEffect } from "react";
|
|
import { PAGES } from "../../constants/pages";
|
|
import { unstashRedirect } from "../../services/redirect";
|
|
import type { PageProps } from "../../types/page";
|
|
|
|
/**
|
|
* [Note: Finish passkey flow in the requesting app]
|
|
*
|
|
* The passkey finish step needs to happen in the context of the client which
|
|
* invoked the passkey flow since it needs to save the obtained credentials
|
|
* in local storage (which is tied to the current origin).
|
|
*/
|
|
const Page: React.FC<PageProps> = () => {
|
|
const router = useRouter();
|
|
|
|
useEffect(() => {
|
|
// Extract response from query params
|
|
const searchParams = new URLSearchParams(window.location.search);
|
|
const passkeySessionID = searchParams.get("passkeySessionID");
|
|
const response = searchParams.get("response");
|
|
if (!passkeySessionID || !response) return;
|
|
|
|
saveCredentialsAndNavigateTo(passkeySessionID, response).then(
|
|
(slug: string) => {
|
|
router.push(slug);
|
|
},
|
|
);
|
|
}, []);
|
|
|
|
return (
|
|
<VerticallyCentered>
|
|
<EnteSpinner />
|
|
</VerticallyCentered>
|
|
);
|
|
};
|
|
|
|
export default Page;
|
|
|
|
/**
|
|
* Extract credentials from a successful passkey flow "response" query parameter
|
|
* and save them to local storage for use by subsequent steps (or normal
|
|
* functioning) of the app.
|
|
*
|
|
* @param passkeySessionID The string that is passed as the "passkeySessionID"
|
|
* query parameter to us.
|
|
*
|
|
* @param response The string that is passed as the "response" query parameter to
|
|
* us (we're the final "finish" page in the passkey flow).
|
|
*
|
|
* @returns the slug that we should navigate to now.
|
|
*/
|
|
const saveCredentialsAndNavigateTo = async (
|
|
passkeySessionID: string,
|
|
response: string,
|
|
) => {
|
|
const inflightPasskeySessionID = nullToUndefined(
|
|
sessionStorage.getItem("inflightPasskeySessionID"),
|
|
);
|
|
if (
|
|
!inflightPasskeySessionID ||
|
|
passkeySessionID != inflightPasskeySessionID
|
|
) {
|
|
// This is not the princess we were looking for. However, we have
|
|
// already entered this castle. Redirect back to home without changing
|
|
// any state, hopefully this will get the user back to where they were.
|
|
log.info(
|
|
`Ignoring redirect for unexpected passkeySessionID ${passkeySessionID}`,
|
|
);
|
|
return "/";
|
|
}
|
|
|
|
sessionStorage.removeItem("inflightPasskeySessionID");
|
|
|
|
// Decode response string (inverse of the steps we perform in
|
|
// `passkeyAuthenticationSuccessRedirectURL`).
|
|
const decodedResponse = JSON.parse(
|
|
await fromB64URLSafeNoPaddingString(response),
|
|
);
|
|
|
|
// Only one of `encryptedToken` or `token` will be present depending on the
|
|
// account's lifetime:
|
|
//
|
|
// - The plaintext "token" will be passed during fresh signups, where we
|
|
// don't yet have keys to encrypt it, the account itself is being created
|
|
// as we go through this flow.
|
|
// TODO(MR): Conceptually this cannot happen. During a _real_ fresh signup
|
|
// we'll never enter the passkey verification flow. Remove this code after
|
|
// making sure that it doesn't get triggered in cases where an existing
|
|
// user goes through the new user flow.
|
|
//
|
|
// - The encrypted `encryptedToken` will be present otherwise (i.e. if the
|
|
// user is signing into an existing account).
|
|
const { keyAttributes, encryptedToken, token, id } = decodedResponse;
|
|
|
|
await setLSUser({
|
|
...getData(LS_KEYS.USER),
|
|
token,
|
|
encryptedToken,
|
|
id,
|
|
});
|
|
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
|
|
|
|
return unstashRedirect() ?? PAGES.CREDENTIALS;
|
|
};
|