diff --git a/webapp/channels/src/components/login/login.tsx b/webapp/channels/src/components/login/login.tsx index e4a11ae4be..b8d736edf4 100644 --- a/webapp/channels/src/components/login/login.tsx +++ b/webapp/channels/src/components/login/login.tsx @@ -48,6 +48,7 @@ import PasswordInput from 'components/widgets/inputs/password_input/password_inp import Constants from 'utils/constants'; import DesktopApp from 'utils/desktop_api'; +import {isEmbedded} from 'utils/embed'; import {t} from 'utils/i18n'; import {showNotification} from 'utils/notifications'; import {isDesktopApp} from 'utils/user_agent'; @@ -164,7 +165,7 @@ const Login = ({onCustomizeHeader}: LoginProps) => { icon: , label: GitLabButtonText || formatMessage({id: 'login.gitlab', defaultMessage: 'GitLab'}), style: {color: GitLabButtonColor, borderColor: GitLabButtonColor}, - onClick: desktopExternalAuth(url), + onClick: handleExternalAuth(url, 'gitlab'), }); } @@ -175,7 +176,7 @@ const Login = ({onCustomizeHeader}: LoginProps) => { url, icon: , label: formatMessage({id: 'login.google', defaultMessage: 'Google'}), - onClick: desktopExternalAuth(url), + onClick: handleExternalAuth(url, 'google'), }); } @@ -186,7 +187,7 @@ const Login = ({onCustomizeHeader}: LoginProps) => { url, icon: , label: formatMessage({id: 'login.office365', defaultMessage: 'Entra ID'}), - onClick: desktopExternalAuth(url), + onClick: handleExternalAuth(url, 'office365'), }); } @@ -198,7 +199,7 @@ const Login = ({onCustomizeHeader}: LoginProps) => { icon: , label: OpenIdButtonText || formatMessage({id: 'login.openid', defaultMessage: 'Open ID'}), style: {color: OpenIdButtonColor, borderColor: OpenIdButtonColor}, - onClick: desktopExternalAuth(url), + onClick: handleExternalAuth(url, 'openid'), }); } @@ -209,21 +210,68 @@ const Login = ({onCustomizeHeader}: LoginProps) => { url, icon: , label: SamlLoginButtonText || formatMessage({id: 'login.saml', defaultMessage: 'SAML'}), - onClick: desktopExternalAuth(url), + onClick: handleExternalAuth(url, 'saml'), }); } return externalLoginOptions; }; - const desktopExternalAuth = (href: string) => { + const handleExternalAuth = (href: string, provider: string) => { return (event: React.MouseEvent) => { + // If the user is running the desktop app, we need to redirect them to the desktop login page if (isDesktopApp()) { event.preventDefault(); setDesktopLoginLink(href); history.push(`/login/desktop${search}`); } + + // If the user is running the app in an embedded view, we need send the parent window a message + // to continue the login process if the parent frame answers a message to confirm that is going to + // take care for the authentication process. + if (isEmbedded()) { + event.preventDefault(); + + // Create a promise that will resolve if the parent window responds + const messagePromise = new Promise((resolve) => { + // Set up a one-time event listener for the response + const messageHandler = (event: MessageEvent) => { + // Right now we are embedding from a plugin so let's just check the origin is the same as this server origin. + if (event.origin !== window.location.origin) { + return; + } + + if (event.data && event.data.type === 'mattermost_external_auth_login' && event.data.ack === true) { + window.removeEventListener('message', messageHandler); + resolve(true); + } + }; + + window.addEventListener('message', messageHandler); + + // Wait for at least one second for a response from the parent. + setTimeout(() => { + window.removeEventListener('message', messageHandler); + resolve(false); + }, 1000); + }); + + // Notify the parent + window.parent.postMessage({ + type: 'mattermost_external_auth_login', + provider, + href, + }, window.location.origin); + + // Wait for response or timeout, following with the usual authentication flow + messagePromise.then((received) => { + if (!received) { + // If the parent didn't respond, navigate to the href directly + history.push(href); + } + }); + } }; }; diff --git a/webapp/channels/src/utils/embed.tsx b/webapp/channels/src/utils/embed.tsx new file mode 100644 index 0000000000..c9c37d7517 --- /dev/null +++ b/webapp/channels/src/utils/embed.tsx @@ -0,0 +1,17 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +/** + * Checks if the application is running embedded in another platform + * This is determined by checking if window.self is not equal to window.parent. + * If accessing window.parent throws an error, it is assumed the app is in a cross-origin iframe. + * @returns True if the app is running embedded + */ +export function isEmbedded(): boolean { + try { + return window.self !== window.parent; + } catch (e) { + // If accessing window.parent throws an error, we're in a cross-origin iframe + return true; + } +}