|
@@ -1,23 +1,13 @@
|
|
|
import config from "config";
|
|
|
-import axios from "axios";
|
|
|
-import async from "async";
|
|
|
import cors from "cors";
|
|
|
import cookieParser from "cookie-parser";
|
|
|
import bodyParser from "body-parser";
|
|
|
import express from "express";
|
|
|
-import oauth from "oauth";
|
|
|
import http from "http";
|
|
|
import CoreClass from "../core";
|
|
|
|
|
|
-const { OAuth2 } = oauth;
|
|
|
-
|
|
|
let AppModule;
|
|
|
-let MailModule;
|
|
|
-let CacheModule;
|
|
|
-let DBModule;
|
|
|
-let ActivitiesModule;
|
|
|
-let PlaylistsModule;
|
|
|
-let UtilsModule;
|
|
|
+let UsersModule;
|
|
|
|
|
|
class _AppModule extends CoreClass {
|
|
|
// eslint-disable-next-line require-jsdoc
|
|
@@ -33,12 +23,7 @@ class _AppModule extends CoreClass {
|
|
|
*/
|
|
|
initialize() {
|
|
|
return new Promise(resolve => {
|
|
|
- MailModule = this.moduleManager.modules.mail;
|
|
|
- CacheModule = this.moduleManager.modules.cache;
|
|
|
- DBModule = this.moduleManager.modules.db;
|
|
|
- ActivitiesModule = this.moduleManager.modules.activities;
|
|
|
- PlaylistsModule = this.moduleManager.modules.playlists;
|
|
|
- UtilsModule = this.moduleManager.modules.utils;
|
|
|
+ UsersModule = this.moduleManager.modules.users;
|
|
|
|
|
|
const app = (this.app = express());
|
|
|
const SIDname = config.get("cookie");
|
|
@@ -49,13 +34,6 @@ class _AppModule extends CoreClass {
|
|
|
app.use(bodyParser.json());
|
|
|
app.use(bodyParser.urlencoded({ extended: true }));
|
|
|
|
|
|
- let userModel;
|
|
|
- DBModule.runJob("GET_MODEL", { modelName: "user" })
|
|
|
- .then(model => {
|
|
|
- userModel = model;
|
|
|
- })
|
|
|
- .catch(console.error);
|
|
|
-
|
|
|
const appUrl = `${config.get("url.secure") ? "https" : "http"}://${config.get("url.host")}`;
|
|
|
|
|
|
const corsOptions = JSON.parse(JSON.stringify(config.get("cors")));
|
|
@@ -74,15 +52,6 @@ class _AppModule extends CoreClass {
|
|
|
}
|
|
|
|
|
|
if (config.get("apis.github.enabled")) {
|
|
|
- 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("apis.github.redirect_uri").length > 0
|
|
|
? config.get("apis.github.redirect_uri")
|
|
@@ -120,7 +89,7 @@ class _AppModule extends CoreClass {
|
|
|
`client_id=${config.get("apis.github.client")}`,
|
|
|
`redirect_uri=${redirectUri}`,
|
|
|
`scope=user:email`,
|
|
|
- `state=${req.cookies[SIDname]}`
|
|
|
+ `state=${req.cookies[SIDname]}` // TODO don't do this
|
|
|
].join("&");
|
|
|
return res.redirect(`https://github.com/login/oauth/authorize?${params}`);
|
|
|
});
|
|
@@ -133,384 +102,74 @@ class _AppModule extends CoreClass {
|
|
|
`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.");
|
|
|
+ redirectOnErr(res, "Something went wrong on our end. Please try again later.");
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- const { code } = req.query;
|
|
|
- let accessToken;
|
|
|
- let body;
|
|
|
- let address;
|
|
|
-
|
|
|
- const { state } = req.query;
|
|
|
-
|
|
|
- const verificationToken = await UtilsModule.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;
|
|
|
-
|
|
|
- const options = {
|
|
|
- headers: {
|
|
|
- "User-Agent": "request",
|
|
|
- Authorization: `token ${accessToken}`
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- return axios
|
|
|
- .get("https://api.github.com/user", options)
|
|
|
- .then(github => next(null, github))
|
|
|
- .catch(err => next(err));
|
|
|
- },
|
|
|
-
|
|
|
- (github, next) => {
|
|
|
- if (github.status !== 200) return next(github.data.message);
|
|
|
-
|
|
|
- if (state) {
|
|
|
- return async.waterfall(
|
|
|
- [
|
|
|
- next => {
|
|
|
- CacheModule.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: github.data.id,
|
|
|
- access_token: accessToken
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- { runValidators: true },
|
|
|
- err => {
|
|
|
- if (err) return next(err);
|
|
|
- return next(null, user, github.data);
|
|
|
- }
|
|
|
- );
|
|
|
- },
|
|
|
-
|
|
|
- user => {
|
|
|
- CacheModule.runJob("PUB", {
|
|
|
- channel: "user.linkGithub",
|
|
|
- value: user._id
|
|
|
- });
|
|
|
-
|
|
|
- CacheModule.runJob("PUB", {
|
|
|
- channel: "user.updated",
|
|
|
- value: { userId: user._id }
|
|
|
- });
|
|
|
-
|
|
|
- res.redirect(`${appUrl}/settings?tab=security`);
|
|
|
- }
|
|
|
- ],
|
|
|
- next
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- if (!github.data.id) return next("Something went wrong, no id.");
|
|
|
-
|
|
|
- return userModel.findOne({ "services.github.id": github.data.id }, (err, user) => {
|
|
|
- next(err, user, github.data);
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- (user, _body, next) => {
|
|
|
- body = _body;
|
|
|
-
|
|
|
- if (user) {
|
|
|
- user.services.github.access_token = accessToken;
|
|
|
- return user.save(() => next(true, user._id));
|
|
|
- }
|
|
|
+ const { code, state, error, error_description: errorDescription } = req.query;
|
|
|
|
|
|
- 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 axios
|
|
|
- .get("https://api.github.com/user/emails", {
|
|
|
- headers: {
|
|
|
- "User-Agent": "request",
|
|
|
- Authorization: `token ${accessToken}`
|
|
|
- }
|
|
|
- })
|
|
|
- .then(res => next(null, res.data))
|
|
|
- .catch(err => next(err));
|
|
|
- },
|
|
|
-
|
|
|
- (body, next) => {
|
|
|
- if (!Array.isArray(body)) return next(body.message);
|
|
|
-
|
|
|
- body.forEach(email => {
|
|
|
- if (email.primary) address = email.email.toLowerCase();
|
|
|
- });
|
|
|
+ // GITHUB_AUTHORIZE_CALLBACK job handles login/register/linking
|
|
|
+ UsersModule.runJob("GITHUB_AUTHORIZE_CALLBACK", { code, state, error, errorDescription })
|
|
|
+ .then(({ redirectUrl, sessionId, userId }) => {
|
|
|
+ if (sessionId) {
|
|
|
+ const date = new Date();
|
|
|
+ date.setTime(new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000);
|
|
|
|
|
|
- return userModel.findOne({ "email.address": address }, next);
|
|
|
- },
|
|
|
-
|
|
|
- (user, next) => {
|
|
|
- UtilsModule.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,
|
|
|
- username: body.login,
|
|
|
- name: body.name,
|
|
|
- location: body.location,
|
|
|
- bio: body.bio,
|
|
|
- email: {
|
|
|
- address,
|
|
|
- verificationToken
|
|
|
- },
|
|
|
- services: {
|
|
|
- github: {
|
|
|
- id: body.id,
|
|
|
- access_token: accessToken
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- // generate the url for gravatar avatar
|
|
|
- (user, next) => {
|
|
|
- UtilsModule.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);
|
|
|
- },
|
|
|
-
|
|
|
- (user, next) => {
|
|
|
- MailModule.runJob("GET_SCHEMA", {
|
|
|
- schemaName: "verifyEmail"
|
|
|
- }).then(verifyEmailSchema => {
|
|
|
- verifyEmailSchema(address, body.login, user.email.verificationToken, err => {
|
|
|
- next(err, user._id);
|
|
|
- });
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- // create a liked songs playlist for the new user
|
|
|
- (userId, next) => {
|
|
|
- PlaylistsModule.runJob("CREATE_USER_PLAYLIST", {
|
|
|
- userId,
|
|
|
- displayName: "Liked Songs",
|
|
|
- type: "user-liked"
|
|
|
- })
|
|
|
- .then(likedSongsPlaylist => {
|
|
|
- next(null, likedSongsPlaylist, userId);
|
|
|
- })
|
|
|
- .catch(err => next(err));
|
|
|
- },
|
|
|
-
|
|
|
- // create a disliked songs playlist for the new user
|
|
|
- (likedSongsPlaylist, userId, next) => {
|
|
|
- PlaylistsModule.runJob("CREATE_USER_PLAYLIST", {
|
|
|
- userId,
|
|
|
- displayName: "Disliked Songs",
|
|
|
- type: "user-disliked"
|
|
|
- })
|
|
|
- .then(dislikedSongsPlaylist => {
|
|
|
- next(
|
|
|
- null,
|
|
|
- {
|
|
|
- likedSongsPlaylist,
|
|
|
- dislikedSongsPlaylist
|
|
|
- },
|
|
|
- userId
|
|
|
- );
|
|
|
- })
|
|
|
- .catch(err => next(err));
|
|
|
- },
|
|
|
-
|
|
|
- // associate liked + disliked songs playlist to the user object
|
|
|
- ({ likedSongsPlaylist, dislikedSongsPlaylist }, userId, next) => {
|
|
|
- userModel.updateOne(
|
|
|
- { _id: userId },
|
|
|
- {
|
|
|
- $set: {
|
|
|
- likedSongsPlaylist,
|
|
|
- dislikedSongsPlaylist
|
|
|
- }
|
|
|
- },
|
|
|
- { runValidators: true },
|
|
|
- err => {
|
|
|
- if (err) return next(err);
|
|
|
- return next(null, userId);
|
|
|
- }
|
|
|
- );
|
|
|
- },
|
|
|
-
|
|
|
- // add the activity of account creation
|
|
|
- (userId, next) => {
|
|
|
- ActivitiesModule.runJob("ADD_ACTIVITY", {
|
|
|
- userId,
|
|
|
- type: "user__joined",
|
|
|
- payload: { message: "Welcome to Musare!" }
|
|
|
- });
|
|
|
-
|
|
|
- next(null, userId);
|
|
|
- }
|
|
|
- ],
|
|
|
- async (err, userId) => {
|
|
|
- if (err && err !== true) {
|
|
|
- err = await UtilsModule.runJob("GET_ERROR", {
|
|
|
- error: err
|
|
|
+ res.cookie(SIDname, sessionId, {
|
|
|
+ expires: date,
|
|
|
+ secure: config.get("url.secure"),
|
|
|
+ path: "/",
|
|
|
+ domain: config.get("url.host")
|
|
|
});
|
|
|
|
|
|
this.log(
|
|
|
- "ERROR",
|
|
|
+ "INFO",
|
|
|
"AUTH_GITHUB_AUTHORIZE_CALLBACK",
|
|
|
- `Failed to authorize with GitHub. "${err}"`
|
|
|
+ `User "${userId}" successfully authorized with GitHub.`
|
|
|
);
|
|
|
-
|
|
|
- return redirectOnErr(res, err);
|
|
|
}
|
|
|
|
|
|
- const sessionId = await UtilsModule.runJob("GUID", {});
|
|
|
- const sessionSchema = await CacheModule.runJob("GET_SCHEMA", {
|
|
|
- schemaName: "session"
|
|
|
- });
|
|
|
-
|
|
|
- return CacheModule.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("url.secure"),
|
|
|
- path: "/",
|
|
|
- domain: config.get("url.host")
|
|
|
- });
|
|
|
-
|
|
|
- this.log(
|
|
|
- "INFO",
|
|
|
- "AUTH_GITHUB_AUTHORIZE_CALLBACK",
|
|
|
- `User "${userId}" successfully authorized with GitHub.`
|
|
|
- );
|
|
|
-
|
|
|
- res.redirect(appUrl);
|
|
|
- })
|
|
|
- .catch(err => redirectOnErr(res, err.message));
|
|
|
- }
|
|
|
- );
|
|
|
+ res.redirect(redirectUrl);
|
|
|
+ })
|
|
|
+ .catch(err => {
|
|
|
+ this.log(
|
|
|
+ "ERROR",
|
|
|
+ "AUTH_GITHUB_AUTHORIZE_CALLBACK",
|
|
|
+ `Failed to authorize with GitHub. "${err.message}"`
|
|
|
+ );
|
|
|
+
|
|
|
+ return redirectOnErr(res, err.message);
|
|
|
+ });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- app.get("/auth/verify_email", async (req, res) => {
|
|
|
+ app.get("/auth/verify_email", (req, res) => {
|
|
|
if (this.getStatus() !== "READY") {
|
|
|
this.log(
|
|
|
"INFO",
|
|
|
"APP_REJECTED_VERIFY_EMAIL",
|
|
|
`A user tried to use verify email, but the APP module is currently not ready.`
|
|
|
);
|
|
|
- return redirectOnErr(res, "Something went wrong on our end. Please try again later.");
|
|
|
+ redirectOnErr(res, "Something went wrong on our end. Please try again later.");
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
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: "error",
|
|
|
- message: error
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
+ UsersModule.runJob("VERIFY_EMAIL", { code })
|
|
|
+ .then(() => {
|
|
|
this.log("INFO", "VERIFY_EMAIL", `Successfully verified email.`);
|
|
|
|
|
|
- return res.redirect(`${appUrl}?toast=Thank you for verifying your email`);
|
|
|
- }
|
|
|
- );
|
|
|
+ res.redirect(`${appUrl}?toast=Thank you for verifying your email`);
|
|
|
+ })
|
|
|
+ .catch(err => {
|
|
|
+ this.log("ERROR", "VERIFY_EMAIL", `Verifying email failed. "${err.message}"`);
|
|
|
+
|
|
|
+ res.json({
|
|
|
+ status: "error",
|
|
|
+ message: err.message
|
|
|
+ });
|
|
|
+ });
|
|
|
});
|
|
|
|
|
|
resolve();
|