gristlabs_grist-core/app/server/lib/PluginEndpoint.ts
Dmitry S 51ff72c15e (core) Faster builds all around.
Summary:
Building:
- Builds no longer wait for tsc for either client, server, or test targets. All use esbuild which is very fast.
- Build still runs tsc, but only to report errors. This may be turned off with `SKIP_TSC=1` env var.
- Grist-core continues to build using tsc.
- Esbuild requires ES6 module semantics. Typescript's esModuleInterop is turned
  on, so that tsc accepts and enforces correct usage.
- Client-side code is watched and bundled by webpack as before (using esbuild-loader)

Code changes:
- Imports must now follow ES6 semantics: `import * as X from ...` produces a
  module object; to import functions or class instances, use `import X from ...`.
- Everything is now built with isolatedModules flag. Some exports were updated for it.

Packages:
- Upgraded browserify dependency, and related packages (used for the distribution-building step).
- Building the distribution now uses esbuild's minification. babel-minify is no longer used.

Test Plan: Should have no behavior changes, existing tests should pass, and docker image should build too.

Reviewers: georgegevoian

Reviewed By: georgegevoian

Subscribers: alexmojaki

Differential Revision: https://phab.getgrist.com/D3506
2022-07-04 10:42:40 -04:00

79 lines
3.5 KiB
TypeScript

import {FlexServer} from 'app/server/lib/FlexServer';
import log from 'app/server/lib/log';
import {PluginManager} from 'app/server/lib/PluginManager';
import * as express from 'express';
import * as mimeTypes from 'mime-types';
import * as path from 'path';
// Get the url where plugin material should be served from.
export function getUntrustedContentOrigin(): string|undefined {
return process.env.APP_UNTRUSTED_URL;
}
// Get the host serving plugin material
export function getUntrustedContentHost(): string|undefined {
const origin = getUntrustedContentOrigin();
if (!origin) { return; }
return new URL(origin).host;
}
// Add plugin endpoints to be served on untrusted host
export function addPluginEndpoints(server: FlexServer, pluginManager: PluginManager) {
const host = getUntrustedContentHost();
if (host) {
server.app.get(/^\/plugins\/(installed|builtIn)\/([^/]+)\/(.+)/, (req, res) =>
servePluginContent(req, res, pluginManager, host));
}
}
// Serve content for plugins with various checks that it is being accessed as we expect.
function servePluginContent(req: express.Request, res: express.Response,
pluginManager: PluginManager, untrustedContentHost: string) {
const pluginKind = req.params[0];
const pluginId = req.params[1];
const pluginPath = req.params[2];
// We should not serve untrusted content (as from plugins) from the same domain as the main app
// (at least not html pages), as it's an open door to XSS attacks.
// - For hosted version, we serve it from a separate domain name.
// - For electron version, we give access to protected <webview> content based on a special header.
// - We also allow "application/javascript" content from the main domain for serving the
// WebWorker main script, since that's hard to distinguish in electron case, and should not
// enable XSS.
if (matchHost(req.get('host'), untrustedContentHost) ||
req.get('X-From-Plugin-WebView') === "true" ||
mimeTypes.lookup(path.extname(pluginPath)) === "application/javascript") {
const dirs = pluginManager.dirs();
const contentRoot = pluginKind === "installed" ? dirs.installed : dirs.builtIn;
// Note that pluginPath may not be safe, but `sendFile` with the "root" option restricts
// relative paths to be within the root folder (see the 3rd party library unit-test:
// https://github.com/pillarjs/send/blob/3daa901cf731b86187e4449fa2c52f971e0b3dbc/test/send.js#L1363)
return res.sendFile(`${pluginId}/${pluginPath}`, {root: contentRoot});
}
log.warn(`Refusing to serve untrusted plugin content on ${req.get('host')}`);
res.status(403).end('Plugin content is not accessible to this request');
}
// Middleware to restrict some assets to untrusted host.
export function limitToPlugins(handler: express.RequestHandler) {
const host = getUntrustedContentHost();
return function(req: express.Request, resp: express.Response, next: express.NextFunction) {
if (!host) { return next(); }
if (matchHost(req.get('host'), host) || req.get('X-From-Plugin-WebView') === "true") {
return handler(req, resp, next);
}
return next();
};
}
// Compare hosts, bearing in mind that if they happen to be on port 443 the
// port number may or may not be included. This assumes we are serving over https.
function matchHost(host1: string|undefined, host2: string) {
if (!host1) { return false; }
if (host1 === host2) { return true; }
if (host1.indexOf(':') === -1) { host1 += ":443"; }
if (host2.indexOf(':') === -1) { host2 += ":443"; }
return host1 === host2;
}