From f293a94b766d5a17a55ac26c80085e780c53a5fc Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 10 Mar 2017 10:52:31 +0100 Subject: [PATCH 1/4] :sparkles: handle proxy authentication --- src/vs/code/electron-main/auth.html | 123 ++++++++++++++++++++++++++++ src/vs/code/electron-main/auth.ts | 74 +++++++++++++++++ src/vs/code/electron-main/main.ts | 8 ++ 3 files changed, 205 insertions(+) create mode 100644 src/vs/code/electron-main/auth.html create mode 100644 src/vs/code/electron-main/auth.ts diff --git a/src/vs/code/electron-main/auth.html b/src/vs/code/electron-main/auth.html new file mode 100644 index 00000000000000..207f72194383e4 --- /dev/null +++ b/src/vs/code/electron-main/auth.html @@ -0,0 +1,123 @@ + + + + + + + + + + +

Authentication Required

+
+

+
+

+

+

+ + +

+
+
+ + + + + \ No newline at end of file diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts new file mode 100644 index 00000000000000..7c522cf39ea695 --- /dev/null +++ b/src/vs/code/electron-main/auth.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { localize } from 'vs/nls'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IWindowsMainService } from 'vs/code/electron-main/windows'; +import { fromEventEmitter } from 'vs/base/node/event'; +import { BrowserWindow, app } from 'electron'; + +type LoginEvent = { + event: Electron.Event; + webContents: Electron.WebContents; + req: Electron.LoginRequest; + authInfo: Electron.LoginAuthInfo; + cb: (username: string, password: string) => void; +}; + +export class AuthHandler { + + _serviceBrand: any; + + private disposables: IDisposable[] = []; + + constructor( + @IWindowsMainService private windowsService: IWindowsMainService + ) { + const onLogin = fromEventEmitter(app, 'login', (event, webContents, req, authInfo, cb) => ({ event, webContents, req, authInfo, cb })); + onLogin(this.onLogin, this, this.disposables); + } + + private onLogin({ event, authInfo, cb }: LoginEvent): void { + const opts: any = { + alwaysOnTop: true, + skipTaskbar: true, + resizable: false, + width: 450, + height: 260, + show: true, + title: localize('authRequired', "Authentication Required") + }; + + const focusedWindow = this.windowsService.getFocusedWindow(); + + if (focusedWindow) { + opts.parent = focusedWindow.win; + opts.modal = true; + } + + const win = new BrowserWindow(opts); + + const config = {}; + + const baseUrl = require.toUrl('./auth.html'); + const url = `${baseUrl}?config=${encodeURIComponent(JSON.stringify(config))}`; + win.loadURL(url); + + const proxyUrl = `${authInfo.host}:${authInfo.port}`; + const message = localize('proxyauth', "The proxy {0} requires a username and password.", proxyUrl); + + event.preventDefault(); + win.webContents.executeJavaScript('promptForCredentials(' + JSON.stringify({ message }) + ')', true).then(({ username, password }: { username: string, password: string }) => { + cb(username, password); + win.close(); + }); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} \ No newline at end of file diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 8936a0baad0004..e2781aee76952c 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -8,6 +8,7 @@ import { app, ipcMain as ipc, BrowserWindow } from 'electron'; import { assign } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { parseMainProcessArgv } from 'vs/platform/environment/node/argv'; import { mkdirp } from 'vs/base/node/pfs'; import { validatePaths } from 'vs/code/electron-main/paths'; @@ -56,6 +57,7 @@ import { resolveCommonProperties, machineIdStorageKey, machineIdIpcChannel } fro import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import product from 'vs/platform/node/product'; import pkg from 'vs/platform/node/package'; +import { AuthHandler } from './auth'; import * as fs from 'original-fs'; @@ -221,6 +223,8 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo app.setAppUserModelId(product.win32AppUserModelId); } + const disposables: IDisposable[] = []; + function dispose() { if (mainIpcServer) { mainIpcServer.dispose(); @@ -233,6 +237,7 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo configurationService.dispose(); sharedProcess.dispose(); + disposables.forEach(d => d.dispose()); } // Dispose on app quit @@ -275,6 +280,9 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo // Start shared process here sharedProcess.spawn(); + + const authHandler = instantiationService2.createInstance(AuthHandler); + disposables.push(authHandler); }); } From 9396dd4bb8166586e57edb542582a2ef2065e930 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 13 Apr 2017 18:23:27 +0200 Subject: [PATCH 2/4] improve proxy auth dialog --- src/vs/code/electron-main/app.ts | 4 ++-- src/vs/code/electron-main/auth.html | 20 +++++++++++--------- src/vs/code/electron-main/auth.ts | 29 ++++++++++++++++++++++------- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 53762b3d899040..23470739b82fbc 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -44,7 +44,7 @@ import { resolveCommonProperties, machineIdStorageKey, machineIdIpcChannel } fro import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import product from 'vs/platform/node/product'; import pkg from 'vs/platform/node/package'; -import { AuthHandler } from './auth'; +import { ProxyAuthHandler } from './auth'; import { IDisposable, dispose } from "vs/base/common/lifecycle"; import { ConfigurationService } from "vs/platform/configuration/node/configurationService"; import { TPromise } from "vs/base/common/winjs.base"; @@ -157,7 +157,7 @@ export class VSCodeApplication { const appInstantiationService = this.initServices(); // Setup Auth Handler - const authHandler = appInstantiationService.createInstance(AuthHandler); + const authHandler = appInstantiationService.createInstance(ProxyAuthHandler); this.toDispose.push(authHandler); // Open Windows diff --git a/src/vs/code/electron-main/auth.html b/src/vs/code/electron-main/auth.html index 207f72194383e4..e27ec2fc02e25e 100644 --- a/src/vs/code/electron-main/auth.html +++ b/src/vs/code/electron-main/auth.html @@ -16,18 +16,18 @@ -webkit-user-select: none; user-select: none; } - + body { font-family: "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", sans-serif, "Droid Sans Fallback"; font-size: 10pt; background-color: #F3F3F3; } - + #main { box-sizing: border-box; padding: 10px; } - + h1 { margin: 0; padding: 10px 0; @@ -36,11 +36,11 @@ color: #f0f0f0; text-align: center; } - + #form { margin-top: 10px; } - + #username, #password { padding: 6px 10px; @@ -48,15 +48,15 @@ box-sizing: border-box; width: 100%; } - + #buttons { text-align: center; } - + p { margin: 6px 0; } - + input { font-family: "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", sans-serif, "Droid Sans Fallback" !important; } @@ -64,7 +64,7 @@ -

Authentication Required

+

@@ -87,6 +87,7 @@

Authentication Required

function promptForCredentials(data) { return new Promise((c, e) => { + const $title = document.getElementById('title'); const $username = document.getElementById('username'); const $password = document.getElementById('password'); const $form = document.getElementById('form'); @@ -113,6 +114,7 @@

Authentication Required

} }); + $title.textContent = data.title; $message.textContent = data.message; $username.focus(); }); diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index 7c522cf39ea695..27c4e6d2f1d4d0 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -19,10 +19,16 @@ type LoginEvent = { cb: (username: string, password: string) => void; }; -export class AuthHandler { +type Credentials = { + username: string; + password: string; +}; + +export class ProxyAuthHandler { _serviceBrand: any; + private retryCount = 0; private disposables: IDisposable[] = []; constructor( @@ -33,14 +39,22 @@ export class AuthHandler { } private onLogin({ event, authInfo, cb }: LoginEvent): void { + if (!authInfo.isProxy) { + return; + } + + if (this.retryCount++ > 1) { + return; + } + const opts: any = { alwaysOnTop: true, skipTaskbar: true, resizable: false, width: 450, - height: 260, + height: 220, show: true, - title: localize('authRequired', "Authentication Required") + title: 'VS Code' }; const focusedWindow = this.windowsService.getFocusedWindow(); @@ -51,18 +65,19 @@ export class AuthHandler { } const win = new BrowserWindow(opts); - const config = {}; - const baseUrl = require.toUrl('./auth.html'); const url = `${baseUrl}?config=${encodeURIComponent(JSON.stringify(config))}`; win.loadURL(url); const proxyUrl = `${authInfo.host}:${authInfo.port}`; - const message = localize('proxyauth', "The proxy {0} requires a username and password.", proxyUrl); + const title = localize('authRequire', "Proxy Authentication Required"); + const message = localize('proxyauth', "The proxy {0} requires authentication.", proxyUrl); + const data = { title, message }; + const javascript = 'promptForCredentials(' + JSON.stringify(data) + ')'; event.preventDefault(); - win.webContents.executeJavaScript('promptForCredentials(' + JSON.stringify({ message }) + ')', true).then(({ username, password }: { username: string, password: string }) => { + win.webContents.executeJavaScript(javascript, true).then(({ username, password }: Credentials) => { cb(username, password); win.close(); }); From bf7c48e9a76bc8aa45fc9211f997138cb309b412 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 13 Apr 2017 21:49:09 +0200 Subject: [PATCH 3/4] :bug: handle proxy auth window closing --- src/vs/code/electron-main/auth.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index 27c4e6d2f1d4d0..69b4e8dc51120e 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -77,8 +77,13 @@ export class ProxyAuthHandler { const javascript = 'promptForCredentials(' + JSON.stringify(data) + ')'; event.preventDefault(); + + const onWindowClose = () => cb('', ''); + win.on('close', onWindowClose); + win.webContents.executeJavaScript(javascript, true).then(({ username, password }: Credentials) => { cb(username, password); + win.removeListener('close', onWindowClose); win.close(); }); } From 2f3db87f7da3ac1c07b6b396b20645f58d6c3497 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 13 Apr 2017 21:53:45 +0200 Subject: [PATCH 4/4] :art: simple refactoring --- src/vs/code/electron-main/auth.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index 69b4e8dc51120e..11017dec9ecd58 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -47,6 +47,8 @@ export class ProxyAuthHandler { return; } + event.preventDefault(); + const opts: any = { alwaysOnTop: true, skipTaskbar: true, @@ -68,19 +70,16 @@ export class ProxyAuthHandler { const config = {}; const baseUrl = require.toUrl('./auth.html'); const url = `${baseUrl}?config=${encodeURIComponent(JSON.stringify(config))}`; - win.loadURL(url); - const proxyUrl = `${authInfo.host}:${authInfo.port}`; const title = localize('authRequire', "Proxy Authentication Required"); const message = localize('proxyauth', "The proxy {0} requires authentication.", proxyUrl); const data = { title, message }; const javascript = 'promptForCredentials(' + JSON.stringify(data) + ')'; - event.preventDefault(); - const onWindowClose = () => cb('', ''); win.on('close', onWindowClose); + win.loadURL(url); win.webContents.executeJavaScript(javascript, true).then(({ username, password }: Credentials) => { cb(username, password); win.removeListener('close', onWindowClose);