diff --git a/apps/meteor/app/iframe-login/client/iframe_client.ts b/apps/meteor/app/iframe-login/client/iframe_client.ts deleted file mode 100644 index 30a52e855f12d..0000000000000 --- a/apps/meteor/app/iframe-login/client/iframe_client.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Accounts } from 'meteor/accounts-base'; - -import { IframeLogin } from '../../ui-utils/client'; - -const iframeLogin = new IframeLogin(); - -const { _unstoreLoginToken } = Accounts; -Accounts._unstoreLoginToken = function (...args) { - iframeLogin.tryLogin(); - _unstoreLoginToken.apply(Accounts, args); -}; - -window.addEventListener('message', (e) => { - if (!(typeof e.data === 'function' || (typeof e.data === 'object' && !!e.data))) { - return; - } - - switch (e.data.event) { - case 'try-iframe-login': - iframeLogin.tryLogin((error) => { - if (error) { - e.source?.postMessage( - { - event: 'login-error', - response: error.message, - }, - { targetOrigin: e.origin }, - ); - } - }); - break; - - case 'login-with-token': - iframeLogin.loginWithToken(e.data, (error) => { - if (error) { - e.source?.postMessage( - { - event: 'login-error', - response: error.message, - }, - { targetOrigin: e.origin }, - ); - } - }); - break; - } -}); diff --git a/apps/meteor/app/iframe-login/client/index.ts b/apps/meteor/app/iframe-login/client/index.ts deleted file mode 100644 index db3f3000d4e38..0000000000000 --- a/apps/meteor/app/iframe-login/client/index.ts +++ /dev/null @@ -1 +0,0 @@ -import './iframe_client'; diff --git a/apps/meteor/app/ui-utils/client/index.ts b/apps/meteor/app/ui-utils/client/index.ts index 7fdb76e73d4ea..5912c38dc7d7a 100644 --- a/apps/meteor/app/ui-utils/client/index.ts +++ b/apps/meteor/app/ui-utils/client/index.ts @@ -2,5 +2,4 @@ export { messageBox } from './lib/messageBox'; export { LegacyRoomManager } from './lib/LegacyRoomManager'; export { upsertMessage, RoomHistoryManager } from './lib/RoomHistoryManager'; export { mainReady } from './lib/mainReady'; -export { IframeLogin } from './lib/IframeLogin'; export { MessageTypes, MessageType } from '../lib/MessageTypes'; diff --git a/apps/meteor/app/ui-utils/client/lib/IframeLogin.ts b/apps/meteor/app/ui-utils/client/lib/IframeLogin.ts deleted file mode 100644 index 160ebba96747d..0000000000000 --- a/apps/meteor/app/ui-utils/client/lib/IframeLogin.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { Accounts } from 'meteor/accounts-base'; -import { Match } from 'meteor/check'; -import { HTTP } from 'meteor/http'; -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Tracker } from 'meteor/tracker'; - -import { settings } from '../../../settings/client'; - -export class IframeLogin { - private enabled = false; - - public reactiveEnabled = new ReactiveVar(false); - - public reactiveIframeUrl = new ReactiveVar(undefined); - - private iframeUrl: string | undefined; - - private apiUrl: string | undefined; - - private apiMethod: string | undefined; - - constructor() { - Tracker.autorun((c) => { - this.enabled = settings.get('Accounts_iframe_enabled'); - this.reactiveEnabled.set(this.enabled); - - this.iframeUrl = settings.get('Accounts_iframe_url'); - this.apiUrl = settings.get('Accounts_Iframe_api_url'); - this.apiMethod = settings.get('Accounts_Iframe_api_method'); - - if (this.enabled === false) { - return c.stop(); - } - - if (this.enabled === true && this.iframeUrl && this.apiUrl && this.apiMethod) { - c.stop(); - if (!Accounts._storedLoginToken()) { - this.tryLogin(); - } - } - }); - } - - tryLogin(callback?: (error: Meteor.Error | Meteor.TypedError | Error | null | undefined, result: unknown) => void) { - if (!this.enabled) { - return; - } - - if (!this.iframeUrl || !this.apiUrl || !this.apiMethod) { - return; - } - - console.log('tryLogin'); - const options = { - beforeSend: (xhr: XMLHttpRequest) => { - xhr.withCredentials = true; - }, - }; - - let { iframeUrl } = this; - let separator = '?'; - if (iframeUrl.indexOf('?') > -1) { - separator = '&'; - } - - if (navigator.userAgent.indexOf('Electron') > -1) { - iframeUrl += `${separator}client=electron`; - } - - HTTP.call(this.apiMethod, this.apiUrl, options, (error, result) => { - console.log(error, result); - if (result?.data && (result.data.token || result.data.loginToken)) { - this.loginWithToken(result.data, (error: Meteor.Error | Meteor.TypedError | Error | null | undefined) => { - if (error) { - this.reactiveIframeUrl.set(iframeUrl); - } else { - this.reactiveIframeUrl.set(undefined); - } - callback?.(error, result); - }); - } else { - this.reactiveIframeUrl.set(iframeUrl); - callback?.(error, result); - } - }); - } - - loginWithToken( - tokenData: string | { loginToken: string } | { token: string }, - callback?: (error: Meteor.Error | Meteor.TypedError | Error | null | undefined) => void, - ) { - if (!this.enabled) { - return; - } - - if (Match.test(tokenData, String)) { - tokenData = { - token: tokenData, - }; - } - - console.log('loginWithToken'); - - if ('loginToken' in tokenData) { - return Meteor.loginWithToken(tokenData.loginToken, callback); - } - - Accounts.callLoginMethod({ - methodArguments: [ - { - iframe: true, - token: tokenData.token, - }, - ], - userCallback: callback, - }); - } -} - -export const iframeLogin = new IframeLogin(); diff --git a/apps/meteor/client/hooks/iframe/useIframe.ts b/apps/meteor/client/hooks/iframe/useIframe.ts new file mode 100644 index 0000000000000..78677d7c45421 --- /dev/null +++ b/apps/meteor/client/hooks/iframe/useIframe.ts @@ -0,0 +1,76 @@ +import { useLoginWithIframe, useLoginWithToken, useSetting } from '@rocket.chat/ui-contexts'; +import { useCallback, useState } from 'react'; + +export const useIframe = () => { + const [iframeLoginUrl, setIframeLoginUrl] = useState(undefined); + + const iframeEnabled = useSetting('Accounts_iframe_enabled', false); + const accountIframeUrl = useSetting('Accounts_iframe_url', ''); + const apiUrl = useSetting('Accounts_Iframe_api_url', ''); + const apiMethod = useSetting('Accounts_Iframe_api_method', ''); + + const iframeLogin = useLoginWithIframe(); + const tokenLogin = useLoginWithToken(); + + const loginWithToken = useCallback( + (tokenData: string | { loginToken: string } | { token: string }, callback?: (error: Error | null | undefined) => void) => { + if (typeof tokenData === 'string') { + tokenData = { + token: tokenData, + }; + } + if ('loginToken' in tokenData) { + tokenLogin(tokenData.loginToken); + } + if ('token' in tokenData) { + iframeLogin(tokenData.token, callback); + } + }, + [iframeLogin, tokenLogin], + ); + + const tryLogin = useCallback( + async (callback?: (error: Error | null | undefined, result: unknown) => void) => { + let url = accountIframeUrl; + let separator = '?'; + if (url.indexOf('?') > -1) { + separator = '&'; + } + + if (navigator.userAgent.indexOf('Electron') > -1) { + url += `${separator}client=electron`; + } + + const result = await fetch(apiUrl, { + method: apiMethod, + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }); + + if (!result.ok || result.status !== 200) { + setIframeLoginUrl(url); + callback?.(new Error(), null); + return; + } + + loginWithToken(await result.json(), async (error: Meteor.Error | Meteor.TypedError | Error | null | undefined) => { + if (error) { + setIframeLoginUrl(url); + } else { + setIframeLoginUrl(undefined); + } + callback?.(error, await result.json()); + }); + }, + [apiMethod, apiUrl, accountIframeUrl, loginWithToken], + ); + + return { + enabled: Boolean(iframeEnabled && accountIframeUrl && apiUrl && apiMethod), + tryLogin, + loginWithToken, + iframeLoginUrl, + }; +}; diff --git a/apps/meteor/client/hooks/iframe/useIframeLoginListener.ts b/apps/meteor/client/hooks/iframe/useIframeLoginListener.ts new file mode 100644 index 0000000000000..99b3575de3cf6 --- /dev/null +++ b/apps/meteor/client/hooks/iframe/useIframeLoginListener.ts @@ -0,0 +1,64 @@ +import { useUnstoreLoginToken } from '@rocket.chat/ui-contexts'; +import { useEffect } from 'react'; + +import { useIframe } from './useIframe'; + +export const useIframeLoginListener = () => { + const { enabled: iframeEnabled, tryLogin, loginWithToken } = useIframe(); + const unstoreLoginToken = useUnstoreLoginToken(); + + useEffect(() => { + if (!iframeEnabled) { + return; + } + tryLogin(); + }, [iframeEnabled, tryLogin]); + + useEffect(() => { + if (!iframeEnabled) { + return; + } + const messageListener = (e: MessageEvent) => { + if (!(typeof e.data === 'function' || (typeof e.data === 'object' && !!e.data))) { + return; + } + + switch (e.data.event) { + case 'try-iframe-login': + tryLogin((error) => { + if (error) { + e.source?.postMessage( + { + event: 'login-error', + response: error.message, + }, + { targetOrigin: e.origin }, + ); + } + }); + break; + + case 'login-with-token': + loginWithToken(e.data, (error) => { + if (error) { + e.source?.postMessage( + { + event: 'login-error', + response: error.message, + }, + { targetOrigin: e.origin }, + ); + } + }); + break; + } + }; + + window.addEventListener('message', messageListener); + return () => { + window.removeEventListener('message', messageListener); + }; + }, [iframeEnabled, loginWithToken, tryLogin]); + + useEffect(() => unstoreLoginToken(tryLogin), [tryLogin, unstoreLoginToken]); +}; diff --git a/apps/meteor/client/importPackages.ts b/apps/meteor/client/importPackages.ts index 556f77a4146ae..aede0a7479bd8 100644 --- a/apps/meteor/client/importPackages.ts +++ b/apps/meteor/client/importPackages.ts @@ -4,7 +4,6 @@ import '../app/autotranslate/client'; import '../app/emoji/client'; import '../app/emoji-emojione/client'; import '../app/gitlab/client'; -import '../app/iframe-login/client'; import '../app/license/client'; import '../app/lib/client'; import '../app/livechat-enterprise/client'; diff --git a/apps/meteor/client/providers/AuthenticationProvider/AuthenticationProvider.tsx b/apps/meteor/client/providers/AuthenticationProvider/AuthenticationProvider.tsx index 619fcf344c5c0..6d79d6457c45d 100644 --- a/apps/meteor/client/providers/AuthenticationProvider/AuthenticationProvider.tsx +++ b/apps/meteor/client/providers/AuthenticationProvider/AuthenticationProvider.tsx @@ -1,6 +1,7 @@ import type { LoginServiceConfiguration } from '@rocket.chat/core-typings'; import { capitalize } from '@rocket.chat/string-helpers'; import { AuthenticationContext, useSetting } from '@rocket.chat/ui-contexts'; +import { Accounts } from 'meteor/accounts-base'; import { Meteor } from 'meteor/meteor'; import type { ContextType, ReactElement, ReactNode } from 'react'; import { useMemo } from 'react'; @@ -14,6 +15,16 @@ type AuthenticationProviderProps = { children: ReactNode; }; +const callLoginMethod = ( + options: { loginToken?: string; token?: string; iframe?: boolean }, + userCallback: ((err?: any) => void) | undefined, +) => { + Accounts.callLoginMethod({ + methodArguments: [options], + userCallback, + }); +}; + const AuthenticationProvider = ({ children }: AuthenticationProviderProps): ReactElement => { const isLdapEnabled = useSetting('LDAP_Enable', false); const isCrowdEnabled = useSetting('CROWD_Enable', false); @@ -71,6 +82,38 @@ const AuthenticationProvider = ({ children }: AuthenticationProviderProps): Reac }); }); }, + loginWithIframe: (token: string, callback) => + new Promise((resolve, reject) => { + callLoginMethod({ iframe: true, token }, (error) => { + if (error) { + console.error(error); + callback?.(error); + return reject(error); + } + resolve(); + }); + }), + loginWithTokenRoute: (token: string, callback) => + new Promise((resolve, reject) => { + callLoginMethod({ token }, (error) => { + if (error) { + console.error(error); + callback?.(error); + return reject(error); + } + resolve(); + }); + }), + unstoreLoginToken: (callback) => { + const { _unstoreLoginToken } = Accounts; + Accounts._unstoreLoginToken = function (...args) { + callback(); + _unstoreLoginToken.apply(Accounts, args); + }; + return () => { + Accounts._unstoreLoginToken = _unstoreLoginToken; + }; + }, queryLoginServices: { getCurrentValue: () => loginServices.getLoginServiceButtons(), diff --git a/apps/meteor/client/views/root/AppLayout.tsx b/apps/meteor/client/views/root/AppLayout.tsx index e7afa4be398da..7cdb800700fa9 100644 --- a/apps/meteor/client/views/root/AppLayout.tsx +++ b/apps/meteor/client/views/root/AppLayout.tsx @@ -19,6 +19,7 @@ import { useGitLabAuth } from '../../../app/gitlab/client/hooks/useGitLabAuth'; import { useLivechatEnterprise } from '../../../app/livechat-enterprise/hooks/useLivechatEnterprise'; import { useNextcloud } from '../../../app/nextcloud/client/useNextcloud'; import { useTokenPassAuth } from '../../../app/tokenpass/client/hooks/useTokenPassAuth'; +import { useIframeLoginListener } from '../../hooks/iframe/useIframeLoginListener'; import { useNotificationPermission } from '../../hooks/notification/useNotificationPermission'; import { useAnalytics } from '../../hooks/useAnalytics'; import { useAnalyticsEventTracking } from '../../hooks/useAnalyticsEventTracking'; @@ -37,6 +38,7 @@ const AppLayout = () => { }; }, []); + useIframeLoginListener(); useMessageLinkClicks(); useGoogleTagManager(); useAnalytics(); diff --git a/apps/meteor/client/views/root/LoginTokenRoute.tsx b/apps/meteor/client/views/root/LoginTokenRoute.tsx index 3bce5901da64d..27eb4c5e9f5fa 100644 --- a/apps/meteor/client/views/root/LoginTokenRoute.tsx +++ b/apps/meteor/client/views/root/LoginTokenRoute.tsx @@ -1,23 +1,16 @@ -import { useRouter } from '@rocket.chat/ui-contexts'; -import { Accounts } from 'meteor/accounts-base'; +import { useLoginWithTokenRoute, useRouter } from '@rocket.chat/ui-contexts'; import { useEffect } from 'react'; const LoginTokenRoute = () => { const router = useRouter(); + const loginMethod = useLoginWithTokenRoute(); useEffect(() => { - Accounts.callLoginMethod({ - methodArguments: [ - { - loginToken: router.getRouteParameters().token, - }, - ], - userCallback(error) { - console.error(error); - router.navigate('/'); - }, + loginMethod(router.getRouteParameters().token, (error) => { + console.error(error); + router.navigate('/'); }); - }, [router]); + }, [loginMethod, router]); return null; }; diff --git a/apps/meteor/client/views/root/MainLayout/LoginPage.tsx b/apps/meteor/client/views/root/MainLayout/LoginPage.tsx index 5a197c706a4a2..2cab3afb1e209 100644 --- a/apps/meteor/client/views/root/MainLayout/LoginPage.tsx +++ b/apps/meteor/client/views/root/MainLayout/LoginPage.tsx @@ -4,13 +4,13 @@ import RegistrationRoute from '@rocket.chat/web-ui-registration'; import type { ReactElement, ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; -import { useIframeLogin } from './useIframeLogin'; import LoggedOutBanner from '../../../components/deviceManagement/LoggedOutBanner'; +import { useIframe } from '../../../hooks/iframe/useIframe'; const LoginPage = ({ defaultRoute, children }: { defaultRoute?: LoginRoutes; children?: ReactNode }): ReactElement => { const { t } = useTranslation(); const showForcedLogoutBanner = useSession('force_logout') as boolean | undefined; - const iframeLoginUrl = useIframeLogin(); + const { iframeLoginUrl } = useIframe(); if (iframeLoginUrl) { return