Adds authentication callback support
This commit is contained in:
parent
774c4baa8f
commit
de701c049c
81
scripts/callback.html
Normal file
81
scripts/callback.html
Normal file
|
@ -0,0 +1,81 @@
|
|||
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<!-- Disable pinch zooming -->
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||||
|
||||
<!-- Content Security Policy -->
|
||||
<meta http-equiv="Content-Security-Policy" content="
|
||||
default-src 'self';
|
||||
img-src 'self' https: data: blob:;
|
||||
media-src 'none';
|
||||
script-src 'self';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
font-src 'self' blob:;
|
||||
">
|
||||
|
||||
<title>Visual Studio Code</title>
|
||||
|
||||
<!-- Styling -->
|
||||
<style type="text/css">
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<span class="branding">
|
||||
Visual Studio Code
|
||||
</span>
|
||||
<div class="message-container">
|
||||
<div class="message">
|
||||
You can close this page now.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -6,6 +6,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// @ts-check
|
||||
/** @typedef {import('../src/vs/workbench/workbench.web.api').IWorkbenchConstructionOptions} WebConfiguration **/
|
||||
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
|
@ -156,6 +157,8 @@ async function initialize() {
|
|||
|
||||
const staticExtensionsPromise = initialize();
|
||||
|
||||
const mapCallbackUriToRequestId = new Map();
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
const parsedUrl = url.parse(req.url, true);
|
||||
const pathname = parsedUrl.pathname;
|
||||
|
@ -187,6 +190,12 @@ const server = http.createServer((req, res) => {
|
|||
if (pathname === '/') {
|
||||
// main web
|
||||
return handleRoot(req, res);
|
||||
} else if (pathname === '/callback') {
|
||||
// callback support
|
||||
return handleCallback(req, res, parsedUrl);
|
||||
} else if (pathname === '/fetch-callback') {
|
||||
// callback fetch support
|
||||
return handleFetchCallback(req, res, parsedUrl);
|
||||
}
|
||||
|
||||
return serveError(req, res, 404, 'Not found.');
|
||||
|
@ -253,14 +262,20 @@ async function handleRoot(req, res) {
|
|||
}
|
||||
|
||||
const staticExtensions = await staticExtensionsPromise;
|
||||
const webConfiguration = escapeAttribute(JSON.stringify({
|
||||
staticExtensions, folderUri: ghPath
|
||||
/** @type {WebConfiguration} */
|
||||
const webConfig = {
|
||||
staticExtensions: staticExtensions,
|
||||
};
|
||||
|
||||
const webConfigJSON = escapeAttribute(JSON.stringify({
|
||||
...webConfig,
|
||||
folderUri: ghPath
|
||||
? { scheme: 'github', authority: 'github.com', path: ghPath }
|
||||
: { scheme: 'memfs', path: `/sample-folder` }
|
||||
: { scheme: 'memfs', path: `/sample-folder` },
|
||||
}));
|
||||
|
||||
const data = (await util.promisify(fs.readFile)(WEB_MAIN)).toString()
|
||||
.replace('{{WORKBENCH_WEB_CONFIGURATION}}', () => webConfiguration) // use a replace function to avoid that regexp replace patterns ($&, $0, ...) are applied
|
||||
.replace('{{WORKBENCH_WEB_CONFIGURATION}}', () => webConfigJSON) // use a replace function to avoid that regexp replace patterns ($&, $0, ...) are applied
|
||||
.replace('{{WEBVIEW_ENDPOINT}}', '')
|
||||
.replace('{{REMOTE_USER_DATA_URI}}', '');
|
||||
|
||||
|
@ -268,6 +283,100 @@ async function handleRoot(req, res) {
|
|||
return res.end(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle HTTP requests for /callback
|
||||
* @param {import('http').IncomingMessage} req
|
||||
* @param {import('http').ServerResponse} res
|
||||
* @param {import('url').UrlWithParsedQuery} parsedUrl
|
||||
*/
|
||||
async function handleCallback(req, res, parsedUrl) {
|
||||
const wellKnownKeys = ['vscode-requestId', 'vscode-scheme', 'vscode-authority', 'vscode-path', 'vscode-query', 'vscode-fragment'];
|
||||
const [requestId, vscodeScheme, vscodeAuthority, vscodePath, vscodeQuery, vscodeFragment] = wellKnownKeys.map(key => {
|
||||
const value = getFirstQueryValue(parsedUrl, key);
|
||||
if (value) {
|
||||
return decodeURIComponent(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
if (!requestId) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
return res.end(`Bad request.`);
|
||||
}
|
||||
|
||||
// merge over additional query values that we got
|
||||
let query = vscodeQuery;
|
||||
let index = 0;
|
||||
getFirstQueryValues(parsedUrl, wellKnownKeys).forEach((value, key) => {
|
||||
if (!query) {
|
||||
query = '';
|
||||
}
|
||||
|
||||
const prefix = (index++ === 0) ? '' : '&';
|
||||
query += `${prefix}${key}=${value}`;
|
||||
});
|
||||
|
||||
|
||||
// add to map of known callbacks
|
||||
mapCallbackUriToRequestId.set(requestId, JSON.stringify({ scheme: vscodeScheme || 'code-oss', authority: vscodeAuthority, path: vscodePath, query, fragment: vscodeFragment }));
|
||||
return serveFile(req, res, path.join(APP_ROOT, 'scripts', 'callback.html'), { 'Content-Type': 'text/html' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle HTTP requests for /fetch-callback
|
||||
* @param {import('http').IncomingMessage} req
|
||||
* @param {import('http').ServerResponse} res
|
||||
* @param {import('url').UrlWithParsedQuery} parsedUrl
|
||||
*/
|
||||
async function handleFetchCallback(req, res, parsedUrl) {
|
||||
const requestId = getFirstQueryValue(parsedUrl, 'vscode-requestId');
|
||||
if (!requestId) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
return res.end(`Bad request.`);
|
||||
}
|
||||
|
||||
const knownCallbackUri = mapCallbackUriToRequestId.get(requestId);
|
||||
if (knownCallbackUri) {
|
||||
mapCallbackUriToRequestId.delete(requestId);
|
||||
}
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/json' });
|
||||
return res.end(knownCallbackUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('url').UrlWithParsedQuery} parsedUrl
|
||||
* @param {string} key
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
function getFirstQueryValue(parsedUrl, key) {
|
||||
const result = parsedUrl.query[key];
|
||||
return Array.isArray(result) ? result[0] : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('url').UrlWithParsedQuery} parsedUrl
|
||||
* @param {string[] | undefined} ignoreKeys
|
||||
* @returns {Map<string, string>}
|
||||
*/
|
||||
function getFirstQueryValues(parsedUrl, ignoreKeys) {
|
||||
const queryValues = new Map();
|
||||
|
||||
for (const key in parsedUrl.query) {
|
||||
if (ignoreKeys && ignoreKeys.indexOf(key) >= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = getFirstQueryValue(parsedUrl, key);
|
||||
if (typeof value === 'string') {
|
||||
queryValues.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return queryValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue