| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 | 
							- import config from "config";
 
- import async from "async";
 
- import request from "request";
 
- import cors from "cors";
 
- import cookieParser from "cookie-parser";
 
- import bodyParser from "body-parser";
 
- import express from "express";
 
- import { OAuth2 } from "oauth";
 
- import CoreClass from "../core";
 
- class AppModule extends CoreClass {
 
- 	constructor() {
 
- 		super("app");
 
- 	}
 
- 	initialize() {
 
- 		return new Promise(resolve => {
 
- 			const { mail } = this.moduleManager.modules;
 
- 			const { cache } = this.moduleManager.modules;
 
- 			const { db } = this.moduleManager.modules;
 
- 			const { activities } = this.moduleManager.modules;
 
- 			this.utils = this.moduleManager.modules.utils;
 
- 			const app = (this.app = express());
 
- 			const SIDname = config.get("cookie.SIDname");
 
- 			this.server = app.listen(config.get("serverPort"));
 
- 			app.use(cookieParser());
 
- 			app.use(bodyParser.json());
 
- 			app.use(bodyParser.urlencoded({ extended: true }));
 
- 			let userModel;
 
- 			db.runJob("GET_MODEL", { modelName: "user" })
 
- 				.then(model => {
 
- 					userModel = model;
 
- 				})
 
- 				.catch(console.error);
 
- 			const corsOptions = { ...config.get("cors") };
 
- 			app.use(cors(corsOptions));
 
- 			app.options("*", cors(corsOptions));
 
- 			const oauth2 = new OAuth2(
 
- 				config.get("apis.github.client"),
 
- 				config.get("apis.github.secret"),
 
- 				"https://github.com/",
 
- 				"login/oauth/authorize",
 
- 				"login/oauth/access_token",
 
- 				null
 
- 			);
 
- 			const redirectUri = `${config.get("serverDomain")}/auth/github/authorize/callback`;
 
- 			/**
 
- 			 * @param {object} res - response object from Express
 
- 			 * @param {string} err - custom error message
 
- 			 */
 
- 			function redirectOnErr(res, err) {
 
- 				res.redirect(`${config.get("domain")}/?err=${encodeURIComponent(err)}`);
 
- 			}
 
- 			app.get("/auth/github/authorize", async (req, res) => {
 
- 				if (this.getStatus() !== "READY") {
 
- 					this.log(
 
- 						"INFO",
 
- 						"APP_REJECTED_GITHUB_AUTHORIZE",
 
- 						`A user tried to use github authorize, but the APP module is currently not ready.`
 
- 					);
 
- 					return redirectOnErr(res, "Something went wrong on our end. Please try again later.");
 
- 				}
 
- 				const params = [
 
- 					`client_id=${config.get("apis.github.client")}`,
 
- 					`redirect_uri=${config.get("serverDomain")}/auth/github/authorize/callback`,
 
- 					`scope=user:email`
 
- 				].join("&");
 
- 				return res.redirect(`https://github.com/login/oauth/authorize?${params}`);
 
- 			});
 
- 			app.get("/auth/github/link", async (req, res) => {
 
- 				if (this.getStatus() !== "READY") {
 
- 					this.log(
 
- 						"INFO",
 
- 						"APP_REJECTED_GITHUB_AUTHORIZE",
 
- 						`A user tried to use github authorize, but the APP module is currently not ready.`
 
- 					);
 
- 					return redirectOnErr(res, "Something went wrong on our end. Please try again later.");
 
- 				}
 
- 				const params = [
 
- 					`client_id=${config.get("apis.github.client")}`,
 
- 					`redirect_uri=${config.get("serverDomain")}/auth/github/authorize/callback`,
 
- 					`scope=user:email`,
 
- 					`state=${req.cookies[SIDname]}`
 
- 				].join("&");
 
- 				return res.redirect(`https://github.com/login/oauth/authorize?${params}`);
 
- 			});
 
- 			app.get("/auth/github/authorize/callback", async (req, res) => {
 
- 				if (this.getStatus() !== "READY") {
 
- 					this.log(
 
- 						"INFO",
 
- 						"APP_REJECTED_GITHUB_AUTHORIZE",
 
- 						`A user tried to use github authorize, but the APP module is currently not ready.`
 
- 					);
 
- 					return redirectOnErr(res, "Something went wrong on our end. Please try again later.");
 
- 				}
 
- 				const { code } = req.query;
 
- 				let accessToken;
 
- 				let body;
 
- 				let address;
 
- 				const { state } = req.query;
 
- 				const verificationToken = await this.utils.runJob("GENERATE_RANDOM_STRING", { length: 64 });
 
- 				return async.waterfall(
 
- 					[
 
- 						next => {
 
- 							if (req.query.error) return next(req.query.error_description);
 
- 							return next();
 
- 						},
 
- 						next => {
 
- 							oauth2.getOAuthAccessToken(code, { redirect_uri: redirectUri }, next);
 
- 						},
 
- 						(_accessToken, refreshToken, results, next) => {
 
- 							if (results.error) return next(results.error_description);
 
- 							accessToken = _accessToken;
 
- 							return request.get(
 
- 								{
 
- 									url: `https://api.github.com/user`,
 
- 									headers: {
 
- 										"User-Agent": "request",
 
- 										Authorization: `token ${accessToken}`
 
- 									}
 
- 								},
 
- 								next
 
- 							);
 
- 						},
 
- 						(httpResponse, _body, next) => {
 
- 							body = _body = JSON.parse(_body);
 
- 							if (httpResponse.statusCode !== 200) return next(body.message);
 
- 							if (state) {
 
- 								return async.waterfall(
 
- 									[
 
- 										next => {
 
- 											cache
 
- 												.runJob("HGET", {
 
- 													table: "sessions",
 
- 													key: state
 
- 												})
 
- 												.then(session => next(null, session))
 
- 												.catch(next);
 
- 										},
 
- 										(session, next) => {
 
- 											if (!session) return next("Invalid session.");
 
- 											return userModel.findOne({ _id: session.userId }, next);
 
- 										},
 
- 										(user, next) => {
 
- 											if (!user) return next("User not found.");
 
- 											if (user.services.github && user.services.github.id)
 
- 												return next("Account already has GitHub linked.");
 
- 											return userModel.updateOne(
 
- 												{ _id: user._id },
 
- 												{
 
- 													$set: {
 
- 														"services.github": {
 
- 															id: body.id,
 
- 															accessToken
 
- 														}
 
- 													}
 
- 												},
 
- 												{ runValidators: true },
 
- 												err => {
 
- 													if (err) return next(err);
 
- 													return next(null, user, body);
 
- 												}
 
- 											);
 
- 										},
 
- 										user => {
 
- 											cache.runJob("PUB", {
 
- 												channel: "user.linkGithub",
 
- 												value: user._id
 
- 											});
 
- 											res.redirect(`${config.get("domain")}/settings#security`);
 
- 										}
 
- 									],
 
- 									next
 
- 								);
 
- 							}
 
- 							if (!body.id) return next("Something went wrong, no id.");
 
- 							return userModel.findOne({ "services.github.id": body.id }, (err, user) => {
 
- 								next(err, user, body);
 
- 							});
 
- 						},
 
- 						(user, body, next) => {
 
- 							if (user) {
 
- 								user.services.github.access_token = accessToken;
 
- 								return user.save(() => next(true, user._id));
 
- 							}
 
- 							return userModel.findOne({ username: new RegExp(`^${body.login}$`, "i") }, (err, user) =>
 
- 								next(err, user)
 
- 							);
 
- 						},
 
- 						(user, next) => {
 
- 							if (user) return next(`An account with that username already exists.`);
 
- 							return request.get(
 
- 								{
 
- 									url: `https://api.github.com/user/emails`,
 
- 									headers: {
 
- 										"User-Agent": "request",
 
- 										Authorization: `token ${accessToken}`
 
- 									}
 
- 								},
 
- 								next
 
- 							);
 
- 						},
 
- 						(httpResponse, body2, next) => {
 
- 							body2 = JSON.parse(body2);
 
- 							if (!Array.isArray(body2)) return next(body2.message);
 
- 							body2.forEach(email => {
 
- 								if (email.primary) address = email.email.toLowerCase();
 
- 							});
 
- 							return userModel.findOne({ "email.address": address }, next);
 
- 						},
 
- 						(user, next) => {
 
- 							this.utils
 
- 								.runJob("GENERATE_RANDOM_STRING", {
 
- 									length: 12
 
- 								})
 
- 								.then(_id => next(null, user, _id));
 
- 						},
 
- 						(user, _id, next) => {
 
- 							if (user) {
 
- 								if (Object.keys(JSON.parse(user.services.github)).length === 0)
 
- 									return next(
 
- 										`An account with that email address exists, but is not linked to GitHub.`
 
- 									);
 
- 								return next(`An account with that email address already exists.`);
 
- 							}
 
- 							return next(null, {
 
- 								_id, // TODO Check if exists
 
- 								username: body.login,
 
- 								name: body.name,
 
- 								location: body.location,
 
- 								bio: body.bio,
 
- 								email: {
 
- 									address,
 
- 									verificationToken
 
- 								},
 
- 								services: {
 
- 									github: { id: body.id, accessToken }
 
- 								}
 
- 							});
 
- 						},
 
- 						// generate the url for gravatar avatar
 
- 						(user, next) => {
 
- 							this.utils
 
- 								.runJob("CREATE_GRAVATAR", {
 
- 									email: user.email.address
 
- 								})
 
- 								.then(url => {
 
- 									user.avatar = { type: "gravatar", url };
 
- 									next(null, user);
 
- 								});
 
- 						},
 
- 						// save the new user to the database
 
- 						(user, next) => {
 
- 							userModel.create(user, next);
 
- 						},
 
- 						// add the activity of account creation
 
- 						(user, next) => {
 
- 							activities.runJob("ADD_ACTIVITY", {
 
- 								userId: user._id,
 
- 								activityType: "created_account"
 
- 							});
 
- 							next(null, user);
 
- 						},
 
- 						(user, next) => {
 
- 							mail.runJob("GET_SCHEMA", {
 
- 								schemaName: "verifyEmail"
 
- 							}).then(verifyEmailSchema => {
 
- 								verifyEmailSchema(address, body.login, user.email.verificationToken, err => {
 
- 									next(err, user._id);
 
- 								});
 
- 							});
 
- 						}
 
- 					],
 
- 					async (err, userId) => {
 
- 						if (err && err !== true) {
 
- 							err = await this.utils.runJob("GET_ERROR", {
 
- 								error: err
 
- 							});
 
- 							this.log(
 
- 								"ERROR",
 
- 								"AUTH_GITHUB_AUTHORIZE_CALLBACK",
 
- 								`Failed to authorize with GitHub. "${err}"`
 
- 							);
 
- 							return redirectOnErr(res, err);
 
- 						}
 
- 						const sessionId = await this.utils.runJob("GUID", {});
 
- 						const sessionSchema = await cache.runJob("GET_SCHEMA", {
 
- 							schemaName: "session"
 
- 						});
 
- 						return cache
 
- 							.runJob("HSET", {
 
- 								table: "sessions",
 
- 								key: sessionId,
 
- 								value: sessionSchema(sessionId, userId)
 
- 							})
 
- 							.then(() => {
 
- 								const date = new Date();
 
- 								date.setTime(new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000);
 
- 								res.cookie(SIDname, sessionId, {
 
- 									expires: date,
 
- 									secure: config.get("cookie.secure"),
 
- 									path: "/",
 
- 									domain: config.get("cookie.domain")
 
- 								});
 
- 								this.log(
 
- 									"INFO",
 
- 									"AUTH_GITHUB_AUTHORIZE_CALLBACK",
 
- 									`User "${userId}" successfully authorized with GitHub.`
 
- 								);
 
- 								res.redirect(`${config.get("domain")}/`);
 
- 							})
 
- 							.catch(err => redirectOnErr(res, err.message));
 
- 					}
 
- 				);
 
- 			});
 
- 			app.get("/auth/verify_email", async (req, res) => {
 
- 				if (this.getStatus() !== "READY") {
 
- 					this.log(
 
- 						"INFO",
 
- 						"APP_REJECTED_GITHUB_AUTHORIZE",
 
- 						`A user tried to use github authorize, but the APP module is currently not ready.`
 
- 					);
 
- 					return redirectOnErr(res, "Something went wrong on our end. Please try again later.");
 
- 				}
 
- 				const { code } = req.query;
 
- 				return async.waterfall(
 
- 					[
 
- 						next => {
 
- 							if (!code) return next("Invalid code.");
 
- 							return next();
 
- 						},
 
- 						next => {
 
- 							userModel.findOne({ "email.verificationToken": code }, next);
 
- 						},
 
- 						(user, next) => {
 
- 							if (!user) return next("User not found.");
 
- 							if (user.email.verified) return next("This email is already verified.");
 
- 							return userModel.updateOne(
 
- 								{ "email.verificationToken": code },
 
- 								{
 
- 									$set: { "email.verified": true },
 
- 									$unset: { "email.verificationToken": "" }
 
- 								},
 
- 								{ runValidators: true },
 
- 								next
 
- 							);
 
- 						}
 
- 					],
 
- 					err => {
 
- 						if (err) {
 
- 							let error = "An error occurred.";
 
- 							if (typeof err === "string") error = err;
 
- 							else if (err.message) error = err.message;
 
- 							this.log("ERROR", "VERIFY_EMAIL", `Verifying email failed. "${error}"`);
 
- 							return res.json({
 
- 								status: "failure",
 
- 								message: error
 
- 							});
 
- 						}
 
- 						this.log("INFO", "VERIFY_EMAIL", `Successfully verified email.`);
 
- 						return res.redirect(`${config.get("domain")}?msg=Thank you for verifying your email`);
 
- 					}
 
- 				);
 
- 			});
 
- 			return resolve();
 
- 		});
 
- 	}
 
- 	SERVER() {
 
- 		return new Promise(resolve => {
 
- 			resolve(this.server);
 
- 		});
 
- 	}
 
- 	GET_APP() {
 
- 		return new Promise(resolve => {
 
- 			resolve({ app: this.app });
 
- 		});
 
- 	}
 
- 	// EXAMPLE_JOB() {
 
- 	// 	return new Promise((resolve, reject) => {
 
- 	// 		if (true) resolve({});
 
- 	// 		else reject(new Error("Nothing changed."));
 
- 	// 	});
 
- 	// }
 
- }
 
- export default new AppModule();
 
 
  |