require('dotenv').config(); import * as bodyParser from 'body-parser'; import { exec } from 'child_process'; import * as cookieParser from 'cookie-parser'; import * as express from 'express'; import * as session from 'express-session'; import * as expressValidator from 'express-validator'; import * as formidable from 'formidable'; import * as fs from 'fs'; import * as sharp from 'sharp'; import * as Pdfjs from 'pdfjs-dist'; const imageDataUri = require('image-data-uri'); import * as mobileDetect from 'mobile-detect'; import * as passport from 'passport'; import * as path from 'path'; import * as request from 'request'; import * as rp from 'request-promise'; import * as io from 'socket.io'; import { Socket } from 'socket.io'; import * as webpack from 'webpack'; import * as wdm from 'webpack-dev-middleware'; import * as whm from 'webpack-hot-middleware'; import { Utils } from '../Utils'; import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/controllers/user_controller'; import { DashUserModel } from './authentication/models/user_model'; import { Client } from './Client'; import { Database } from './database'; import { MessageStore, Transferable, Types, Diff, Message } from "./Message"; import { RouteStore } from './RouteStore'; const app = express(); const config = require('../../webpack.config'); import { createCanvas, loadImage, Canvas } from "canvas"; const compiler = webpack(config); const port = 1050; // default port to listen const serverPort = 4321; import expressFlash = require('express-flash'); import flash = require('connect-flash'); import c = require("crypto"); import { Search } from './Search'; import { debug } from 'util'; import _ = require('lodash'); import { Response } from 'express-serve-static-core'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest)); const release = process.env.RELEASE === "true"; if (process.env.RELEASE === "true") { console.log("Running server in release mode"); } else { console.log("Running server in debug mode"); } console.log(process.env.PWD); let clientUtils = fs.readFileSync("./src/client/util/ClientUtils.ts.temp", "utf8"); clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(release))}`; fs.writeFileSync("./src/client/util/ClientUtils.ts", clientUtils, "utf8"); const mongoUrl = 'mongodb://localhost:27017/Dash'; mongoose.connection.readyState === 0 && mongoose.connect(mongoUrl); mongoose.connection.on('connected', () => console.log("connected")); // SESSION MANAGEMENT AND AUTHENTICATION MIDDLEWARE // ORDER OF IMPORTS MATTERS app.use(cookieParser()); app.use(session({ secret: "64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc", resave: true, cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 }, saveUninitialized: true, store: new MongoStore({ url: 'mongodb://localhost:27017/Dash' }) })); app.use(flash()); app.use(expressFlash()); app.use(bodyParser.json({ limit: "10mb" })); app.use(bodyParser.urlencoded({ extended: true })); app.use(expressValidator()); app.use(passport.initialize()); app.use(passport.session()); app.use((req, res, next) => { res.locals.user = req.user; next(); }); app.get("/hello", (req, res) => res.send("
Hello
")); enum Method { GET, POST } /** * Please invoke this function when adding a new route to Dash's server. * It ensures that any requests leading to or containing user-sensitive information * does not execute unless Passport authentication detects a user logged in. * @param method whether or not the request is a GET or a POST * @param handler the action to invoke, recieving a DashUserModel and, as expected, the Express.Request and Express.Response * @param onRejection an optional callback invoked on return if no user is found to be logged in * @param subscribers the forward slash prepended path names (reference and add to RouteStore.ts) that will all invoke the given @param handler */ function addSecureRoute(method: Method, handler: (user: DashUserModel, res: express.Response, req: express.Request) => void, onRejection: (res: express.Response, req: express.Request) => any = res => res.redirect(RouteStore.login), ...subscribers: string[] ) { let abstracted = (req: express.Request, res: express.Response) => { if (req.user) { handler(req.user, res, req); } else { req.session!.target = req.originalUrl; onRejection(res, req); } }; subscribers.forEach(route => { switch (method) { case Method.GET: app.get(route, abstracted); break; case Method.POST: app.post(route, abstracted); break; } }); } // STATIC FILE SERVING app.use(express.static(__dirname + RouteStore.public)); app.use(RouteStore.images, express.static(__dirname + RouteStore.public)); app.get("/pull", (req, res) => exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', (err, stdout, stderr) => { if (err) { res.send(err.message); return; } res.redirect("/"); })); app.get("/version", (req, res) => { exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout, stderr) => { if (err) { res.send(err.message); return; } res.send(stdout); }); }); // SEARCH // GETTERS app.get("/search", async (req, res) => { const solrQuery: any = {}; ["q", "fq", "start", "rows", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]); if (solrQuery.q === undefined) { res.send([]); return; } let results = await Search.Instance.search(solrQuery); res.send(results); }); function msToTime(duration: number) { let milliseconds = Math.floor((duration % 1000) / 100), seconds = Math.floor((duration / 1000) % 60), minutes = Math.floor((duration / (1000 * 60)) % 60), hours = Math.floor((duration / (1000 * 60 * 60)) % 24); let hoursS = (hours < 10) ? "0" + hours : hours; let minutesS = (minutes < 10) ? "0" + minutes : minutes; let secondsS = (seconds < 10) ? "0" + seconds : seconds; return hoursS + ":" + minutesS + ":" + secondsS + "." + milliseconds; } app.get("/serializeDoc/:docId", async (req, res) => { const files: { [name: string]: string[] } = {}; const docs: { [id: string]: any } = {}; const fn = (doc: any): string[] => { const ids: string[] = []; for (const key in doc) { if (!doc.hasOwnProperty(key)) { continue; } const field = doc[key]; if (field === undefined || field === null) { continue; } if (field.__type === "proxy" || field.__type === "prefetch_proxy") { ids.push(field.fieldId); } else if (field.__type === "list") { ids.push(...fn(field.fields)); } else if (typeof field === "string") { const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w\-]*)"/g; let match: string[] | null; while ((match = re.exec(field)) !== null) { ids.push(match[1]); } } else if (field.__type === "RichTextField") { const re = /"href"\s*:\s*"(.*?)"/g; let match: string[] | null; while ((match = re.exec(field.Data)) !== null) { const urlString = match[1]; const split = new URL(urlString).pathname.split("doc/"); if (split.length > 1) { ids.push(split[split.length - 1]); } } const re2 = /"src"\s*:\s*"(.*?)"/g; while ((match = re2.exec(field.Data)) !== null) { const urlString = match[1]; const pathname = new URL(urlString).pathname; const ext = path.extname(pathname); const fileName = path.basename(pathname, ext); let exts = files[fileName]; if (!exts) { files[fileName] = exts = []; } exts.push(ext); } } else if (["audio", "image", "video", "pdf", "web"].includes(field.__type)) { const url = new URL(field.url); const pathname = url.pathname; const ext = path.extname(pathname); const fileName = path.basename(pathname, ext); let exts = files[fileName]; if (!exts) { files[fileName] = exts = []; } exts.push(ext); } } docs[doc.id] = doc; return ids; }; Database.Instance.visit([req.params.docId], fn); }); app.get("/downloadId/:docId", (req, res) => { res.download(`/serializeDoc/${req.params.docId}`, `DocumentExport.zip`); }); app.get("/whosOnline", (req, res) => { let users: any = { active: {}, inactive: {} }; const now = Date.now(); for (const user in timeMap) { const time = timeMap[user]; const key = ((now - time) / 1000) < (60 * 5) ? "active" : "inactive"; users[key][user] = `Last active ${msToTime(now - time)} ago`; } res.send(users); }); app.get("/thumbnail/:filename", (req, res) => { let filename = req.params.filename; let noExt = filename.substring(0, filename.length - ".png".length); let pagenumber = parseInt(noExt.split('-')[1]); fs.exists(uploadDir + filename, (exists: boolean) => { console.log(`${uploadDir + filename} ${exists ? "exists" : "does not exist"}`); if (exists) { let input = fs.createReadStream(uploadDir + filename); probe(input, (err: any, result: any) => { if (err) { console.log(err); console.log(`error on ${filename}`); return; } res.send({ path: "/files/" + filename, width: result.width, height: result.height }); }); } else { LoadPage(uploadDir + filename.substring(0, filename.length - noExt.split('-')[1].length - ".PNG".length - 1) + ".pdf", pagenumber, res); } }); }); function LoadPage(file: string, pageNumber: number, res: Response) { console.log(file); Pdfjs.getDocument(file).promise .then((pdf: Pdfjs.PDFDocumentProxy) => { let factory = new NodeCanvasFactory(); console.log(pageNumber); pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { console.log("reading " + page); let viewport = page.getViewport(1); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, viewport: viewport, canvasFactory: factory }; console.log("read " + pageNumber); page.render(renderContext).promise .then(() => { console.log("saving " + pageNumber); let stream = canvasAndContext.canvas.createPNGStream(); let pngFile = `${file.substring(0, file.length - ".pdf".length)}-${pageNumber}.PNG`; let out = fs.createWriteStream(pngFile); stream.pipe(out); out.on("finish", () => { console.log(`Success! Saved to ${pngFile}`); let name = path.basename(pngFile); res.send({ path: "/files/" + name, width: viewport.width, height: viewport.height }); }); }, (reason: string) => { console.error(reason + ` ${pageNumber}`); }); }); }); } // anyone attempting to navigate to localhost at this port will // first have to login addSecureRoute( Method.GET, (user, res) => res.redirect(RouteStore.home), undefined, RouteStore.root ); addSecureRoute( Method.GET, async (_, res) => { const cursor = await Database.Instance.query({}, { email: 1, userDocumentId: 1 }, "users"); const results = await cursor.toArray(); res.send(results.map(user => ({ email: user.email, userDocumentId: user.userDocumentId }))); }, undefined, RouteStore.getUsers ); addSecureRoute( Method.GET, (user, res, req) => { let detector = new mobileDetect(req.headers['user-agent'] || ""); let filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html'; res.sendFile(path.join(__dirname, '../../deploy/' + filename)); }, undefined, RouteStore.home, RouteStore.openDocumentWithId ); addSecureRoute( Method.GET, (user, res) => res.send(user.userDocumentId || ""), undefined, RouteStore.getUserDocumentId, ); addSecureRoute( Method.GET, (user, res) => res.send(JSON.stringify({ id: user.id, email: user.email })), undefined, RouteStore.getCurrUser ); const ServicesApiKeyMap = new Map