Skip to content

Commit

Permalink
Use local server for auth so that a completion page can be shown.
Browse files Browse the repository at this point in the history
  • Loading branch information
Rachel Macfarlane committed Nov 22, 2019
1 parent 17ce94c commit 6b78be0
Show file tree
Hide file tree
Showing 7 changed files with 392 additions and 106 deletions.
100 changes: 100 additions & 0 deletions src/vs/platform/auth/common/auth.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

html {
height: 100%;
}

body {
box-sizing: border-box;
min-height: 100%;
margin: 0;
padding: 15px 30px;
display: flex;
flex-direction: column;
color: white;
font-family: "Segoe UI","Helvetica Neue","Helvetica",Arial,sans-serif;
background-color: #373277;
}

.branding {
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGRlZnM+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtmaWxsOiNmNmY2ZjY7b3BhY2l0eTowO30uaWNvbi13aGl0ZXtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5CcmFuZFZpc3VhbFN0dWRpb0NvZGUyMDE3UlRXXzI0eF93aGl0ZV8yNHg8L3RpdGxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTI0LDBWMjRIMFYwWiIvPjxwYXRoIGNsYXNzPSJpY29uLXdoaXRlIiBkPSJNMjQsMi41VjIxLjVMMTgsMjQsMCwxOC41di0uNTYxbDE4LDEuNTQ1VjBaTTEsMTMuMTExLDQuMzg1LDEwLDEsNi44ODlsMS40MTgtLjgyN0w1Ljg1Myw4LjY1LDEyLDNsMywxLjQ1NlYxNS41NDRMMTIsMTcsNS44NTMsMTEuMzUsMi40MTksMTMuOTM5Wk03LjY0NCwxMCwxMiwxMy4yODNWNi43MTdaIi8+PC9zdmc+");
background-size: 24px;
background-repeat: no-repeat;
background-position: left 50%;
padding-left: 36px;
font-size: 20px;
letter-spacing: -0.04rem;
font-weight: 400;
color: white;
text-decoration: none;
}

.message-container {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
margin: 0 30px;
}

.message {
font-weight: 300;
font-size: 1.3rem;
}

body.error .message {
display: none;
}

body.error .error-message {
display: block;
}

.error-message {
display: none;
font-weight: 300;
font-size: 1.3rem;
}

.error-text {
color: red;
font-size: 1rem;
}

@font-face {
font-family: 'Segoe UI';
src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.eot?#iefix") format("embedded-opentype");
src: local("Segoe UI Light"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.woff2") format("woff2"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.svg#web") format("svg");
font-weight: 200
}

@font-face {
font-family: 'Segoe UI';
src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.eot?#iefix") format("embedded-opentype");
src: local("Segoe UI Semilight"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.woff2") format("woff2"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.svg#web") format("svg");
font-weight: 300
}

@font-face {
font-family: 'Segoe UI';
src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.eot?#iefix") format("embedded-opentype");
src: local("Segoe UI"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.woff2") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.svg#web") format("svg");
font-weight: 400
}

@font-face {
font-family: 'Segoe UI';
src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.eot?#iefix") format("embedded-opentype");
src: local("Segoe UI Semibold"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.woff2") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.svg#web") format("svg");
font-weight: 600
}

