diff options
author | Bob Zeleznik <zzzman@gmail.com> | 2019-05-17 21:52:49 -0400 |
---|---|---|
committer | Bob Zeleznik <zzzman@gmail.com> | 2019-05-17 21:52:49 -0400 |
commit | 618b4a42795b59cde47510b86b6e25dc03e15935 (patch) | |
tree | f10a9f093df478db15e94fbf8992a32fe8ba99d0 /src/server/index.ts | |
parent | 19fca408a19c5f7a759ff6c3bfefe27b96ec3563 (diff) | |
parent | 4e244951b7b18d7973360f423e8de80c42466228 (diff) |
merged
Diffstat (limited to 'src/server/index.ts')
-rw-r--r-- | src/server/index.ts | 426 |
1 files changed, 312 insertions, 114 deletions
diff --git a/src/server/index.ts b/src/server/index.ts index 0d0b65b22..da6bc0165 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,68 +1,66 @@ -import * as express from 'express' -const app = express() -import * as webpack from 'webpack' -import * as wdm from 'webpack-dev-middleware'; -import * as whm from 'webpack-hot-middleware'; -import * as path from 'path' -import * as formidable from 'formidable' +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 mobileDetect from 'mobile-detect'; +import { ObservableMap } from 'mobx'; import * as passport from 'passport'; -import { MessageStore, Message, SetFieldArgs, GetFieldArgs, Transferable } from "./Message"; -import { Client } from './Client'; +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 { ObservableMap } from 'mobx'; -import { FieldId, Field } from '../fields/Field'; +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 { ServerUtils } from './ServerUtil'; -import { ObjectID } from 'mongodb'; -import { Document } from '../fields/Document'; -import * as io from 'socket.io' -import * as passportConfig from './authentication/config/passport'; -import { getLogin, postLogin, getSignup, postSignup } from './authentication/controllers/user'; +import { MessageStore, Transferable, Types, Diff } from "./Message"; +import { RouteStore } from './RouteStore'; +const app = express(); const config = require('../../webpack.config'); const compiler = webpack(config); const port = 1050; // default port to listen -const serverPort = 1234; -import * as expressValidator from 'express-validator'; +const serverPort = 4321; import expressFlash = require('express-flash'); -import * as bodyParser from 'body-parser'; -import * as session from 'express-session'; +import flash = require('connect-flash'); import c = require("crypto"); +import { Search } from './Search'; +import { debug } from 'util'; +import _ = require('lodash'); const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); -const bluebird = require('bluebird'); -import { performance } from 'perf_hooks' -import * as fs from 'fs'; -import * as request from 'request' -const download = (url: string, dest: fs.PathLike) => { - request.get(url).pipe(fs.createWriteStream(dest)); -} +const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest)); const mongoUrl = 'mongodb://localhost:27017/Dash'; -// mongoose.Promise = bluebird; -mongoose.connect(mongoUrl)//.then( -// () => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ }, -// ).catch((err: any) => { -// console.log("MongoDB connection error. Please make sure MongoDB is running. " + err); -// process.exit(); -// }); -mongoose.connection.on('connected', function () { - console.log("connected"); -}) +mongoose.connect(mongoUrl); +mongoose.connection.on('connected', () => console.log("connected")); -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: true })); -app.use(expressValidator()); -app.use(expressFlash()); -app.use(require('express-session')({ - secret: `${c.randomBytes(64)}`, +// 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' - }) + store: new MongoStore({ url: 'mongodb://localhost:27017/Dash' }) })); + +app.use(flash()); +app.use(expressFlash()); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(expressValidator()); app.use(passport.initialize()); app.use(passport.session()); app.use((req, res, next) => { @@ -70,111 +68,311 @@ app.use((req, res, next) => { next(); }); -app.get("/signup", getSignup); -app.post("/signup", postSignup); -app.get("/login", getLogin); -app.post("/login", postLogin); - -// IMAGE UPLOADING HANDLER -app.post("/upload", (req, res, err) => { - let form = new formidable.IncomingForm() - form.uploadDir = __dirname + "/public/files/" - form.keepExtensions = true - // let path = req.body.path; - console.log("upload") - form.parse(req, (err, fields, files) => { - console.log("parsing") - let names: any[] = []; - for (const name in files) { - let file = files[name]; - names.push(`/files/` + path.basename(file.path)); +app.get("/hello", (req, res) => res.send("<p>Hello</p>")); + +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) => any = (res) => res.redirect(RouteStore.logout), + ...subscribers: string[] +) { + let abstracted = (req: express.Request, res: express.Response) => { + if (req.user) { + handler(req.user, res, req); + } else { + onRejection(res); + } + }; + subscribers.forEach(route => { + switch (method) { + case Method.GET: + app.get(route, abstracted); + break; + case Method.POST: + app.post(route, abstracted); + break; } - res.send(names); }); -}) +} -app.use(express.static(__dirname + '/public')); -app.use('/images', express.static(__dirname + '/public')) +// STATIC FILE SERVING +app.use(express.static(__dirname + RouteStore.public)); +app.use(RouteStore.images, express.static(__dirname + RouteStore.public)); -let FieldStore: ObservableMap<FieldId, Field> = new ObservableMap(); +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("/"); + })); -// define a route handler for the default home page -app.get("/", (req, res) => { - res.sendFile(path.join(__dirname, '../../deploy/index.html')); -}); +// SEARCH -app.get("/hello", (req, res) => { - res.send("<p>Hello</p>"); -}) +// GETTERS -app.use("/corsProxy", (req, res) => { - req.pipe(request(req.url.substring(1))).pipe(res); +app.get("/search", async (req, res) => { + let query = req.query.query || "hello"; + let results = await Search.Instance.search(query); + res.send(results); }); -app.get("/delete", (req, res) => { - deleteAll(); - res.redirect("/"); -}); +// 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, + (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 +); -app.use(wdm(compiler, { - publicPath: config.output.publicPath -})) +addSecureRoute( + Method.GET, + (user, res) => res.send(user.userDocumentId || ""), + undefined, + RouteStore.getUserDocumentId, +); -app.use(whm(compiler)) +addSecureRoute( + Method.GET, + (user, res) => res.send(JSON.stringify({ id: user.id, email: user.email })), + undefined, + RouteStore.getCurrUser +); + +// SETTERS + +addSecureRoute( + Method.POST, + (user, res, req) => { + let form = new formidable.IncomingForm(); + form.uploadDir = __dirname + "/public/files/"; + form.keepExtensions = true; + // let path = req.body.path; + console.log("upload"); + form.parse(req, (err, fields, files) => { + console.log("parsing"); + let names: string[] = []; + for (const name in files) { + names.push(`/files/` + path.basename(files[name].path)); + } + res.send(names); + }); + }, + undefined, + RouteStore.upload +); + +// AUTHENTICATION + +// Sign Up +app.get(RouteStore.signup, getSignup); +app.post(RouteStore.signup, postSignup); + +// Log In +app.get(RouteStore.login, getLogin); +app.post(RouteStore.login, postLogin); + +// Log Out +app.get(RouteStore.logout, getLogout); + +// FORGOT PASSWORD EMAIL HANDLING +app.get(RouteStore.forgot, getForgot); +app.post(RouteStore.forgot, postForgot); + +// RESET PASSWORD EMAIL HANDLING +app.get(RouteStore.reset, getReset); +app.post(RouteStore.reset, postReset); + +app.use(RouteStore.corsProxy, (req, res) => + req.pipe(request(req.url.substring(1))).pipe(res)); + +app.get(RouteStore.delete, (req, res) => + deleteFields().then(() => res.redirect(RouteStore.home))); + +app.get(RouteStore.deleteAll, (req, res) => + deleteAll().then(() => res.redirect(RouteStore.home))); + +app.use(wdm(compiler, { publicPath: config.output.publicPath })); + +app.use(whm(compiler)); // start the Express server -app.listen(port, () => { - console.log(`server started at http://localhost:${port}`); -}) +app.listen(port, () => + console.log(`server started at http://localhost:${port}`)); const server = io(); interface Map { [key: string]: Client; } -let clients: Map = {} +let clients: Map = {}; server.on("connection", function (socket: Socket) { - console.log("a user has connected") + console.log("a user has connected"); - Utils.Emit(socket, MessageStore.Foo, "handshooken") + Utils.Emit(socket, MessageStore.Foo, "handshooken"); - Utils.AddServerHandler(socket, MessageStore.Bar, barReceived) - Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args)) - Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField) - Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields) - Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteAll) -}) + Utils.AddServerHandler(socket, MessageStore.Bar, barReceived); + Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args)); + Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField); + Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields); + Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields); -function deleteAll() { - Database.Instance.deleteAll(); + Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField); + Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff)); + Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField); + Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields); +}); + +async function deleteFields() { + await Database.Instance.deleteAll(); + await Search.Instance.clear(); + await Database.Instance.deleteAll('newDocuments'); +} + +async function deleteAll() { + await Database.Instance.deleteAll(); + await Database.Instance.deleteAll('newDocuments'); + await Database.Instance.deleteAll('sessions'); + await Database.Instance.deleteAll('users'); + await Search.Instance.clear(); } function barReceived(guid: String) { clients[guid.toString()] = new Client(guid.toString()); } -function addDocument(document: Document) { +function getField([id, callback]: [string, (result?: Transferable) => void]) { + Database.Instance.getDocument(id, (result?: Transferable) => + callback(result ? result : undefined)); +} +function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) { + Database.Instance.getDocuments(ids, callback); } -function getField([id, callback]: [string, (result: any) => void]) { - Database.Instance.getDocument(id, (result: any) => { - if (result) { - callback(result) +function setField(socket: Socket, newValue: Transferable) { + Database.Instance.update(newValue.id, newValue, () => + socket.broadcast.emit(MessageStore.SetField.Message, newValue)); + if (newValue.type === Types.Text) { + Search.Instance.updateDocument({ id: newValue.id, data: (newValue as any).data }); + console.log("set field"); + } +} + +function GetRefField([id, callback]: [string, (result?: Transferable) => void]) { + Database.Instance.getDocument(id, callback, "newDocuments"); +} + +function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) { + Database.Instance.getDocuments(ids, callback, "newDocuments"); +} + + +const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = { + "number": "_n", + "string": "_t", + // "boolean": "_b", + "image": ["_t", "url"], + "video": ["_t", "url"], + "pdf": ["_t", "url"], + "audio": ["_t", "url"], + "web": ["_t", "url"], + "date": ["_d", value => new Date(value.date).toISOString()], + "proxy": ["_i", "fieldId"], + "list": ["_l", list => { + const results = []; + for (const value of list.fields) { + const term = ToSearchTerm(value); + if (term) { + results.push(term.value); + } } - else { - callback(undefined) + return results.length ? results : null; + }] +}; + +function ToSearchTerm(val: any): { suffix: string, value: any } | undefined { + if (val === null || val === undefined) { + return; + } + const type = val.__type || typeof val; + let suffix = suffixMap[type]; + if (!suffix) { + return; + } + + if (Array.isArray(suffix)) { + const accessor = suffix[1]; + if (typeof accessor === "function") { + val = accessor(val); + } else { + val = val[accessor]; } - }) + suffix = suffix[0]; + } + + return { suffix, value: val }; } -function getFields([ids, callback]: [string[], (result: any) => void]) { - Database.Instance.getDocuments(ids, callback); +function getSuffix(value: string | [string, any]): string { + return typeof value === "string" ? value : value[0]; } -function setField(socket: Socket, newValue: Transferable) { - Database.Instance.update(newValue._id, newValue) - socket.broadcast.emit(MessageStore.SetField.Message, newValue) +function UpdateField(socket: Socket, diff: Diff) { + Database.Instance.update(diff.id, diff.diff, + () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false, "newDocuments"); + const docfield = diff.diff.$set; + if (!docfield) { + return; + } + const update: any = { id: diff.id }; + let dynfield = false; + for (let key in docfield) { + if (!key.startsWith("fields.")) continue; + dynfield = true; + let val = docfield[key]; + key = key.substring(7); + Object.values(suffixMap).forEach(suf => update[key + getSuffix(suf)] = { set: null }); + let term = ToSearchTerm(val); + if (term !== undefined) { + let { suffix, value } = term; + update[key + suffix] = { set: value }; + } + } + if (dynfield) { + Search.Instance.updateDocument(update); + } +} + +function CreateField(newValue: any) { + Database.Instance.insert(newValue, "newDocuments"); } server.listen(serverPort); |