feat: support SSO while embedded (#31002)

* feat: send a message to the parent window if embeddd

* feat: handle embedded sso flows

* fix: use new `isEmbedded` logic

* fix: postMessage targetOrigin

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Felipe Martin
2025-05-23 16:18:05 +00:00
коммит произвёл GitHub
родитель e02e1aab10
Коммит a09bee609a
2 изменённых файлов: 71 добавлений и 6 удалений

Просмотреть файл

@@ -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: <LoginGitlabIcon/>,
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: <LoginGoogleIcon/>,
label: formatMessage({id: 'login.google', defaultMessage: 'Google'}),
onClick: desktopExternalAuth(url),
onClick: handleExternalAuth(url, 'google'),
});
}
@@ -186,7 +187,7 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
url,
icon: <EntraIdIcon/>,
label: formatMessage({id: 'login.office365', defaultMessage: 'Entra ID'}),
onClick: desktopExternalAuth(url),
onClick: handleExternalAuth(url, 'office365'),
});
}
@@ -198,7 +199,7 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
icon: <LoginOpenIDIcon/>,
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: <LockIcon/>,
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<boolean>((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);
}
});
}
};
};

17
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;
}
}