From a62bb80478632116ce0f20409ae7b6eb068193a8 Mon Sep 17 00:00:00 2001 From: Ruben Meyer <46384706+rxbnDE@users.noreply.github.com> Date: Sun, 8 Sep 2019 19:43:52 +0200 Subject: [PATCH] web module --- bin/web/app/routes/main.js | 23 ++++++ bin/web/app/views/index.pug | 0 bin/web/auth/routes/api.js | 51 ++++++++++++ bin/web/auth/routes/main.js | 8 ++ bin/web/auth/routes/static.js | 23 ++++++ bin/web/auth/views/index.pug | 66 +++++++++++++++ bin/web/module.js | 138 ++++++++++++++++++++++++++++++++ res/web/auth/css/stylesheet.css | 8 ++ 8 files changed, 317 insertions(+) create mode 100644 bin/web/app/routes/main.js create mode 100644 bin/web/app/views/index.pug create mode 100644 bin/web/auth/routes/api.js create mode 100644 bin/web/auth/routes/main.js create mode 100644 bin/web/auth/routes/static.js create mode 100644 bin/web/auth/views/index.pug create mode 100644 bin/web/module.js create mode 100644 res/web/auth/css/stylesheet.css diff --git a/bin/web/app/routes/main.js b/bin/web/app/routes/main.js new file mode 100644 index 0000000..e09ce75 --- /dev/null +++ b/bin/web/app/routes/main.js @@ -0,0 +1,23 @@ +var express = require('express'); +var route = express.Router(); + + +route.use((req, res, next) => { + if(!req.session || !req.session.user) { + if(!req.path.startsWith('/api')) { + let path = (global['gds'].cfg.web.rootUrl+'/auth'); + + if(global['gds'].cfg.web.doubleSlashCheck) path = path.replace(/\/+/g, "/"); + + res.redirect(path); + } else { + res.redirect('/auth'+req.path); + } + } else next(); +}); + +route.get('/', (req, res) => { + res.status(202).end('test'); +}); + +module.exports = route; diff --git a/bin/web/app/views/index.pug b/bin/web/app/views/index.pug new file mode 100644 index 0000000..e69de29 diff --git a/bin/web/auth/routes/api.js b/bin/web/auth/routes/api.js new file mode 100644 index 0000000..91405f9 --- /dev/null +++ b/bin/web/auth/routes/api.js @@ -0,0 +1,51 @@ +var express = require('express'); +var route = express.Router(); + +route.get('/register', (req, res) => { + if(!global['gds'].cfg.web.registration) { + return res.type('json').status(400).end(JSON.stringify({status: 400, message: "msg.auth.registration.deactivated"})); + } + // TODO: register +}); + +route.get('/login', (req, res) => { + // TODO: login + let a = global['modules'].sso.createAuthentication({ + url: global['gds'].cfg.sso.authenticator, + appId: global['gds'].cfg.sso.appId + }) + console.log(a); + res.end('login'); +}); + +route.post('/authenticate', (req, res) => { + // TODO: authenticate +}); + +route.get('/logout', (req, res) => { + if(!req.session.user) { + return res.type('json').end(JSON.stringify({ + status: 401, + message: 'msg.auth.login.required' + })); + } else { + res.clearCookie('RememberMe'); + req.session.destroy(); + return res.type('json').end(JSON.stringify({ + status: 200, + message: 'msg.auth.logout.successful' + })); + } +}); + +if(global['gds'].debug) { + // DEBUG info + route.get('/info', (req, res) => { + let obj = {}; + if(req.session) obj.session = req.session; + if(req.cookies) obj.cookie = req.cookies; + res.type('json').end(JSON.stringify(obj)); + }); +} + +module.exports = route; diff --git a/bin/web/auth/routes/main.js b/bin/web/auth/routes/main.js new file mode 100644 index 0000000..c36b2c0 --- /dev/null +++ b/bin/web/auth/routes/main.js @@ -0,0 +1,8 @@ +var express = require('express'); +var route = express.Router(); + + +route.use('/api', require(global['__dirname']+'/bin/web/auth/routes/api')); +route.use('/', require(global['__dirname']+'/bin/web/auth/routes/static')); + +module.exports = route; diff --git a/bin/web/auth/routes/static.js b/bin/web/auth/routes/static.js new file mode 100644 index 0000000..7144484 --- /dev/null +++ b/bin/web/auth/routes/static.js @@ -0,0 +1,23 @@ +var express = require('express'); +var route = express.Router(); + + +route.all('/', function(req, res, next) { + // TODO: show login page or dashboard + // res.end('login or dashboard'); + res.render('auth/views/index'); +}); + +route.all('/*', (req, res, next) => { + // passthrough to next route + if(req.path.startsWith('/api')) + return next(); + + // TODO: try to login + // TODO: role-based authorization + // TODO: show login page or page + + res.end('500 - LEL'); +}); + +module.exports = route; diff --git a/bin/web/auth/views/index.pug b/bin/web/auth/views/index.pug new file mode 100644 index 0000000..29f98ee --- /dev/null +++ b/bin/web/auth/views/index.pug @@ -0,0 +1,66 @@ +//- variables +- var appName = global['gds'].cfg.app.name || "SSObaseApp"; +- var title = "Dashboard"; + +mixin navItem(name, id, symbol, href) + li(title=name, id=id) + a(href=href) + i.fa-fw(class=symbol) + span= name + +doctype(html) +html(lang='en') + head + meta(charset="utf-8") + meta(http-equiv="X-UA-Compatible", content="IE=edge") + meta(name="viewport", content="width=device-width, initial-scale=1, shrink-to-fit=no") + + meta(name="author", content="Ruben Meyer") + meta(name="description" content="auth.rxbn.de") + if(title) + title=""+appName+" - "+title + else + title=appName + + block css + //- UIkit CSS + link(href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.5/css/uikit.min.css", rel="stylesheet") + + //- Custom Stylesheet + link(href="/res/css/stylesheet.css", rel="stylesheet") + + //- Custom fonts for this template + link(href="https://use.fontawesome.com/releases/v5.1.1/css/all.css" integrity="sha384-O8whS3fhG2OnA5Kas0Y9l3cfpmYjapjI0E4theH4iuMD+pLhbf6JI0jIMfYcK3yZ", crossorigin="anonymous", rel="stylesheet") + body + //- Navigation + nav(uk-navbar).uk-navbar-container + .uk-navbar-left.uk-margin-left + ul.uk-navbar-nav + li(title=appName) + a(href="/", style="text-transform: unset") + span=appName + .uk-navbar-right.uk-margin-right + ul.uk-navbar-nav + +navItem("Register", "register", "fas fa-user-plus", "/api/register") + +navItem("Login", "login", "far fa-arrow-alt-circle-right", "/api/login") + div + .uk-flex.uk-margin-medium-top.uk-margin-medium-bottom + div(class="uk-width-auto uk-width-1-4@s") + .uk-flex.uk-flex-auto.uk-flex-column.uk-flex-center.uk-margin-left.uk-margin-right + h1 Please login + p You need to be logged in to use this service + a(href="/api/login").uk-button.uk-button-default Login + div(class="uk-width-auto uk-width-1-4@s") + footer + .uk-text-center + small Copyright © Ruben Meyer 2019 + + block scripts + //- UIkit JS + script(src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.5/js/uikit.min.js") + script(src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.5/js/uikit-icons.min.js") + + //- Custom scripts for this template + script(src="/res/js/locales.js") + script(src="/res/js/custom.js") + diff --git a/bin/web/module.js b/bin/web/module.js new file mode 100644 index 0000000..350f1f4 --- /dev/null +++ b/bin/web/module.js @@ -0,0 +1,138 @@ +// init +var methods = {}; + +/** + * start web server + * @author Ruben Meyer + * @return {Void} + */ +methods.start = () => { + // init express framework + let express = require('express'); + let session_handler = require('express-session'); + + // utilities + let fs = require('fs'); + let path = require('path'); + let mime = require('mime-types'); + + // app variable + let app = express(); + app.set('view engine', 'pug'); // page engine + app.set('views', global['__dirname']+'/bin/web'); + + let bp = require('body-parser'); // POST Body parser + let cp = require('cookie-parser'); // Cookie handler + + // Access Control Headers + app.use( (req, res, next) => { + res.set({ + 'X-Powered-By': global['gds'].cfg + }); + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + next(); + }); + + // BodyParser & CookieParser + app.use(bp.json()); + app.use(bp.urlencoded({ + extended: true + })); + app.use(cp(global['gds'].cfg.web.cookieKey)); + + // Pretty print + app.locals.pretty = true; + + // Sessions + session_options = { + secret: global['gds'].cfg.web.sessionKey, + resave: false, + saveUninitialized: false, cookie: {}}; + if(app.get('env') === 'production') { + session_options.cookie.secure = true; + } + app.use(session_handler(session_options)); + + //static files + app.use('/res', (req, res, next) => { + if(typeof global['gds'].cache.web == 'undefined') global['gds'].cache.web = {}; + + let dir = global['__dirname'] + '/res/web'; + + + if(!req.session || !req.session.user) dir += '/auth'; + else dir += '/app'; + + let joined_path = path.join(dir, /^[^?]+/.exec(req.url)[0]); + + // path already cached; not exist + if(global['gds'].cache.web[joined_path] == false) { + res.status(404).end(); + // path already cached; exist + } else if(global['gds'].cache.web[joined_path] == true){ + let contentType = mime.contentType(path.extname(joined_path)); + res.setHeader('Content-Type', contentType); + fs.createReadStream(joined_path).pipe(res); + // check path + } else { + fs.exists(joined_path, (exists) => { + global['gds'].cache.web[joined_path] = exists; + if(exists) { + let contentType = mime.contentType(path.extname(joined_path)); + res.setHeader('Content-Type', contentType); + + fs.createReadStream(joined_path).pipe(res); + } else { + res.status(404).end(); + } + }); + } + }); + + // web routes + app.use('/auth', require(global['__dirname']+'/bin/web/auth/routes/main')); + app.use('/', require(global['__dirname']+'/bin/web/app/routes/main')); + + // start server + app.listen(global['gds'].cfg.web.port, () => { + global['modules'].logs.log("Server is listening on port: "+global['gds'].cfg.web.port); + }); + + // DEBUG OUTPUT: list all routes with HTTP method + setTimeout(function () { + function print (path, layer) { + if (layer.route) { + layer.route.stack.forEach(print.bind(null, path.concat(split(layer.route.path)))) + } else if (layer.name === 'router' && layer.handle.stack) { + layer.handle.stack.forEach(print.bind(null, path.concat(split(layer.regexp)))) + } else if (layer.method) { + console.log('%s /%s', + layer.method.toUpperCase(), + path.concat(split(layer.regexp)).filter(Boolean).join('/') + ); + } + } + + function split (thing) { + if (typeof thing === 'string') { + return thing.split('/') + } else if (thing.fast_slash) { + return '' + } else { + var match = thing.toString() + .replace('\\/?', '') + .replace('(?=\\/|$)', '$') + .match(/^\/\^((?:\\[.*+?^${}()|[\]\\\/]|[^.*+?^${}()|[\]\\\/])*)\$\//) + + return match + ? match[1].replace(/\\(.)/g, '$1').split('/') + : '' + } + } + + app._router.stack.forEach(print.bind(null, [])) + }, 1500); +}; + +module.exports = methods; diff --git a/res/web/auth/css/stylesheet.css b/res/web/auth/css/stylesheet.css new file mode 100644 index 0000000..23684d8 --- /dev/null +++ b/res/web/auth/css/stylesheet.css @@ -0,0 +1,8 @@ +.uk-navbar a i { + margin-right: .2em; + transition: all .1s cubic-bezier(0.65, 0.05, 0.36, 1); +} +.uk-navbar a:hover i { + margin-top: .1em; + font-size: 1.4em; +}