diff options
Diffstat (limited to 'src/server/server_Initialization.ts')
-rw-r--r-- | src/server/server_Initialization.ts | 250 |
1 files changed, 127 insertions, 123 deletions
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index 2d52ea906..9183688c6 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -1,7 +1,11 @@ import * as bodyParser from 'body-parser'; +import * as brotli from 'brotli'; import { blue, yellow } from 'colors'; +import * as flash from 'connect-flash'; +import * as MongoStoreConnect from 'connect-mongo'; import * as cors from 'cors'; import * as express from 'express'; +import * as expressFlash from 'express-flash'; import * as session from 'express-session'; import { createServer } from 'https'; import * as passport from 'passport'; @@ -10,64 +14,45 @@ import * as webpack from 'webpack'; import * as wdm from 'webpack-dev-middleware'; import * as whm from 'webpack-hot-middleware'; import * as zlib from 'zlib'; -import { publicDirectory } from '.'; +import * as config from '../../webpack.config'; import { logPort } from './ActionUtilities'; +import RouteManager from './RouteManager'; +import RouteSubscriber from './RouteSubscriber'; +import { publicDirectory, resolvedPorts } from './SocketData'; import { SSL } from './apis/google/CredentialsLoader'; import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager'; import { Database } from './database'; -import RouteManager from './RouteManager'; -import RouteSubscriber from './RouteSubscriber'; import { WebSocket } from './websocket'; -import * as expressFlash from 'express-flash'; -import * as flash from 'connect-flash'; -import * as brotli from 'brotli'; -import * as MongoStoreConnect from 'connect-mongo'; -import * as config from '../../webpack.config'; /* RouteSetter is a wrapper around the server that prevents the server from being exposed. */ export type RouteSetter = (server: RouteManager) => void; -//export let disconnect: Function; +// export let disconnect: Function; -export let resolvedPorts: { server: number; socket: number } = { server: 1050, socket: 4321 }; +// eslint-disable-next-line import/no-mutable-exports export let resolvedServerUrl: string; -export default async function InitializeServer(routeSetter: RouteSetter) { - const isRelease = determineEnvironment(); - const app = buildWithMiddleware(express()); - const compiler = webpack(config as any); - - // route table managed by express. routes are tested sequentially against each of these map rules. when a match is found, the handler is called to process the request - app.use(wdm(compiler, { publicPath: config.output.publicPath })); - app.use(whm(compiler)); - app.get(new RegExp(/^\/+$/), (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between - app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); //all urls that start with dash's public directory: /files/ (e.g., /files/images, /files/audio, etc) - app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) })); - registerAuthenticationRoutes(app); // this adds routes to authenticate a user (login, etc) - registerCorsProxy(app); // this adds a /corsProxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies - isRelease && !SSL.Loaded && SSL.exit(); - routeSetter(new RouteManager(app, isRelease)); // this sets up all the regular supervised routes (things like /home, download/upload api's, pdf, search, session, etc) - registerEmbeddedBrowseRelativePathHandler(app); // this allows renered web pages which internally have relative paths to find their content +const week = 7 * 24 * 60 * 60 * 1000; +const secret = '64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc'; +const store = process.env.DB === 'MEM' ? new session.MemoryStore() : MongoStoreConnect.create({ mongoUrl: Database.url }); - isRelease && process.env.serverPort && (resolvedPorts.server = Number(process.env.serverPort)); - const server = isRelease ? createServer(SSL.Credentials, app) : app; - await new Promise<void>(resolve => server.listen(resolvedPorts.server, resolve)); - logPort('server', resolvedPorts.server); +/* Determine if the enviroment is dev mode or release mode. */ +function determineEnvironment() { + const isRelease = process.env.RELEASE === 'true'; - resolvedServerUrl = `${isRelease && process.env.serverName ? `https://${process.env.serverName}.com` : 'http://localhost'}:${resolvedPorts.server}`; + const color = isRelease ? blue : yellow; + const label = isRelease ? 'release' : 'development'; + console.log(`\nrunning server in ${color(label)} mode`); - // initialize the web socket (bidirectional communication: if a user changes - // a field on one client, that change must be broadcast to all other clients) - await WebSocket.initialize(isRelease, SSL.Credentials); + // // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE + // // on the client side, thanks to dotenv in webpack.config.js + // let clientUtils = fs.readFileSync('./src/client/util/ClientUtils.ts.temp', 'utf8'); + // clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`; + // fs.writeFileSync('./src/client/util/ClientUtils.ts', clientUtils, 'utf8'); - //disconnect = async () => new Promise<Error>(resolve => server.close(resolve)); return isRelease; } -const week = 7 * 24 * 60 * 60 * 1000; -const secret = '64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc'; -const store = process.env.DB === 'MEM' ? new session.MemoryStore() : MongoStoreConnect.create({ mongoUrl: Database.url }); - function buildWithMiddleware(server: express.Express) { [ session({ @@ -100,72 +85,43 @@ function buildWithMiddleware(server: express.Express) { return server; } -/* Determine if the enviroment is dev mode or release mode. */ -function determineEnvironment() { - const isRelease = process.env.RELEASE === 'true'; - - const color = isRelease ? blue : yellow; - const label = isRelease ? 'release' : 'development'; - console.log(`\nrunning server in ${color(label)} mode`); - - // // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE - // // on the client side, thanks to dotenv in webpack.config.js - // let clientUtils = fs.readFileSync('./src/client/util/ClientUtils.ts.temp', 'utf8'); - // clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`; - // fs.writeFileSync('./src/client/util/ClientUtils.ts', clientUtils, 'utf8'); - - return isRelease; -} - -function registerAuthenticationRoutes(server: express.Express) { - server.get('/signup', getSignup); - server.post('/signup', postSignup); - - server.get('/login', getLogin); - server.post('/login', postLogin); - - server.get('/logout', getLogout); - - server.get('/forgotPassword', getForgot); - server.post('/forgotPassword', postForgot); - - const reset = new RouteSubscriber('resetPassword').add('token').build; - server.get(reset, getReset); - server.post(reset, postReset); -} - -function registerCorsProxy(server: express.Express) { - server.use('/corsProxy', async (req, res) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE'); - res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers')); - const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : ''; - let requrlraw = decodeURIComponent(req.url.substring(1)); - const qsplit = requrlraw.split('?q='); - const newqsplit = requrlraw.split('&q='); - if (qsplit.length > 1 && newqsplit.length > 1) { - const lastq = newqsplit[newqsplit.length - 1]; - requrlraw = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1]; - } - const requrl = requrlraw.startsWith('/') ? referer + requrlraw : requrlraw; - // cors weirdness here... - // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative, - // then we redirect again to the cors referer and just add the relative path. - if (!requrl.startsWith('http') && req.originalUrl.startsWith('/corsProxy') && referer?.includes('corsProxy')) { - res.redirect(referer + (referer.endsWith('/') ? '' : '/') + requrl); +function registerEmbeddedBrowseRelativePathHandler(server: express.Express) { + server.use('*', (req, res) => { + // res.setHeader('Access-Control-Allow-Origin', '*'); + // res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE'); + // res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers')); + const relativeUrl = req.originalUrl; + if (!res.headersSent && req.headers.referer?.includes('corsProxy')) { + if (!req.user) res.redirect('/home'); // When no user is logged in, we interpret a relative URL as being a reference to something they don't have access to and redirect to /home + // a request for something by a proxied referrer means it must be a relative reference. So construct a proxied absolute reference here. + try { + const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart) + const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ ) + const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ''); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart) + const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org) + const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>) + const redirectUrl = relativeUrl.startsWith('//') ? 'http:' + relativeUrl : redirectedProxiedUrl; + res.redirect(redirectUrl); + } catch (e) { + console.log('Error embed: ', e); + } + } else if (relativeUrl.startsWith('/search') && !req.headers.referer?.includes('corsProxy')) { + // detect search query and use default search engine + res.redirect(req.headers.referer + 'corsProxy/' + encodeURIComponent('http://www.google.com' + relativeUrl)); } else { - proxyServe(req, requrl, res); + res.end(); } }); } function proxyServe(req: any, requrl: string, response: any) { + // eslint-disable-next-line global-require const htmlBodyMemoryStream = new (require('memorystream'))(); - var wasinBrFormat = false; + let wasinBrFormat = false; const sendModifiedBody = () => { const header = response.headers['content-encoding']; - const refToCors = (match: any, tag: string, sym: string, href: string, offset: any, string: any) => `${tag}=${sym + resolvedServerUrl}/corsProxy/${href + sym}`; - const relpathToCors = (match: any, href: string, offset: any, string: any) => `="${resolvedServerUrl + '/corsProxy/' + decodeURIComponent(req.originalUrl.split('/corsProxy/')[1].match(/https?:\/\/[^\/]*/)?.[0] ?? '') + '/' + href}"`; + const refToCors = (match: any, tag: string, sym: string, href: string) => `${tag}=${sym + resolvedServerUrl}/corsProxy/${href + sym}`; + // const relpathToCors = (match: any, href: string, offset: any, string: any) => `="${resolvedServerUrl + '/corsProxy/' + decodeURIComponent(req.originalUrl.split('/corsProxy/')[1].match(/https?:\/\/[^\/]*/)?.[0] ?? '') + '/' + href}"`; if (header) { try { const bodyStream = htmlBodyMemoryStream.read(); @@ -174,8 +130,8 @@ function proxyServe(req: any, requrl: string, response: any) { const htmlText = htmlInputText .toString('utf8') .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>') - .replace(/(src|href)=([\'\"])(https?[^\2\n]*)\1/g, refToCors) // replace src or href='http(s)://...' or href="http(s)://.." - //.replace(/= *"\/([^"]*)"/g, relpathToCors) + .replace(/(src|href)=(['"])(https?[^\2\n]*)\1/g, refToCors) // replace src or href='http(s)://...' or href="http(s)://.." + // .replace(/= *"\/([^"]*)"/g, relpathToCors) .replace(/data-srcset="[^"]*"/g, '') .replace(/srcset="[^"]*"/g, '') .replace(/target="_blank"/g, ''); @@ -198,7 +154,7 @@ function proxyServe(req: any, requrl: string, response: any) { } }; const retrieveHTTPBody = () => { - //req.headers.cookie = ''; + // req.headers.cookie = ''; req.pipe(request(requrl)) .on('error', (e: any) => { console.log(`CORS url error: ${requrl}`, e); @@ -227,6 +183,7 @@ function proxyServe(req: any, requrl: string, response: any) { res.headers['x-permitted-cross-domain-policies'] = 'all'; res.headers['x-frame-options'] = ''; res.headers['content-security-policy'] = ''; + // eslint-disable-next-line no-multi-assign response.headers = response._headers = res.headers; }) .on('end', sendModifiedBody) @@ -236,31 +193,78 @@ function proxyServe(req: any, requrl: string, response: any) { retrieveHTTPBody(); } -function registerEmbeddedBrowseRelativePathHandler(server: express.Express) { - server.use('*', (req, res) => { - // res.setHeader('Access-Control-Allow-Origin', '*'); - // res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE'); - // res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers')); - const relativeUrl = req.originalUrl; - if (!res.headersSent && req.headers.referer?.includes('corsProxy')) { - if (!req.user) res.redirect('/home'); // When no user is logged in, we interpret a relative URL as being a reference to something they don't have access to and redirect to /home - // a request for something by a proxied referrer means it must be a relative reference. So construct a proxied absolute reference here. - try { - const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart) - const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ ) - const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ''); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart) - const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^\/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org) - const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>) - const redirectUrl = relativeUrl.startsWith('//') ? 'http:' + relativeUrl : redirectedProxiedUrl; - res.redirect(redirectUrl); - } catch (e) { - console.log('Error embed: ', e); - } - } else if (relativeUrl.startsWith('/search') && !req.headers.referer?.includes('corsProxy')) { - // detect search query and use default search engine - res.redirect(req.headers.referer + 'corsProxy/' + encodeURIComponent('http://www.google.com' + relativeUrl)); +function registerCorsProxy(server: express.Express) { + server.use('/corsProxy', async (req, res) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE'); + res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers')); + const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : ''; + let requrlraw = decodeURIComponent(req.url.substring(1)); + const qsplit = requrlraw.split('?q='); + const newqsplit = requrlraw.split('&q='); + if (qsplit.length > 1 && newqsplit.length > 1) { + const lastq = newqsplit[newqsplit.length - 1]; + requrlraw = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1]; + } + const requrl = requrlraw.startsWith('/') ? referer + requrlraw : requrlraw; + // cors weirdness here... + // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative, + // then we redirect again to the cors referer and just add the relative path. + if (!requrl.startsWith('http') && req.originalUrl.startsWith('/corsProxy') && referer?.includes('corsProxy')) { + res.redirect(referer + (referer.endsWith('/') ? '' : '/') + requrl); } else { - res.end(); + proxyServe(req, requrl, res); } }); } + +function registerAuthenticationRoutes(server: express.Express) { + server.get('/signup', getSignup); + server.post('/signup', postSignup); + + server.get('/login', getLogin); + server.post('/login', postLogin); + + server.get('/logout', getLogout); + + server.get('/forgotPassword', getForgot); + server.post('/forgotPassword', postForgot); + + const reset = new RouteSubscriber('resetPassword').add('token').build; + server.get(reset, getReset); + server.post(reset, postReset); +} + +export default async function InitializeServer(routeSetter: RouteSetter) { + const isRelease = determineEnvironment(); + const app = buildWithMiddleware(express()); + const compiler = webpack(config as any); + + // route table managed by express. routes are tested sequentially against each of these map rules. when a match is found, the handler is called to process the request + app.use(wdm(compiler, { publicPath: config.output.publicPath })); + app.use(whm(compiler)); + app.get(/^\/+$/, (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between + app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); // all urls that start with dash's public directory: /files/ (e.g., /files/images, /files/audio, etc) + app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) })); + registerAuthenticationRoutes(app); // this adds routes to authenticate a user (login, etc) + registerCorsProxy(app); // this adds a /corsProxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies + isRelease && !SSL.Loaded && SSL.exit(); + routeSetter(new RouteManager(app, isRelease)); // this sets up all the regular supervised routes (things like /home, download/upload api's, pdf, search, session, etc) + registerEmbeddedBrowseRelativePathHandler(app); // this allows renered web pages which internally have relative paths to find their content + + isRelease && process.env.serverPort && (resolvedPorts.server = Number(process.env.serverPort)); + const server = isRelease ? createServer(SSL.Credentials, app) : app; + await new Promise<void>(resolve => { + server.listen(resolvedPorts.server, resolve); + }); + logPort('server', resolvedPorts.server); + + resolvedServerUrl = `${isRelease && process.env.serverName ? `https://${process.env.serverName}.com` : 'http://localhost'}:${resolvedPorts.server}`; + + // initialize the web socket (bidirectional communication: if a user changes + // a field on one client, that change must be broadcast to all other clients) + await WebSocket.initialize(isRelease, SSL.Credentials); + + // disconnect = async () => new Promise<Error>(resolve => server.close(resolve)); + return isRelease; +} |