Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V1 settings middleware #542

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"mongoose": "^5.9.19",
"mongoose-findorcreate": "^3.0.0",
"mongoose-mongodb-errors": "0.0.2",
"mysql": "^2.18.1",
"passport": "^0.4.1",
"passport-facebook": "^3.0.0",
"passport-github": "^1.1.0",
Expand Down
27 changes: 18 additions & 9 deletions src/middlewares/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,29 +61,38 @@ const googleAuthProvider = (req, res, next) =>

const authProvider = (providerType) => {
return async (req, res, next) => {
const admin = await Admin.findById(req.admin.id).populate("settings");
const { successCallbackUrl, failureCallbackUrl } = admin.settings;
// Temporary check during experimentation to check whether or not the settingsMiddleware
// is enabled. If its enabled then we don't have to use our backup settings
// stored in our database.
if (!req.settings) {
console.log(`No settings from settingsMiddleware: ${req.settings}`);
admin = await Admin.findById(req.admin.id).populate("settings");
req.settings = admin.settings;
} else {
console.log(`Received settings from settingsMiddleware: ${req.settings}`);
}

let provider;
let providerEnabled = false;
const callbacksAvailable =
successCallbackUrl !== null && failureCallbackUrl !== null;
req.settings.successCallbackUrl !== null &&
req.settings.failureCallbackUrl !== null;

switch (providerType) {
case TWITTER_PROVIDER:
provider = admin.settings.twitterAuthProvider;
provider = req.settings.settings.twitterAuthProvider;
providerEnabled = provider && provider.key;
break;
case FACEBOOK_PROVIDER:
provider = admin.settings.facebookAuthProvider;
provider = req.settings.facebookAuthProvider;
providerEnabled = provider && provider.appID;
break;
case GITHUB_PROVIDER:
provider = admin.settings.githubAuthProvider;
provider = req.settings.githubAuthProvider;
providerEnabled = provider && provider.clientID;
break;
case GOOGLE_PROVIDER:
provider = admin.settings.googleAuthProvider;
provider = req.settings.googleAuthProvider;
providerEnabled = provider && provider.clientID;
break;

Expand All @@ -95,8 +104,8 @@ const authProvider = (providerType) => {

if (providerEnabled && callbacksAvailable) {
req.provider = provider;
req.successCallbackUrl = successCallbackUrl;
req.failureCallbackUrl = failureCallbackUrl;
req.successCallbackUrl = req.settings.successCallbackUrl;
req.failureCallbackUrl = req.settings.failureCallbackUrl;

next();
} else {
Expand Down
176 changes: 176 additions & 0 deletions src/middlewares/settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
require("dotenv").config();
const mysql = require("mysql");
const CustomResponse = require("../utils/response");
const settingsHandler = require("../utils/settingsHandler");
const getMockSettings = require("../utils/mocks/settings");
const log = require("debug")("log");

const dbConfig = {
host: process.env.MICROAPI_DB_HOST,
user: process.env.MICROAPI_DB_USER,
password: process.env.MICROAPI_DB_PASSWORD,
database: process.env.MICROAPI_DB_DATABASE,
port: process.env.MICROAPI_DB_PORT,
};

const MAX_CONNECTION_TRIES = 5;

//get settings from external DB or endpoint
//function might be modified to accomodate both sources
const getSettings = async (apiKey) => {
//fool linter
log(apiKey);

let connection;
let connectionTries = 0;

let outsideResolve;
let outsideReject;
const promise = new Promise((resolve, reject) => {
outsideResolve = resolve;
outsideReject = reject;
});

function getConnectionErrorResponse(err) {
let message;

if (connectionTries === MAX_CONNECTION_TRIES) {
message = "Maximum connection tries reached";
} else {
console.log(err);
message = err;
}
return { errors: [{ message }] };
}

function handleDisconnect() {
connectionTries += 1;
// Recreate the connection, since the old one cannot be reused.
connection = mysql.createConnection(dbConfig);

connection.connect((err) => {
// The server is either down or restarting (takes a while sometimes).
if (err) {
if (connectionTries <= MAX_CONNECTION_TRIES) {
console.log("error when connecting to db:", err.message);
// We introduce a delay before attempting to reconnect, to avoid a hot loop,
// and to allow our node script to process asynchronous requests in the meantime.
setTimeout(handleDisconnect, 2000);
} else {
const errorResponse = getConnectionErrorResponse(err);
outsideReject(errorResponse);
}
} else {
// Query the database for the project belonging to the project
connection.query(
"SELECT * FROM user_dashboard_project",
(err, results) => {
if (err) {
if (
err.code === "PROTOCOL_CONNECTION_LOST" ||
connectionTries <= MAX_CONNECTION_TRIES
) {
handleDisconnect();
}
console.log("Query error: ", err);
const errorResponse = getConnectionErrorResponse(err);
outsideReject(errorResponse);
}

console.log(results);
//mock the request for now with mocksettings
//settings need to come from source
//validate the settings by matching against predefined schema
// const settings = settingsHandler.parseSettings(getMockSettings(), true);

outsideResolve(results);
}
);
}
});

connection.on("error", (err) => {
if (err) {
console.log("db error", err);
if (
err.code === "PROTOCOL_CONNECTION_LOST" ||
connectionTries <= MAX_CONNECTION_TRIES
) {
// Connection to the MySQL server is usually lost due to either server restart, or a
// connnection idle timeout (the wait_timeout server variable configures this)
handleDisconnect();
} else {
const errorResponse = getConnectionErrorResponse(err);
outsideReject(errorResponse);
}
}
});
}

handleDisconnect();

return promise;
};

const settingsMiddleware = async (req, res, next) => {
/* In multi-tenant app, projectID is retreived from API key in a custom HTTP header
** For now we stick with multi-tenant and we will customize this to cater for **
** single tenancy architecture in time where projectIDs are irrelevant **
** In retrospect, expiry of API keys should be from MicroAPI, **
** so making a request for settings with an invalid API key will be rejected **
*/
try {
// we are calling our custom HTTP header X-MicroAPI-ProjectKey
const apiKey = req.headers["x-microapi-projectkey"];
if (!apiKey) {
// Temporarily optional as this is an experimental feature.
next();
// res
// .status(401)
// .json(
// CustomResponse(
// "UnauthorizedError",
// { statusCode: 401, message: "No API key found" },
// false
// )
// );
}

// get settings from parent DB/source
const settings = await getSettings(apiKey);
if (settings.errors) {
res
.status(400)
.json(
CustomResponse(
"BadRequestError",
{ statusCode: 400, message: settings.errors[0].message },
false
)
);
}

console.log(settings);

//attach setting to request body
req.settings = settings;
// req.projectId = projectId;

//pass to next middleware
next();
} catch (error) {
console.log(error);
res.status(500).json(
CustomResponse(
"InternalServerError",
{
statusCode: 500,
message: "Something went wrong, please try again later",
},
false
)
);
}
};

module.exports = settingsMiddleware;
3 changes: 2 additions & 1 deletion src/routes/googleLogin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const passport = require("passport");
const url = require("url");
const { google } = require("googleapis");
const CustomResponse = require("../utils/response");
const settingsMiddleware = require("../middlewares/settings");
const createGoogleStrategy = require("../config/passport/googleStrategy");
const {
authorizeUser,
Expand All @@ -13,7 +14,7 @@ const route = express.Router();
// The credentials of all who are currently undergoing Google's authentication flow.
const activeCredentials = {};

route.get("/", authorizeUser, googleAuthProvider, async (req, res) => {
route.get("/", settingsMiddleware, authorizeUser, googleAuthProvider, async (req, res) => {
try {
const oauth2Client = new google.auth.OAuth2(
req.provider.clientID,
Expand Down
4 changes: 2 additions & 2 deletions src/services/adminAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const Settings = require("../models/settings");
const { CustomError } = require("../utils/CustomError");
const RandomString = require("randomstring");
const { sendForgotPasswordMail } = require("../EmailFactory/index");
const { settingsSchema } = require("../utils/settingsHandler");
const settingsHandler = require("../utils/settingsHandler");

class AdminService {
async register(body) {
Expand Down Expand Up @@ -82,7 +82,7 @@ class AdminService {
// New API KEY for admin
const message = "Settings retrieved successfully";
return {
data: settingsSchema,
data: settingsHandler.settingsSchema,
message: message,
};
}
Expand Down
Loading