| 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#security`                                            );                                        },                                    ],                                    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();
 |