From 7f92f3e7216cf770541ef05596e92465740dfba3 Mon Sep 17 00:00:00 2001 From: rxbn_ Date: Wed, 16 Sep 2020 16:20:55 +0200 Subject: [PATCH] web - each api route has its own file to simplify structure --- bin/web/routes/api.js | 244 +++-------------------------- bin/web/routes/api/authenticate.js | 13 ++ bin/web/routes/api/cancel.js | 26 +++ bin/web/routes/api/login.js | 78 +++++++++ bin/web/routes/api/logout.js | 25 +++ bin/web/routes/api/redirect.js | 78 +++++++++ bin/web/routes/api/register.js | 17 ++ bin/web/routes/api/settings.js | 95 +++++++++++ 8 files changed, 354 insertions(+), 222 deletions(-) create mode 100644 bin/web/routes/api/authenticate.js create mode 100644 bin/web/routes/api/cancel.js create mode 100644 bin/web/routes/api/login.js create mode 100644 bin/web/routes/api/logout.js create mode 100644 bin/web/routes/api/redirect.js create mode 100644 bin/web/routes/api/register.js create mode 100644 bin/web/routes/api/settings.js diff --git a/bin/web/routes/api.js b/bin/web/routes/api.js index 173cbe4..3da523e 100644 --- a/bin/web/routes/api.js +++ b/bin/web/routes/api.js @@ -4,238 +4,38 @@ * (c) Ruben Meyer */ +var path = require('path'); +var fs = require('fs'); var express = require('express'); var route = express.Router(); asyncer = require('express-async-handler'); getRoutes = async () => { - let db = global['requireModule']('database'); - await db.connect(); - - /** - * register a user; currently not implemented - * @url /register - * @method POST - */ - route.post('/register', (req, res) => { - // if registration is disabled - if(!global['app'].cfg.web.registration) { - return res.type('json').status(400).end(JSON.stringify({status: 400, message: "msg.auth.registration.deactivated"})); - } else { - // am i rite? - return res.type('json').status(200).end(JSON.stringify({})); - } - }); - - /** - * login a user - * @url /api/login - * @method POST - * @POST ['email', 'password'] - * @TODO add new activity 'action.user.login' - */ - route.post('/login', asyncer(async (req, res) => { - // if user is logged in (existing session); FAIL - if(req.session.user) { - return res.type('json').end(JSON.stringify({ - status: 401, - message: 'msg.auth.logout.required' - })); - } - - // check body variables - if(!req.body.email || !req.body.password) { - return res.type('json').status(401).end(JSON.stringify({ - status: 401, - message: [ - 'msg.request.data.missing', - 'msg.auth.login.failed' - ] - })); - } - let email = req.body.email; - let pass = req.body.password; - - // database query: get user by email - user = await db.getUser(email); - - // if database error - if(user.err) { - // log error while debugging - global['logs'].debug(user.err); - - // login failed because of database error - return res.type('json').status(500).end(JSON.stringify({ - status: 500, - message: [ - 'msg.database.error', - 'msg.auth.login.failed' - ] - })); - } - - // no reply (user does not exist) or password is wrong - if(!user.reply || user.reply === null || user.reply.length == 0 || user.reply.length > 1 || !global['requireModule']('auth').validateHash(user.reply.passhash, pass)) { - return res.type('json').status(401).end(JSON.stringify({ - status: 401, - message: 'msg.auth.login.failed' - })); - // do login - } else { - // add cookies; login - // new activity 'action.user.login' - - // add session data - req.session.user = { - 'id': user.reply._id, - 'group': user.reply.group - }; - - return res.type('json').end(JSON.stringify({ - status: 200, - message: 'msg.auth.login.successful', - type: 'form' // TODO: types - { form, access_app} - })); - } - - })); - - /** - * apps verify token - * @url /api/authenticate - * @method POST - * @POST ['applicationId', 'applicationSecret', 'userId', 'token'] - * @TODO add implementation - */ - route.post('/authenticate', (req, res) => { - // TODO: authenticate - }); - - /** - * cancel app request and clear it - * @url /api/cancel - * @method GET - */ - route.get('/cancel', (req, res) => { - // if user is logged in - if(req.session && req.session.user) { - req.session.appRequest = {}; - - return res.type('json').end(JSON.stringify({ - status: 200, - message: 'msg.request.operation.cancel.successful' - })); - - // user isnt logged in - } else { - return res.type('json').end(JSON.stringify({ - status: 401, - message: 'msg.auth.login.required' - })); - } - }); - - /** - * redirect user to app - * @url /api/redirect - * @method GET - * @GET ['id'] - */ - route.get('/redirect', asyncer(async (req, res) => { - // if user is logged in - if(req.session && req.session.user) { - // missing query data to retrieve app - if(!req.query || !req.query.id) { - return res.type('json').status(500).end(JSON.stringify({ - status: 500, - message: [ - 'msg.request.data.missing' - ] - })); - } - - // set auth code - authCode = await db.setAuthCode({ - aId: req.query.id, - uId: req.session.user.id - }); - - // database error - if(typeof authCode.err !== "undefined") { - global['logs'].debug(authCode.err); - return res.type('json').status(500).end(JSON.stringify({ - status: 500, - message: [ - 'msg.database.error' - ] - })); - } - else if(typeof authCode.reply !== "undefined") { - // retrieve apps - apps = await db.getApps(); - // database error - if(typeof apps.err !== "undefined") { - global['logs'].debug(apps.err); - return res.type('json').status(500).end(JSON.stringify({ - status: 500, - message: [ - 'msg.database.error' - ] - })); - } - // for each app - apps.reply.forEach((app) => { - // if app.id is equal to queried app - if(app.id == req.query.id) { - // redirect to app - return res.redirect(app.access+"?uid="+req.session.user.id+"&token="+authCode.reply.token); - } - }); - } else { - // database error - return res.type('json').status(500).end(JSON.stringify({ - status: 500, - message: [ - 'msg.database.error' - ] - })); - } - // user isnt logged in - } else { - return res.type('json').end(JSON.stringify({ - status: 401, - message: 'msg.auth.login.required' - })); - } - })); - - /** - * logout user - * @url /api/logout - * @method GET - */ - route.get('/logout', (req, res) => { - // user needs to be logged in - if(!req.session || !req.session.user) { - return res.type('json').end(JSON.stringify({ - status: 401, - message: 'msg.auth.login.required' - })); - // logout user - } else { - res.clearCookie('RememberMe'); - req.session.destroy(); - return res.type('json').end(JSON.stringify({ - status: 200, - message: 'msg.auth.logout.successful' - })); - } - }); + // add all routes from files in api + let routesPath = path.join(global['__dirname'], '/bin/web/routes/api'); + await addRoutesFromDirectory(routesPath); return route; }; +addRoutesFromDirectory = async (filepath) => { + for await (const file of fs.readdirSync(filepath)) { + let joinedPath = path.join(filepath, file); + if(fs.statSync(joinedPath).isFile()) + await addRoutesFromFile(joinedPath); + else if(fs.statSync(joinedPath).isDirectory()) + await addRoutesFromDirectory(joinedPath); + } +}; +addRoutesFromFile = async (filepath) => { + let file = require(filepath); + if(typeof file !== "object" || !file.hasOwnProperty("path")) return; + if(file.hasOwnProperty("all")) route.all(file.path, asyncer(file.all)); + if(file.hasOwnProperty("get")) route.get(file.path, asyncer(file.get)); + if(file.hasOwnProperty("post")) route.post(file.path, asyncer(file.post)); +}; + module.exports = { getRoutes: getRoutes }; diff --git a/bin/web/routes/api/authenticate.js b/bin/web/routes/api/authenticate.js new file mode 100644 index 0000000..486b95c --- /dev/null +++ b/bin/web/routes/api/authenticate.js @@ -0,0 +1,13 @@ +module.exports = { + path: "/authenticate", + /** + * apps verify token + * @url /api/authenticate + * @method POST + * @POST ['applicationId', 'applicationSecret', 'userId', 'token'] + * @TODO add implementation + */ + post: async (req, res) => { + return res.end(); + } +}; diff --git a/bin/web/routes/api/cancel.js b/bin/web/routes/api/cancel.js new file mode 100644 index 0000000..555db9e --- /dev/null +++ b/bin/web/routes/api/cancel.js @@ -0,0 +1,26 @@ +module.exports = { + path: "/cancel", + /** + * cancel app request and clear it + * @url /api/cancel + * @method GET + */ + get: (req, res) => { + // if user is logged in + if(req.session && req.session.user) { + req.session.appRequest = {}; + + return res.type('json').end(JSON.stringify({ + status: 200, + message: 'msg.request.operation.cancel.successful' + })); + + // user isnt logged in + } else { + return res.type('json').end(JSON.stringify({ + status: 401, + message: 'msg.auth.login.required' + })); + } + } +}; diff --git a/bin/web/routes/api/login.js b/bin/web/routes/api/login.js new file mode 100644 index 0000000..0355547 --- /dev/null +++ b/bin/web/routes/api/login.js @@ -0,0 +1,78 @@ +var sanitize = require('mongo-sanitize'); +let db = global['requireModule']('database'); + +module.exports = { + path: "/login", + /** + * login a user + * @url /api/login + * @method POST + * @POST ['email', 'password'] + * @TODO add new activity 'action.user.login' + */ + post: async (req, res) => { + // if user is logged in (existing session); FAIL + if(req.session.user) { + return res.type('json').end(JSON.stringify({ + status: 401, + message: 'msg.auth.logout.required' + })); + } + + // check body variables + if(!req.body.email || !req.body.password) { + return res.type('json').status(401).end(JSON.stringify({ + status: 401, + message: [ + 'msg.request.data.missing', + 'msg.auth.login.failed' + ] + })); + } + let email = sanitize(req.body.email); + let pass = sanitize(req.body.password); + + // database query: get user by email + user = await db.getUser(email); + + // if database error + if(user.err) { + // log error while debugging + global['logs'].debug(user.err); + + // login failed because of database error + return res.type('json').status(500).end(JSON.stringify({ + status: 500, + message: [ + 'msg.database.error', + 'msg.auth.login.failed' + ] + })); + } + + // no reply (user does not exist) or password is wrong + if(!user.reply || user.reply === null || user.reply.length == 0 || user.reply.length > 1 || !global['requireModule']('auth').validateHash(user.reply.passhash, pass)) { + return res.type('json').status(401).end(JSON.stringify({ + status: 401, + message: 'msg.auth.login.failed' + })); + // do login + } else { + // add cookies; login + // new activity 'action.user.login' + + // add session data + req.session.user = { + 'id': user.reply._id, + 'group': user.reply.group + }; + + return res.type('json').end(JSON.stringify({ + status: 200, + message: 'msg.auth.login.successful', + type: 'form' // TODO: types - { form, access_app} + })); + } + + } +}; diff --git a/bin/web/routes/api/logout.js b/bin/web/routes/api/logout.js new file mode 100644 index 0000000..be70212 --- /dev/null +++ b/bin/web/routes/api/logout.js @@ -0,0 +1,25 @@ +module.exports = { + path: "/logout", + /** + * logout user + * @url /api/logout + * @method GET + */ + get: (req, res) => { + // user needs to be logged in + if(!req.session || !req.session.user) { + return res.type('json').end(JSON.stringify({ + status: 401, + message: 'msg.auth.login.required' + })); + // logout user + } else { + res.clearCookie('RememberMe'); + req.session.destroy(); + return res.type('json').end(JSON.stringify({ + status: 200, + message: 'msg.auth.logout.successful' + })); + } + } +}; diff --git a/bin/web/routes/api/redirect.js b/bin/web/routes/api/redirect.js new file mode 100644 index 0000000..a3fd36a --- /dev/null +++ b/bin/web/routes/api/redirect.js @@ -0,0 +1,78 @@ +let db = global['requireModule']('database'); + +module.exports = { + path: "/redirect", + /** + * redirect user to app + * @url /api/redirect + * @method GET + * @GET ['id'] + */ + get: async (req, res) => { + // if user is logged in + if(req.session && req.session.user) { + // missing query data to retrieve app + if(!req.query || !req.query.id) { + return res.type('json').status(500).end(JSON.stringify({ + status: 500, + message: [ + 'msg.request.data.missing' + ] + })); + } + + // set auth code + authCode = await db.setAuthCode({ + aId: req.query.id, + uId: req.session.user.id + }); + + // database error + if(typeof authCode.err !== "undefined") { + global['logs'].debug(authCode.err); + return res.type('json').status(500).end(JSON.stringify({ + status: 500, + message: [ + 'msg.database.error' + ] + })); + } + else if(typeof authCode.reply !== "undefined") { + // retrieve apps + apps = await db.getApps(); + // database error + if(typeof apps.err !== "undefined") { + global['logs'].debug(apps.err); + return res.type('json').status(500).end(JSON.stringify({ + status: 500, + message: [ + 'msg.database.error' + ] + })); + } + // for each app + apps.reply.forEach((app) => { + // if app.id is equal to queried app + if(app.id == req.query.id) { + // redirect to app + return res.redirect(app.access+"?uid="+req.session.user.id+"&token="+authCode.reply.token); + } + }); + } else { + // database error + return res.type('json').status(500).end(JSON.stringify({ + status: 500, + message: [ + 'msg.database.error' + ] + })); + } + // user isnt logged in + } else { + return res.type('json').end(JSON.stringify({ + status: 401, + message: 'msg.auth.login.required' + })); + } + } +}; diff --git a/bin/web/routes/api/register.js b/bin/web/routes/api/register.js new file mode 100644 index 0000000..016d04f --- /dev/null +++ b/bin/web/routes/api/register.js @@ -0,0 +1,17 @@ +module.exports = { + path: "/register", + /** + * register a user; currently not implemented + * @url /register + * @method POST + */ + post: async (req, res) => { + // if registration is disabled + if(!global['gds'].cfg.web.registration) { + return res.type('json').status(400).end(JSON.stringify({status: 400, message: "msg.auth.registration.deactivated"})); + } else { + // am i rite? + return res.type('json').status(200).end(JSON.stringify({})); + } + } +}; diff --git a/bin/web/routes/api/settings.js b/bin/web/routes/api/settings.js new file mode 100644 index 0000000..3830a38 --- /dev/null +++ b/bin/web/routes/api/settings.js @@ -0,0 +1,95 @@ +let sanitize = require('mongo-sanitize'); +let db = global['requireModule']('database'); +let crypto = require('crypto'); +let auth = global['requireModule']('auth'); + +module.exports = { + path: "/settings", + /** + * update user + * @TODO add implementation + * @url /api/settings + * @method POST + * @POST ['email', 'password', 'repassword', 'mfa'] + */ + post: async (req, res) => { + // if user is not logged in; FAIL + if(!req.session || !req.session.user) { + return res.type('json').end(JSON.stringify({ + status: 401, + message: 'msg.auth.logout.required' + })); + } + + // check body variables + if( + !( + (req.body.email) || + (req.body.password && req.body.repassword) || + (req.body.mfa) + ) + ) { + return res.type('json').status(401).end(JSON.stringify({ + status: 401, + message: 'msg.request.data.missing' + })); + } + + user = await db.getUser(req.session.user.id); + // if database error + if(user.err) { + // log error while debugging + global['logs'].debug(user.err); + + // query failed because of database error + return res.type('json').status(500).end(JSON.stringify({ + status: 500, + message: 'msg.database.error' + })); + } + + obj = {}; + if(req.body.email) obj.email = sanitize(req.body.email); + if(req.body.password && req.body.repassword) { + password = sanitize(req.body.password); + repassword = sanitize(req.body.repassword); + if(password.length == repassword.length && crypto.timingSafeEqual( + Buffer.from(password, 'hex'), + Buffer.from(repassword, 'hex') + )) { + obj.passhash = auth.generateHash(password); + } else { + return res.type('json').status(400).end(JSON.stringify({status: 400, message: "msg.request.data.missing"})); + } + } + + // empty obj, do not update + if(Object.keys(obj).length === 0 && obj.constructor === Object) { + return res.type('json').status(400).end(JSON.stringify({status: 400, message: "msg.request.data.missing"})); + } + update = await db.updateUser(user.reply._id, obj); + // if database error + if(update.err) { + // log error while debugging + global['logs'].debug(update.err); + + // update failed because of database error + return res.type('json').status(500).end(JSON.stringify({ + status: 500, + message: 'msg.database.error' + })); + } + else if(update.reply) { + console.log(obj); + return res.type('json').end(JSON.stringify({ + status: 200, + message: 'msg.settings.update.successful' + })); + } + + return res.type('json').end(JSON.stringify({ + status: 401, + message: 'msg.auth.logout.required' + })); + } +};