mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 09:08:48 +01:00
Use local server for auth so that a completion page can be shown.
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
@@ -8,10 +8,11 @@ import * as https from 'https';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
|
||||
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { shell } from 'electron';
|
||||
import { createServer, startServer } from 'vs/platform/auth/electron-browser/authServer';
|
||||
|
||||
const SERVICE_NAME = 'VS Code';
|
||||
const ACCOUNT = 'MyAccount';
|
||||
@@ -23,14 +24,6 @@ const activeDirectoryResourceId = 'https://management.core.windows.net/';
|
||||
const clientId = 'aebc6443-996d-45c2-90f0-388ff96faa56';
|
||||
const tenantId = 'common';
|
||||
|
||||
function parseQuery(uri: URI) {
|
||||
return uri.query.split('&').reduce((prev: any, current) => {
|
||||
const queryString = current.split('=');
|
||||
prev[queryString[0]] = queryString[1];
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function toQuery(obj: any): string {
|
||||
return Object.keys(obj).map(key => `${key}=${obj[key]}`).join('&');
|
||||
}
|
||||
@@ -72,34 +65,63 @@ export class AuthTokenService extends Disposable implements IAuthTokenService {
|
||||
});
|
||||
}
|
||||
|
||||
public async login(callbackUri: URI): Promise<void> {
|
||||
public async login(): Promise<void> {
|
||||
this.setStatus(AuthTokenStatus.SigningIn);
|
||||
|
||||
const nonce = generateUuid();
|
||||
const port = (callbackUri.authority.match(/:([0-9]*)$/) || [])[1] || (callbackUri.scheme === 'https' || callbackUri.scheme === 'http' ? 443 : 80);
|
||||
const state = `${callbackUri.scheme},${port},${encodeURIComponent(nonce)},${encodeURIComponent(callbackUri.query)}`;
|
||||
const signInUrl = `${activeDirectoryEndpointUrl}${tenantId}/oauth2/authorize`;
|
||||
const { server, redirectPromise, codePromise } = createServer(nonce);
|
||||
|
||||
const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64'));
|
||||
const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64'));
|
||||
try {
|
||||
const port = await startServer(server);
|
||||
shell.openExternal(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`);
|
||||
|
||||
let uri = URI.parse(signInUrl);
|
||||
uri = uri.with({
|
||||
query: `response_type=code&client_id=${encodeURIComponent(clientId)}&redirect_uri=${redirectUrlAAD}&state=${encodeURIComponent(state)}&resource=${activeDirectoryResourceId}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`
|
||||
});
|
||||
const redirectReq = await redirectPromise;
|
||||
if ('err' in redirectReq) {
|
||||
const { err, res } = redirectReq;
|
||||
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` });
|
||||
res.end();
|
||||
throw err;
|
||||
}
|
||||
|
||||
await shell.openExternal(uri.toString(true));
|
||||
const host = redirectReq.req.headers.host || '';
|
||||
const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1];
|
||||
const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port;
|
||||
|
||||
const timeoutPromise = new Promise((resolve: (value: IToken) => void, reject) => {
|
||||
const wait = setTimeout(() => {
|
||||
this.setStatus(AuthTokenStatus.SignedOut);
|
||||
clearTimeout(wait);
|
||||
reject('Login timed out.');
|
||||
}, 1000 * 60 * 5);
|
||||
});
|
||||
const state = `${updatedPort},${encodeURIComponent(nonce)}`;
|
||||
const signInUrl = `${activeDirectoryEndpointUrl}${tenantId}/oauth2/authorize`;
|
||||
|
||||
const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64'));
|
||||
const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64'));
|
||||
|
||||
let uri = URI.parse(signInUrl);
|
||||
uri = uri.with({
|
||||
query: `response_type=code&client_id=${encodeURIComponent(clientId)}&redirect_uri=${redirectUrlAAD}&state=${encodeURIComponent(state)}&resource=${activeDirectoryResourceId}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`
|
||||
});
|
||||
|
||||
await redirectReq.res.writeHead(302, { Location: uri.toString(true) });
|
||||
redirectReq.res.end();
|
||||
|
||||
const codeRes = await codePromise;
|
||||
const res = codeRes.res;
|
||||
|
||||
try {
|
||||
if ('err' in codeRes) {
|
||||
throw codeRes.err;
|
||||
}
|
||||
const token = await this.exchangeCodeForToken(codeRes.code, codeVerifier);
|
||||
this.setToken(token);
|
||||
res.writeHead(302, { Location: '/' });
|
||||
res.end();
|
||||
} catch (err) {
|
||||
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` });
|
||||
res.end();
|
||||
}
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
server.close();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
return Promise.race([this.exchangeCodeForToken(clientId, tenantId, codeVerifier, state), timeoutPromise]).then(token => {
|
||||
this.setToken(token);
|
||||
});
|
||||
}
|
||||
|
||||
public getToken(): Promise<string | undefined> {
|
||||
@@ -120,71 +142,55 @@ export class AuthTokenService extends Disposable implements IAuthTokenService {
|
||||
this.setStatus(AuthTokenStatus.SignedIn);
|
||||
}
|
||||
|
||||
private async exchangeCodeForToken(clientId: string, tenantId: string, codeVerifier: string, state: string): Promise<IToken> {
|
||||
let uriEventListener: IDisposable;
|
||||
private exchangeCodeForToken(code: string, codeVerifier: string): Promise<IToken> {
|
||||
return new Promise((resolve: (value: IToken) => void, reject) => {
|
||||
uriEventListener = this.onDidGetCallback(async (uri: URI) => {
|
||||
try {
|
||||
const query = parseQuery(uri);
|
||||
const code = query.code;
|
||||
try {
|
||||
const postData = toQuery({
|
||||
grant_type: 'authorization_code',
|
||||
code: code,
|
||||
client_id: clientId,
|
||||
code_verifier: codeVerifier,
|
||||
redirect_uri: redirectUrlAAD
|
||||
});
|
||||
|
||||
if (query.state !== state) {
|
||||
return;
|
||||
const post = https.request({
|
||||
host: 'login.microsoftonline.com',
|
||||
path: `/${tenantId}/oauth2/token`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': postData.length
|
||||
}
|
||||
|
||||
const postData = toQuery({
|
||||
grant_type: 'authorization_code',
|
||||
code: code,
|
||||
client_id: clientId,
|
||||
code_verifier: codeVerifier,
|
||||
redirect_uri: redirectUrlAAD
|
||||
}, result => {
|
||||
const buffer: Buffer[] = [];
|
||||
result.on('data', (chunk: Buffer) => {
|
||||
buffer.push(chunk);
|
||||
});
|
||||
|
||||
const post = https.request({
|
||||
host: 'login.microsoftonline.com',
|
||||
path: `/${tenantId}/oauth2/token`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': postData.length
|
||||
result.on('end', () => {
|
||||
if (result.statusCode === 200) {
|
||||
const json = JSON.parse(Buffer.concat(buffer).toString());
|
||||
resolve({
|
||||
expiresIn: json.access_token,
|
||||
expiresOn: json.expires_on,
|
||||
accessToken: json.access_token,
|
||||
refreshToken: json.refresh_token
|
||||
});
|
||||
} else {
|
||||
reject(new Error('Bad!'));
|
||||
}
|
||||
}, result => {
|
||||
const buffer: Buffer[] = [];
|
||||
result.on('data', (chunk: Buffer) => {
|
||||
buffer.push(chunk);
|
||||
});
|
||||
result.on('end', () => {
|
||||
if (result.statusCode === 200) {
|
||||
const json = JSON.parse(Buffer.concat(buffer).toString());
|
||||
resolve({
|
||||
expiresIn: json.access_token,
|
||||
expiresOn: json.expires_on,
|
||||
accessToken: json.access_token,
|
||||
refreshToken: json.refresh_token
|
||||
});
|
||||
} else {
|
||||
reject(new Error('Bad!'));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
post.write(postData);
|
||||
post.write(postData);
|
||||
|
||||
post.end();
|
||||
post.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
post.end();
|
||||
post.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}).then(result => {
|
||||
uriEventListener.dispose();
|
||||
return result;
|
||||
}).catch(err => {
|
||||
uriEventListener.dispose();
|
||||
throw err;
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export class AuthTokenService extends Disposable implements IAuthTokenService {
|
||||
@@ -27,23 +26,11 @@ export class AuthTokenService extends Disposable implements IAuthTokenService {
|
||||
|
||||
constructor(
|
||||
@ISharedProcessService sharedProcessService: ISharedProcessService,
|
||||
@IURLService private readonly urlService: IURLService
|
||||
) {
|
||||
super();
|
||||
this.channel = sharedProcessService.getChannel('authToken');
|
||||
this._register(this.channel.listen<AuthTokenStatus>('onDidChangeStatus')(status => this.updateStatus(status)));
|
||||
this.channel.call<AuthTokenStatus>('_getInitialStatus').then(status => this.updateStatus(status));
|
||||
|
||||
this.urlService.registerHandler(this);
|
||||
}
|
||||
|
||||
handleURL(uri: URI) {
|
||||
if (uri.authority === 'vscode.login') {
|
||||
this.channel.call('exchangeCodeForToken', uri);
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
getToken(): Promise<string> {
|
||||
@@ -51,8 +38,7 @@ export class AuthTokenService extends Disposable implements IAuthTokenService {
|
||||
}
|
||||
|
||||
login(): Promise<void> {
|
||||
const callbackUri = this.urlService.create({ authority: 'vscode.login ' });
|
||||
return this.channel.call('login', callbackUri);
|
||||
return this.channel.call('login');
|
||||
}
|
||||
|
||||
refreshToken(): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user