diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 89aa8d1770..c8565557a6 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -224,6 +224,7 @@ const Page: React.FC = ({ appContext }) => { id, twoFactorSessionID, passkeySessionID, + accountsUrl, } = await userVerificationResultAfterResolvingSecondFactorChoice( await loginViaSRP(srpAttributes!, kek), @@ -245,8 +246,10 @@ const Page: React.FC = ({ appContext }) => { isTwoFactorPasskeysEnabled: true, }); stashRedirect("/"); - const url = - passkeyVerificationRedirectURL(passkeySessionID); + const url = passkeyVerificationRedirectURL( + accountsUrl, + passkeySessionID, + ); setPasskeyVerificationData({ passkeySessionID, url }); openPasskeyVerificationURL({ passkeySessionID, url }); throw Error(CustomError.TWO_FACTOR_ENABLED); diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index c5c6ac4ab0..0aa53f5e6e 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -90,6 +90,7 @@ const Page: React.FC = ({ appContext }) => { id, twoFactorSessionID, passkeySessionID, + accountsUrl, } = await userVerificationResultAfterResolvingSecondFactorChoice( await verifyEmail(email, ott, cleanedReferral), ); @@ -107,7 +108,10 @@ const Page: React.FC = ({ appContext }) => { // Update: This flag causes the interactive encryption key to be // generated, so it has a functional impact we need. setIsFirstLogin(true); - const url = passkeyVerificationRedirectURL(passkeySessionID); + const url = passkeyVerificationRedirectURL( + accountsUrl, + passkeySessionID, + ); setPasskeyVerificationData({ passkeySessionID, url }); openPasskeyVerificationURL({ passkeySessionID, url }); } else if (twoFactorSessionID) { diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index f6f4ae1d68..1440dbd9cd 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -30,10 +30,16 @@ import { unstashRedirect } from "./redirect"; * On successful verification, the accounts app will redirect back to our * `/passkeys/finish` page. * + * @param accountsURL Base URL for the accounts app (provided to us by remote in + * the email or SRP verification response). + * * @param passkeySessionID An identifier provided by museum for this passkey * verification session. */ -export const passkeyVerificationRedirectURL = (passkeySessionID: string) => { +export const passkeyVerificationRedirectURL = ( + accountsURL: string | undefined, + passkeySessionID: string, +) => { const clientPackage = clientPackageName; // Using `window.location.origin` will work both when we're running in a web // browser, and in our desktop app. See: [Note: Using deeplinks to navigate @@ -49,7 +55,8 @@ export const passkeyVerificationRedirectURL = (passkeySessionID: string) => { redirect, ...recoverOption, }); - return `${accountsAppOrigin()}/passkeys/verify?${params.toString()}`; + const baseURL = accountsURL ?? accountsAppOrigin(); + return `${baseURL}/passkeys/verify?${params.toString()}`; }; interface OpenPasskeyVerificationURLOptions { diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index f16e1d91e7..ca85ceec1a 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -17,6 +17,11 @@ export interface UserVerificationResponse { encryptedToken?: string | undefined; token?: string; twoFactorSessionID?: string | undefined; + /** + * Base URL for the accounts app where we should redirect to for passkey + * verification. + */ + accountsUrl?: string | undefined; passkeySessionID?: string | undefined; /** * If both passkeys and TOTP based two factors are enabled, then {@link @@ -153,8 +158,8 @@ export const EmailOrSRPAuthorizationResponse = z.object({ encryptedToken: z.string().nullish().transform(nullToUndefined), token: z.string().nullish().transform(nullToUndefined), passkeySessionID: z.string().nullish().transform(nullToUndefined), - // The baseURL of the accounts app, where we should redirect to for passkey - // validation. + // Base URL for the accounts app where we should redirect to for passkey + // verification. accountsUrl: z.string().nullish().transform(nullToUndefined), twoFactorSessionID: z.string().nullish().transform(nullToUndefined), // TwoFactorSessionIDV2 is only set if user has both passkey and two factor