123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- const CoreClass = require("../core.js");
- const express = require("express");
- const bodyParser = require("body-parser");
- const cookieParser = require("cookie-parser");
- const cors = require("cors");
- const config = require("config");
- const async = require("async");
- const request = require("request");
- const OAuth2 = require("oauth").OAuth2;
- class AppModule extends CoreClass {
- constructor() {
- super("app");
- }
- initialize() {
- return new Promise(async (resolve, reject) => {
- const mail = this.moduleManager.modules["mail"],
- cache = this.moduleManager.modules["cache"],
- db = this.moduleManager.modules["db"],
- activities = this.moduleManager.modules["activities"];
- this.utils = this.moduleManager.modules["utils"];
- let 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 }));
- const userModel = await db.runJob("GET_MODEL", {
- modelName: "user",
- });
- let corsOptions = Object.assign({}, config.get("cors"));
- app.use(cors(corsOptions));
- app.options("*", cors(corsOptions));
- let oauth2 = new OAuth2(
- config.get("apis.github.client"),
- config.get("apis.github.secret"),
- "https://github.com/",
- "login/oauth/authorize",
- "login/oauth/access_token",
- null
- );
- let redirect_uri =
- config.get("serverDomain") + "/auth/github/authorize/callback";
- 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."
- );
- }
- let params = [
- `client_id=${config.get("apis.github.client")}`,
- `redirect_uri=${config.get(
- "serverDomain"
- )}/auth/github/authorize/callback`,
- `scope=user:email`,
- ].join("&");
- 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."
- );
- }
- let 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("&");
- res.redirect(
- `https://github.com/login/oauth/authorize?${params}`
- );
- });
- function redirectOnErr(res, err) {
- return res.redirect(
- `${config.get("domain")}/?err=${encodeURIComponent(err)}`
- );
- }
- 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."
- );
- }
- let code = req.query.code;
- let access_token;
- let body;
- let address;
- const state = req.query.state;
- const verificationToken = await this.utils.runJob(
- "GENERATE_RANDOM_STRING",
- { length: 64 }
- );
- async.waterfall(
- [
- (next) => {
- if (req.query.error)
- return next(req.query.error_description);
- next();
- },
- (next) => {
- oauth2.getOAuthAccessToken(
- code,
- { redirect_uri },
- next
- );
- },
- (_access_token, refresh_token, results, next) => {
- if (results.error)
- return next(results.error_description);
- access_token = _access_token;
- request.get(
- {
- url: `https://api.github.com/user`,
- headers: {
- "User-Agent": "request",
- Authorization: `token ${access_token}`,
- },
- },
- 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.");
- 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."
- );
- userModel.updateOne(
- { _id: user._id },
- {
- $set: {
- "services.github": {
- id: body.id,
- access_token,
- },
- },
- },
- { runValidators: true },
- (err) => {
- if (err) return next(err);
- next(null, user, body);
- }
- );
- },
- (user) => {
- cache.runJob("PUB", {
- channel: "user.linkGithub",
- value: user._id,
- });
- res.redirect(
- `${config.get(
- "domain"
- )}/settings`
- );
- },
- ],
- next
- );
- }
- if (!body.id)
- return next("Something went wrong, no id.");
- userModel.findOne(
- { "services.github.id": body.id },
- (err, user) => {
- next(err, user, body);
- }
- );
- },
- (user, body, next) => {
- if (user) {
- user.services.github.access_token = access_token;
- return user.save(() => {
- next(true, user._id);
- });
- }
- 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.`
- );
- request.get(
- {
- url: `https://api.github.com/user/emails`,
- headers: {
- "User-Agent": "request",
- Authorization: `token ${access_token}`,
- },
- },
- 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();
- });
- 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.`)
- else
- return next(`An account with that email address already exists.`);
- }
- 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, access_token },
- },
- });
- },
- // 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
- );
- next(null, 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",
- });
- cache
- .runJob("HSET", {
- table: "sessions",
- key: sessionId,
- value: sessionSchema(sessionId, userId),
- })
- .then(() => {
- let 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) => {
- return 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."
- );
- }
- let code = req.query.code;
- async.waterfall(
- [
- (next) => {
- if (!code) return next("Invalid code.");
- 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.");
- 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.`
- );
- res.redirect(
- `${config.get(
- "domain"
- )}?msg=Thank you for verifying your email`
- );
- }
- );
- });
- resolve();
- });
- }
- SERVER(payload) {
- return new Promise((resolve, reject) => {
- resolve(this.server);
- });
- }
- GET_APP(payload) {
- return new Promise((resolve, reject) => {
- resolve({ app: this.app });
- });
- }
- EXAMPLE_JOB(payload) {
- return new Promise((resolve, reject) => {
- if (true) {
- resolve({});
- } else {
- reject(new Error("Nothing changed."));
- }
- });
- }
- }
- module.exports = new AppModule();
|