diff --git a/apps/web/src/connectors/common.ts b/apps/web/src/connectors/common.ts index ace3dce9daa..e9c7a9515b4 100644 --- a/apps/web/src/connectors/common.ts +++ b/apps/web/src/connectors/common.ts @@ -40,7 +40,7 @@ function appLinkHost(): string { return "bitwarden.com"; } -export function buildMobileCallbackUriFromParam(kind: "duo" | "webauthn"): string { +export function buildMobileDeeplinkUriFromParam(kind: "duo" | "webauthn"): string { const scheme = (getQsParam("deeplinkScheme") || "").toLowerCase(); const path = `${kind}-callback`; if (scheme === "https") { diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts index 1b174dbf7c2..838db9b4cef 100644 --- a/apps/web/src/connectors/duo-redirect.ts +++ b/apps/web/src/connectors/duo-redirect.ts @@ -1,4 +1,4 @@ -import { buildMobileCallbackUriFromParam, getQsParam } from "./common"; +import { buildMobileDeeplinkUriFromParam, getQsParam } from "./common"; import { TranslationService } from "./translation.service"; const mobileDesktopCallback = "bitwarden://duo-callback"; @@ -40,7 +40,7 @@ window.addEventListener("load", async () => { displayHandoffMessage(client); } else if (client === "mobile") { document.location.replace( - buildMobileCallbackUriFromParam("duo") + + buildMobileDeeplinkUriFromParam("duo") + "?code=" + encodeURIComponent(code) + "&state=" + diff --git a/apps/web/src/connectors/webauthn.ts b/apps/web/src/connectors/webauthn.ts index 47509ade340..03c86cfa185 100644 --- a/apps/web/src/connectors/webauthn.ts +++ b/apps/web/src/connectors/webauthn.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { b64Decode, buildMobileCallbackUriFromParam, getQsParam } from "./common"; +import { b64Decode, getQsParam } from "./common"; import { buildDataString, parseWebauthnJson } from "./common-webauthn"; let parsed = false; @@ -11,7 +11,7 @@ let btnAwaitingInteractionText: string = null; let btnReturnText: string = null; let parentUrl: string = null; let parentOrigin: string = null; -let mobileResponse = false; +let callbackUri: string = null; let stopWebAuthn = false; let sentSuccess = false; let obj: any = null; @@ -81,21 +81,8 @@ function parseParameters() { return; } - // Determine if this is a mobile-initiated flow via query param - const client: string | null = getQsParam("client"); - - if (client === "mobile") { - mobileResponse = true; - } - parentUrl = getQsParam("parent"); - if (!parentUrl) { - // In non-mobile flows we must have a parent for postMessage handoff - if (!mobileResponse) { - error("No parent."); - return; - } - } else { + if (parentUrl) { parentUrl = decodeURIComponent(parentUrl); parentOrigin = new URL(parentUrl).origin; } @@ -107,6 +94,13 @@ function parseParameters() { } else { parseParametersV2(); } + + // Require at least one return mechanism + if (!parentUrl && !callbackUri) { + error("No return target provided."); + return; + } + parsed = true; } @@ -131,7 +125,6 @@ function parseParametersV2() { btnText: string; btnReturnText: string; callbackUri?: string; - mobile?: boolean; } = null; try { dataObj = JSON.parse(b64Decode(getQsParam("data"))); @@ -142,8 +135,8 @@ function parseParametersV2() { return; } - // Treat presence of callbackUri/mobile in payload as mobile, or preserve existing mobileResponse (e.g., set by client=mobile) - mobileResponse = mobileResponse || dataObj.callbackUri != null || dataObj.mobile === true; + // Use optional callbackUri to indicate deep-link return; otherwise we will use postMessage to parent + callbackUri = dataObj.callbackUri ?? null; webauthnJson = dataObj.data; headerText = dataObj.headerText; btnText = dataObj.btnText; @@ -184,8 +177,8 @@ function start() { stopWebAuthn = false; if ( - mobileResponse || - (navigator.userAgent.indexOf(" Safari/") !== -1 && navigator.userAgent.indexOf("Chrome") === -1) + navigator.userAgent.indexOf(" Safari/") !== -1 && + navigator.userAgent.indexOf("Chrome") === -1 ) { // Safari and mobile chrome blocks non-user initiated WebAuthn requests. } else { @@ -208,7 +201,7 @@ function onMessage() { window.addEventListener( "message", (event) => { - if (!event.origin || event.origin === "" || event.origin !== parentOrigin) { + if (parentOrigin && (!event.origin || event.origin === "" || event.origin !== parentOrigin)) { return; } @@ -224,11 +217,11 @@ function onMessage() { } function error(message: string) { - if (mobileResponse) { - const callbackUri = buildMobileCallbackUriFromParam("webauthn"); - document.location.replace(callbackUri + "?error=" + encodeURIComponent(message)); - returnButton(callbackUri + "?error=" + encodeURIComponent(message)); - } else { + if (callbackUri) { + const uri = callbackUri + "?error=" + encodeURIComponent(message); + document.location.replace(uri); + returnButton(uri); + } else if (parentUrl) { parent.postMessage("error|" + message, parentUrl); setDefaultWebAuthnButtonState(); } @@ -241,18 +234,18 @@ function success(assertedCredential: PublicKeyCredential) { const dataString = buildDataString(assertedCredential); - if (mobileResponse) { - const callbackUri = buildMobileCallbackUriFromParam("webauthn"); - document.location.replace(callbackUri + "?data=" + encodeURIComponent(dataString)); - returnButton(callbackUri + "?data=" + encodeURIComponent(dataString)); - } else { + if (callbackUri) { + const uri = callbackUri + "?data=" + encodeURIComponent(dataString); + document.location.replace(uri); + returnButton(uri); + } else if (parentUrl) { parent.postMessage("success|" + dataString, parentUrl); sentSuccess = true; } } function info(message: string) { - if (mobileResponse) { + if (!parentUrl) { return; }