diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/ApiManagers/DeleteManager.ts | 79 | ||||
-rw-r--r-- | src/server/RouteManager.ts | 13 | ||||
-rw-r--r-- | src/server/apis/google/CredentialsLoader.ts | 30 | ||||
-rw-r--r-- | src/server/index.ts | 40 | ||||
-rw-r--r-- | src/server/server_Initialization.ts | 19 | ||||
-rw-r--r-- | src/server/websocket.ts | 23 |
6 files changed, 117 insertions, 87 deletions
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts index dcb21c30d..46c0d8a8a 100644 --- a/src/server/ApiManagers/DeleteManager.ts +++ b/src/server/ApiManagers/DeleteManager.ts @@ -3,7 +3,7 @@ import { Method, _permission_denied } from "../RouteManager"; import { WebSocket } from "../websocket"; import { Database } from "../database"; import rimraf = require("rimraf"); -import { filesDirectory, AdminPriviliges } from ".."; +import { filesDirectory } from ".."; import { DashUploadUtils } from "../DashUploadUtils"; import { mkdirSync } from "fs"; import RouteSubscriber from "../RouteSubscriber"; @@ -14,72 +14,35 @@ export default class DeleteManager extends ApiManager { register({ method: Method.GET, + requireAdminInRelease: true, subscription: new RouteSubscriber("delete").add("target?"), - secureHandler: async ({ req, res, isRelease, user: { id } }) => { + secureHandler: async ({ req, res }) => { const { target } = req.params; - if (isRelease && process.env.PASSWORD) { - if (AdminPriviliges.get(id)) { - AdminPriviliges.delete(id); - } else { - return res.redirect(`/admin/delete${target ? `:${target}` : ``}`); + + if (!target) { + await WebSocket.doDelete(); + } else { + let all = false; + switch (target) { + case "all": + all = true; + case "database": + await WebSocket.doDelete(false); + if (!all) break; + case "files": + rimraf.sync(filesDirectory); + mkdirSync(filesDirectory); + await DashUploadUtils.buildFileDirectories(); + break; + default: + await Database.Instance.dropSchema(target); } } - this.doDelete(target); res.redirect("/home"); } }); - register({ - method: Method.GET, - subscription: new RouteSubscriber("admin").add("previous_target"), - secureHandler: ({ res }) => res.render("admin.pug", { title: "Enter Administrator Password" }) - }) - - register({ - method: Method.POST, - subscription: new RouteSubscriber("admin").add("previous_target"), - secureHandler: async ({ req, res, isRelease, user: { id } }) => { - const { PASSWORD } = process.env; - if (!(isRelease && PASSWORD)) { - return res.redirect("/home"); - } - const { password } = req.body; - const { previous_target } = req.params; - let redirect: string; - if (password === PASSWORD) { - AdminPriviliges.set(id, true); - redirect = `/${previous_target.replace(":", "/")}`; - } else { - redirect = `/admin/${previous_target}`; - } - res.redirect(redirect); - } - }) - - } - - - private doDelete = async (target?: string) => { - if (!target) { - await WebSocket.doDelete(); - } else { - let all = false; - switch (target) { - case "all": - all = true; - case "database": - await WebSocket.doDelete(false); - if (!all) break; - case "files": - rimraf.sync(filesDirectory); - mkdirSync(filesDirectory); - await DashUploadUtils.buildFileDirectories(); - break; - default: - await Database.Instance.dropSchema(target); - } - } } }
\ No newline at end of file diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts index 9a87ada6f..350c02f06 100644 --- a/src/server/RouteManager.ts +++ b/src/server/RouteManager.ts @@ -2,6 +2,7 @@ import RouteSubscriber from "./RouteSubscriber"; import { DashUserModel } from "./authentication/DashUserModel"; import { Request, Response, Express } from 'express'; import { cyan, red, green } from 'colors'; +import { AdminPriviliges } from "."; export enum Method { GET, @@ -25,6 +26,7 @@ export interface RouteInitializer { secureHandler: SecureHandler; publicHandler?: PublicHandler; errorHandler?: ErrorHandler; + requireAdminInRelease?: boolean; } const registered = new Map<string, Set<Method>>(); @@ -84,7 +86,7 @@ export default class RouteManager { * @param initializer */ addSupervisedRoute = (initializer: RouteInitializer): void => { - const { method, subscription, secureHandler, publicHandler, errorHandler } = initializer; + const { method, subscription, secureHandler, publicHandler, errorHandler, requireAdminInRelease: requireAdmin } = initializer; typeof (initializer.subscription) === "string" && RouteManager.routes.push(initializer.subscription); initializer.subscription instanceof RouteSubscriber && RouteManager.routes.push(initializer.subscription.root); @@ -94,7 +96,7 @@ export default class RouteManager { }); const isRelease = this._isRelease; const supervised = async (req: Request, res: Response) => { - let { user } = req; + let user = req.user as Partial<DashUserModel> | undefined; const { originalUrl: target } = req; if (process.env.DB === "MEM" && !user) { user = { id: "guest", email: "", userDocumentId: "guestDocId" }; @@ -113,6 +115,13 @@ export default class RouteManager { } }; if (user) { + if (requireAdmin && isRelease && process.env.PASSWORD) { + if (AdminPriviliges.get(user.id)) { + AdminPriviliges.delete(user.id); + } else { + return res.redirect(`/admin/${req.originalUrl.substring(1).replace("/", ":")}`); + } + } await tryExecute(secureHandler, { ...core, user }); } else { req.session!.target = target; diff --git a/src/server/apis/google/CredentialsLoader.ts b/src/server/apis/google/CredentialsLoader.ts index 09db4fc60..ef1f9a91e 100644 --- a/src/server/apis/google/CredentialsLoader.ts +++ b/src/server/apis/google/CredentialsLoader.ts @@ -1,6 +1,7 @@ import { readFile, readFileSync } from "fs"; import { pathFromRoot } from "../../ActionUtilities"; import { SecureContextOptions } from "tls"; +import { blue, red } from "colors"; export namespace GoogleCredentialsLoader { @@ -30,16 +31,37 @@ export namespace GoogleCredentialsLoader { } -export namespace SSLCredentialsLoader { +export namespace SSL { export let Credentials: SecureContextOptions = {}; + export let Loaded = false; + + const suffixes = { + privateKey: ".key", + certificate: ".crt", + caBundle: "-ca.crt" + }; export async function loadCredentials() { const { serverName } = process.env; const cert = (suffix: string) => readFileSync(pathFromRoot(`./${serverName}${suffix}`)).toString(); - Credentials.key = cert(".key"); - Credentials.cert = cert(".crt"); - Credentials.ca = cert("-ca.crt"); + try { + Credentials.key = cert(suffixes.privateKey); + Credentials.cert = cert(suffixes.certificate); + Credentials.ca = cert(suffixes.caBundle); + Loaded = true; + } catch (e) { + Credentials = {}; + Loaded = false; + } + } + + export function exit() { + console.log(red("Running this server in release mode requires the following SSL credentials in the project root:")); + const serverName = process.env.serverName ? process.env.serverName : "{process.env.serverName}"; + Object.values(suffixes).forEach(suffix => console.log(blue(`${serverName}${suffix}`))); + console.log(red("Please ensure these files exist and restart, or run this in development mode.")); + process.exit(0); } } diff --git a/src/server/index.ts b/src/server/index.ts index 65a9f10a7..ff74cfb33 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -12,7 +12,7 @@ import UtilManager from './ApiManagers/UtilManager'; import { SearchManager } from './ApiManagers/SearchManager'; import UserManager from './ApiManagers/UserManager'; import DownloadManager from './ApiManagers/DownloadManager'; -import { GoogleCredentialsLoader, SSLCredentialsLoader } from './apis/google/CredentialsLoader'; +import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader'; import DeleteManager from "./ApiManagers/DeleteManager"; import PDFManager from "./ApiManagers/PDFManager"; import UploadManager from "./ApiManagers/UploadManager"; @@ -41,7 +41,7 @@ async function preliminaryFunctions() { await DashUploadUtils.buildFileDirectories(); await Logger.initialize(); await GoogleCredentialsLoader.loadCredentials(); - SSLCredentialsLoader.loadCredentials(); + SSL.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); if (process.env.DB !== "MEM") { await log_execution({ @@ -102,6 +102,42 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: res.sendFile(path.join(__dirname, '../../deploy/' + filename)); }; + /** + * Serves a simple password input box for any + */ + addSupervisedRoute({ + method: Method.GET, + subscription: new RouteSubscriber("admin").add("previous_target"), + secureHandler: ({ res, isRelease }) => { + const { PASSWORD } = process.env; + if (!(isRelease && PASSWORD)) { + return res.redirect("/home"); + } + res.render("admin.pug", { title: "Enter Administrator Password" }); + } + }); + + addSupervisedRoute({ + method: Method.POST, + subscription: new RouteSubscriber("admin").add("previous_target"), + secureHandler: async ({ req, res, isRelease, user: { id } }) => { + const { PASSWORD } = process.env; + if (!(isRelease && PASSWORD)) { + return res.redirect("/home"); + } + const { password } = req.body; + const { previous_target } = req.params; + let redirect: string; + if (password === PASSWORD) { + AdminPriviliges.set(id, true); + redirect = `/${previous_target.replace(":", "/")}`; + } else { + redirect = `/admin/${previous_target}`; + } + res.redirect(redirect); + } + }); + addSupervisedRoute({ method: Method.GET, subscription: ["/home", new RouteSubscriber("doc").add("docId")], diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index ab91721b2..14b8776d8 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -21,11 +21,11 @@ import * as request from 'request'; import RouteSubscriber from './RouteSubscriber'; import { publicDirectory } from '.'; import { logPort, pathFromRoot, } from './ActionUtilities'; -import { blue, yellow } from 'colors'; +import { blue, yellow, red } from 'colors'; import * as cors from "cors"; import { createServer, Server as HttpsServer } from "https"; import { Server as HttpServer } from "http"; -import { SSLCredentialsLoader } from './apis/google/CredentialsLoader'; +import { SSL } from './apis/google/CredentialsLoader'; /* RouteSetter is a wrapper around the server that prevents the server from being exposed. */ @@ -49,18 +49,19 @@ export default async function InitializeServer(routeSetter: RouteSetter) { const isRelease = determineEnvironment(); + isRelease && !SSL.Loaded && SSL.exit(); + routeSetter(new RouteManager(app, isRelease)); registerRelativePath(app); + let server: HttpServer | HttpsServer; const { serverPort } = process.env; const resolved = isRelease && serverPort ? Number(serverPort) : 1050; - - let server: HttpServer | HttpsServer; - if (isRelease) { - server = createServer(SSLCredentialsLoader.Credentials, app).listen(resolved, () => logPort("server", resolved)); - } else { - server = app.listen(resolved, () => logPort("server", resolved)); - } + await new Promise<void>(resolve => server = isRelease ? + createServer(SSL.Credentials, app).listen(resolved, resolve) : + app.listen(resolved, resolve) + ); + logPort("server", resolved); // initialize the web socket (bidirectional communication: if a user changes // a field on one client, that change must be broadcast to all other clients) diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 1abfe2327..21e772e83 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -8,13 +8,13 @@ import { Database } from "./database"; import { Search } from "./Search"; import * as sio from 'socket.io'; import YoutubeApi from "./apis/youtube/youtubeApiSample"; -import { GoogleCredentialsLoader, SSLCredentialsLoader } from "./apis/google/CredentialsLoader"; +import { GoogleCredentialsLoader, SSL } from "./apis/google/CredentialsLoader"; import { timeMap } from "./ApiManagers/UserManager"; import { green } from "colors"; import { networkInterfaces } from "os"; import executeImport from "../scraping/buxton/final/BuxtonImporter"; import { DocumentsCollection } from "./IDatabase"; -import { createServer } from "https"; +import { createServer, Server } from "https"; import * as express from "express"; export namespace WebSocket { @@ -25,21 +25,20 @@ export namespace WebSocket { export let disconnect: Function; const defaultPort = 4321; - export function initialize(isRelease: boolean, app: express.Express) { + export async function initialize(isRelease: boolean, app: express.Express) { let io: sio.Server; - const log = (port: number) => { - logPort("websocket", port); - console.log(); - } + let resolved: number; if (isRelease) { const { socketPort } = process.env; - const resolved = socketPort ? Number(socketPort) : defaultPort; - const socketEndpoint = createServer(SSLCredentialsLoader.Credentials, app).listen(resolved, () => log(resolved)); - io = sio(socketEndpoint, SSLCredentialsLoader.Credentials as any); + resolved = socketPort ? Number(socketPort) : defaultPort; + let socketEndpoint: Server; + await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolved, resolve)); + io = sio(socketEndpoint!, SSL.Credentials as any); } else { - io = sio().listen(defaultPort); - log(defaultPort); + io = sio().listen(resolved = defaultPort); } + logPort("websocket", resolved); + console.log(); io.on("connection", function (socket: Socket) { _socket = socket; |