From e4490e0af5c76e2ab4cf960e7b2faf823df8438e Mon Sep 17 00:00:00 2001 From: Salah Benmoussati <51402489+sbenmoussati@users.noreply.github.com> Date: Mon, 3 Apr 2023 16:36:25 +0200 Subject: [PATCH] SDA-4120 Proxy authentication on Browser login flow (#1826) --- src/app/main-api-handler.ts | 118 +++++++++++++++++++------ src/renderer/components/basic-auth.tsx | 2 +- src/renderer/styles/basic-auth.less | 47 ++++++---- 3 files changed, 123 insertions(+), 44 deletions(-) diff --git a/src/app/main-api-handler.ts b/src/app/main-api-handler.ts index dfdecd8c..a4e14db0 100644 --- a/src/app/main-api-handler.ts +++ b/src/app/main-api-handler.ts @@ -72,6 +72,24 @@ const broadcastMessage = (method, data) => { const getBrowserLoginUrl = (pod: string) => `${pod}/login/sso/initsso?RelayState=${pod}/client-bff/device-login/index.html?callbackScheme=symphony&action=login`; const AUTH_STATUS_PATH = '/login/checkauth?type=user'; +interface IProxyDetails { + username: string; + password: string; + hostname: string; + retries: number; +} +const proxyDetails: IProxyDetails = { + username: '', + password: '', + hostname: '', + retries: 0, +}; + +let loginUrl = ''; +let formattedPodUrl = ''; +let credentialsPromise; +const credentialsPromiseRefHolder: { [key: string]: any } = {}; + /** * Handle API related ipc messages from renderers. Only messages from windows * we have created are allowed. @@ -368,7 +386,8 @@ ipcMain.on( } break; case apiCmds.unmaximizeMainWindow: - const mainWindow = windowHandler.getMainWindow(); + const mainWindow = + windowHandler.getMainWindow() as ICustomBrowserWindow; if (mainWindow && windowExists(mainWindow)) { if (mainWindow.isFullScreen()) { mainWindow.setFullScreen(false); @@ -402,38 +421,14 @@ ipcMain.on( ? userConfigURL : globalConfigURL; const { subdomain, domain, tld } = whitelistHandler.parseDomain(podUrl); - const formattedPodUrl = `https://${subdomain}.${domain}${tld}`; - const loginUrl = getBrowserLoginUrl(formattedPodUrl); + formattedPodUrl = `https://${subdomain}.${domain}${tld}`; + loginUrl = getBrowserLoginUrl(formattedPodUrl); logger.info( 'main-api-handler:', 'check if sso is enabled for the pod', formattedPodUrl, ); - const response = await fetch(`${formattedPodUrl}${AUTH_STATUS_PATH}`); - const authResponse = (await response.json()) as IAuthResponse; - logger.info('main-api-handler:', 'check auth response', authResponse); - if ( - arg.isBrowserLoginEnabled && - authResponse.authenticationType === 'sso' - ) { - logger.info( - 'main-api-handler:', - 'browser login is enabled - logging in', - loginUrl, - ); - await shell.openExternal(loginUrl); - } else { - logger.info( - 'main-api-handler:', - 'browser login is not enabled - loading main window with', - formattedPodUrl, - ); - const mainWebContents = windowHandler.getMainWebContents(); - if (mainWebContents && !mainWebContents.isDestroyed()) { - windowHandler.setMainWindowOrigin(formattedPodUrl); - mainWebContents.loadURL(formattedPodUrl); - } - } + loadPodUrl(); break; case apiCmds.setBroadcastMessage: if (swiftSearchInstance) { @@ -650,3 +645,70 @@ const logApiCallParams = (arg: any) => { break; } }; + +const loadPodUrl = (proxyLogin = false) => { + logger.info('loading pod URL. Proxy: ', proxyLogin); + let onLogin = {}; + if (proxyLogin) { + onLogin = { + async onLogin(authInfo) { + // this 'authInfo' is the one received by the 'login' event. See https://www.electronjs.org/docs/latest/api/client-request#event-login + proxyDetails.hostname = authInfo.host || authInfo.realm; + await credentialsPromise; + return Promise.resolve({ + username: proxyDetails.username, + password: proxyDetails.password, + }); + }, + }; + } + fetch(`${formattedPodUrl}${AUTH_STATUS_PATH}`, onLogin) + .then(async (response) => { + const authResponse = (await response.json()) as IAuthResponse; + logger.info('main-api-handler:', 'check auth response', authResponse); + if (authResponse.authenticationType === 'sso') { + logger.info( + 'main-api-handler:', + 'browser login is enabled - logging in', + loginUrl, + ); + await shell.openExternal(loginUrl); + } else { + logger.info( + 'main-api-handler:', + 'browser login is not enabled - loading main window with', + formattedPodUrl, + ); + const mainWebContents = windowHandler.getMainWebContents(); + if (mainWebContents && !mainWebContents.isDestroyed()) { + windowHandler.setMainWindowOrigin(formattedPodUrl); + mainWebContents.loadURL(formattedPodUrl); + } + } + }) + .catch(async (error) => { + if ( + (error.type === 'proxy' && error.code === 'PROXY_AUTH_FAILED') || + (error.code === 'ERR_TOO_MANY_RETRIES' && proxyLogin) + ) { + credentialsPromise = new Promise((res, _rej) => { + credentialsPromiseRefHolder.resolutionCallback = res; + }); + const welcomeWindow = + windowHandler.getMainWindow() as ICustomBrowserWindow; + windowHandler.createBasicAuthWindow( + welcomeWindow, + proxyDetails.hostname, + proxyDetails.retries === 0, + undefined, + (username, password) => { + proxyDetails.username = username; + proxyDetails.password = password; + credentialsPromiseRefHolder.resolutionCallback(true); + loadPodUrl(true); + }, + ); + proxyDetails.retries += 1; + } + }); +}; diff --git a/src/renderer/components/basic-auth.tsx b/src/renderer/components/basic-auth.tsx index 4ae5ed9e..41955a6f 100644 --- a/src/renderer/components/basic-auth.tsx +++ b/src/renderer/components/basic-auth.tsx @@ -62,7 +62,7 @@ export default class BasicAuth extends React.Component<{}, IState> { BASIC_AUTH_NAMESPACE, )()} - {hostname} + {hostname && {hostname}} {i18n.t('Invalid user name/password', BASIC_AUTH_NAMESPACE)()} diff --git a/src/renderer/styles/basic-auth.less b/src/renderer/styles/basic-auth.less index 9d30413a..addf36d1 100644 --- a/src/renderer/styles/basic-auth.less +++ b/src/renderer/styles/basic-auth.less @@ -1,4 +1,4 @@ -@import "theme"; +@import 'theme'; @color-red: red; @@ -10,16 +10,22 @@ html { body { margin: 0; - height: 100%; + height: calc(100% - 40px); + margin: 20px; font-size: 14px; } +.components-window-root { + height: 100%; +} + .container { display: flex; flex-direction: column; font-family: @font-family; text-align: center; - padding: 20px; + height: 100%; + justify-content: space-evenly; } .container:lang(ja-JP) { @@ -27,12 +33,11 @@ body { } span { - padding-top: 10px; text-align: start; } .hostname { - font-size: .9em; + font-size: 0.9em; } .credentials-error { @@ -44,28 +49,40 @@ span { display: block; } -form { - padding-top: 15px; -} - table { border-spacing: 5px; + display: flex; + tbody { + display: flex; + flex-direction: column; + width: 100%; + tr { + display: flex; + justify-content: space-between; + text-align: left; + td:last-child { + margin-left: 8px; + display: flex; + flex: 1; + justify-content: flex-end; + } + } + } } input { - width: 200px; + width: calc(100% - 8px); height: 20px; display: inline-block; -} - -button { - width: 80px; + &:lang(fr-FR) { + max-width: 180px; + } } .footer { display: flex; text-align: center; - padding-top: 25px; + padding-top: 20px; } .button-container {