aboutsummaryrefslogtreecommitdiff
path: root/src/server/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/index.ts')
-rw-r--r--src/server/index.ts426
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);