@font-face {
font-family: 'Segoe UI';
src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.eot?#iefix") format("embedded-opentype");
src: local("Segoe UI Bold"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.woff2") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.svg#web") format("svg");
font-weight: 700
}
35 changes: 35 additions & 0 deletions src/vs/platform/auth/common/auth.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Azure Account - Sign In</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="auth.css" />
</head>
<body>
<a class="branding" href="https://code.visualstudio.com/">
Visual Studio Code
</a>
<div class="message-container">
<div class="message">
You are signed in now and can close this page.
</div>
<div class="error-message">
An error occurred while signing in:
<div class="error-text"></div>
</div>
</div>
<script>
var search = window.location.search;
var error = (/[?&^]error=([^&]+)/.exec(search) || [])[1];
if (error) {
document.querySelector('.error-text')
.textContent = decodeURIComponent(error);
document.querySelector('body')
.classList.add('error');
}
</script>
</body>
</html>
2 changes: 1 addition & 1 deletion src/vs/platform/auth/common/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ export interface IAuthTokenService {

getToken(): Promise<string | undefined>;
refreshToken(): Promise<void>;
login(callbackUri?: URI): Promise<void>;
login(): Promise<void>;
logout(): Promise<void>;
}
5 changes: 1 addition & 4 deletions src/vs/platform/auth/common/authTokenIpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,8 @@ export class AuthTokenChannel implements IServerChannel {
switch (command) {
case '_getInitialStatus': return Promise.resolve(this.service.status);
case 'getToken': return this.service.getToken();
case 'exchangeCodeForToken':
this.service._onDidGetCallback.fire(args);
return Promise.resolve();
case 'refreshToken': return this.service.refreshToken();
case 'login': return this.service.login(args);
case 'login': return this.service.login();
case 'logout': return this.service.logout();
}
throw new Error('Invalid call');
Expand Down
162 changes: 162 additions & 0 deletions src/vs/platform/auth/electron-browser/authServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as http from 'http';
import * as url from 'url';
import * as fs from 'fs';
import * as net from 'net';
import { getPathFromAmdModule } from 'vs/base/common/amd';

interface Deferred<T> {
resolve: (result: T | Promise<T>) => void;
reject: (reason: any) => void;
}

export function createTerminateServer(server: http.Server) {
const sockets: Record<number, net.Socket> = {};
let socketCount = 0;
server.on('connection', socket => {
const id = socketCount++;
sockets[id] = socket;
socket.on('close', () => {
delete sockets[id];
});
});
return async () => {
const result = new Promise<void>(resolve => server.close(resolve));
for (const id in sockets) {
sockets[id].destroy();
}
return result;
};
}

export async function startServer(server: http.Server): Promise<string> {
let portTimer: NodeJS.Timer;

function cancelPortTimer() {
clearTimeout(portTimer);
}

const port = new Promise<string>((resolve, reject) => {
portTimer = setTimeout(() => {
reject(new Error('Timeout waiting for port'));
}, 5000);

server.on('listening', () => {
const address = server.address();
if (typeof address === 'string') {
resolve(address);
} else {
resolve(address.port.toString());
}
});

server.on('error', err => {
reject(err);
});

server.on('close', () => {
reject(new Error('Closed'));
});

server.listen(0);

This comment has been minimized.

Copy link
@chrmarti

chrmarti May 21, 2021

Collaborator

@RMacfarlane Any need to listen on all network interfaces? Can this be limited to just the local network interface (by adding '127.0.0.1' as the second argument)? /cc @TylerLeonhardt

This comment has been minimized.

Copy link
@TylerLeonhardt

TylerLeonhardt May 21, 2021

Member

This should be fine. The server is only used in local scenarios and in that case the port doesn't need to listen on all network interfaces.

This comment has been minimized.

Copy link
@TylerLeonhardt

TylerLeonhardt Jun 10, 2021

Member

@chrmarti was this causing an issue? Just curious why you brought it up.

This comment has been minimized.

Copy link
@chrmarti

chrmarti Jun 11, 2021

Collaborator

@TylerLeonhardt A bug report for Remote-Containers made me search for these cases in our main repo. This would expose the port to the local network (and potentially beyond) depending on the local firewall settings.

This comment has been minimized.

Copy link
@TylerLeonhardt

TylerLeonhardt Jul 6, 2021

Member

Added this in f4e48a9

});

port.then(cancelPortTimer, cancelPortTimer);
return port;
}

function sendFile(res: http.ServerResponse, filepath: string, contentType: string) {
fs.readFile(filepath, (err, body) => {
if (err) {
console.error(err);
} else {
res.writeHead(200, {
'Content-Length': body.length,
'Content-Type': contentType
});
res.end(body);
}
});
}

async function callback(nonce: string, reqUrl: url.Url): Promise<string> {
const query = reqUrl.query;
if (!query || typeof query === 'string') {
throw new Error('No query received.');
}

let error = query.error_description || query.error;

if (!error) {
const state = (query.state as string) || '';
const receivedNonce = (state.split(',')[1] || '').replace(/ /g, '+');
if (receivedNonce !== nonce) {
error = 'Nonce does not match.';
}
}

const code = query.code as string;
if (!error && code) {
return code;
}

throw new Error((error as string) || 'No code received.');
}

export function createServer(nonce: string) {
type RedirectResult = { req: http.IncomingMessage; res: http.ServerResponse; } | { err: any; res: http.ServerResponse; };
let deferredRedirect: Deferred<RedirectResult>;
const redirectPromise = new Promise<RedirectResult>((resolve, reject) => deferredRedirect = { resolve, reject });

type CodeResult = { code: string; res: http.ServerResponse; } | { err: any; res: http.ServerResponse; };
let deferredCode: Deferred<CodeResult>;
const codePromise = new Promise<CodeResult>((resolve, reject) => deferredCode = { resolve, reject });

const codeTimer = setTimeout(() => {
deferredCode.reject(new Error('Timeout waiting for code'));
}, 5 * 60 * 1000);

function cancelCodeTimer() {
clearTimeout(codeTimer);
}

const server = http.createServer(function (req, res) {
const reqUrl = url.parse(req.url!, /* parseQueryString */ true);
switch (reqUrl.pathname) {
case '/signin':
const receivedNonce = ((reqUrl.query.nonce as string) || '').replace(/ /g, '+');
if (receivedNonce === nonce) {
deferredRedirect.resolve({ req, res });
} else {
const err = new Error('Nonce does not match.');
deferredRedirect.resolve({ err, res });
}
break;
case '/':
sendFile(res, getPathFromAmdModule(require, '../common/auth.html'), 'text/html; charset=utf-8');
break;
case '/auth.css':
sendFile(res, getPathFromAmdModule(require, '../common/auth.css'), 'text/css; charset=utf-8');
break;
case '/callback':
deferredCode.resolve(callback(nonce, reqUrl)
.then(code => ({ code, res }), err => ({ err, res })));
break;
default:
res.writeHead(404);
res.end();
break;
}
});

codePromise.then(cancelCodeTimer, cancelCodeTimer);
return {
server,
redirectPromise,
codePromise
};
}
Loading

0 comments on commit 6b78be0

Please sign in to comment.