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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
e02e1aab10
Коммит
a09bee609a
@@ -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
Обычный файл
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;
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user