diff --git a/bin/web/module.js b/bin/web/module.js new file mode 100644 index 0000000..898477b --- /dev/null +++ b/bin/web/module.js @@ -0,0 +1,101 @@ +/* + * This file is part of the authRXBN single sign-on package. + * + * (c) Ruben Meyer + */ + +// 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/views'); + + 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(); + }); + + //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'; + 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(); + } + }); + } + }); + + // BodyParser & CookieParser + app.use(bp.json()); + app.use(bp.urlencoded({ + extended: true + })); + app.use(cp(global['gds'].cfg.web.cookieKey)); + + // 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)); + + // web routes + app.use('/', require(global['__dirname']+'/bin/web/routes/static')); + app.use('/api', require(global['__dirname']+'/bin/web/routes/api')); + + // start server + app.listen(global['gds'].cfg.web.port, () => { + console.log("Server is listening on port: "+global['gds'].cfg.web.port); + }); +}; + +module.exports = methods; diff --git a/bin/web/routes/api.js b/bin/web/routes/api.js new file mode 100644 index 0000000..6d20dd4 --- /dev/null +++ b/bin/web/routes/api.js @@ -0,0 +1,51 @@ +/* + * This file is part of the authRXBN single sign-on package. + * + * (c) Ruben Meyer + */ + +var express = require('express'); +var route = express.Router(); + +route.post('/register', (req, res) => { + if(!global['app'].cfg.web.registration) { + return res.type('json').status(400).end(JSON.stringify({status: 400, message: "msg.auth.registration.deactivated"})); + } + // TODO: register +}); + +route.post('/login', (req, res) => { + // TODO: 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/routes/static.js b/bin/web/routes/static.js new file mode 100644 index 0000000..c500627 --- /dev/null +++ b/bin/web/routes/static.js @@ -0,0 +1,29 @@ +/* + * This file is part of the authRXBN single sign-on package. + * + * (c) Ruben Meyer + */ + +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('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/views/blocks/footer.pug b/bin/web/views/blocks/footer.pug new file mode 100644 index 0000000..a5a36e3 --- /dev/null +++ b/bin/web/views/blocks/footer.pug @@ -0,0 +1,25 @@ +footer + .uk-text-center + small Copyright © Ruben Meyer 2019 + +.modals + //- Logout Modal + div(uk-modal)#logoutModal + .uk-modal-dialog.uk-modal-body + h2.uk-modal-title + span Ready to Leave? + // button(type="button" class="close" data-dismiss="modal" aria-label="Close") + // span(aria-hidden="true") × + .uk-modal-footer + form#modal_logInOut + button(type="button").uk-button.uk-button-primary + button(type="button").uk-button.uk-button-default.uk-modal-close Cancel + +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="/public/js/locales.js") + script(src="/public/js/custom.js") diff --git a/bin/web/views/blocks/head.pug b/bin/web/views/blocks/head.pug new file mode 100644 index 0000000..10f3951 --- /dev/null +++ b/bin/web/views/blocks/head.pug @@ -0,0 +1,21 @@ +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="authRXBN - "+title + else + title authRXBN + + 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") diff --git a/bin/web/views/blocks/layout.pug b/bin/web/views/blocks/layout.pug new file mode 100644 index 0000000..6095be2 --- /dev/null +++ b/bin/web/views/blocks/layout.pug @@ -0,0 +1,8 @@ +block var +doctype(html) +html(lang='en') + include head.pug + body(class={"error": error_page}, class=page_class) + include nav.pug + block content + include footer.pug diff --git a/bin/web/views/blocks/nav.pug b/bin/web/views/blocks/nav.pug new file mode 100644 index 0000000..93ddded --- /dev/null +++ b/bin/web/views/blocks/nav.pug @@ -0,0 +1,34 @@ +mixin navItem(name, id, symbol, href) + li(title=name, id=id) + a(href=href) + i.fas.fa-fw(class=symbol) + span= name + +// Navigation +nav(uk-navbar).uk-navbar-container + .uk-navbar-left + ul.uk-navbar-nav + li(title="authRXBN") + a(href="/", style="text-transform: unset") + span authRXBN + .uk-navbar-right + ul.uk-navbar-nav + if(user) + +navItem("Apps", "apps", "fa-tachometer-alt", "/") + +navItem("Configs", "configs", "fa-wrench", "/configs") + else + +navItem("Register", "register", "fa-user-plus", "/register") + +navItem("Login", "login", "fa-sign-in", "/login") + +navItem("Forgot your password?", "reset", "fa-key", "/reset") +div + - var breadcrumb_isSet = typeof breadcrumb !== 'undefined'; + if(breadcrumb_isSet) + .uk-container.uk-container-expand + ul.uk-breadcrumb + each val in breadcrumb + li(class={"uk-disabled": val.disabled}) + if(val.href) + a(href=val.href)= val.name + else + span= val.name + div diff --git a/bin/web/views/index.pug b/bin/web/views/index.pug new file mode 100644 index 0000000..6615aa1 --- /dev/null +++ b/bin/web/views/index.pug @@ -0,0 +1,24 @@ +extends blocks/layout.pug +append var + - var breadcrumb = {0: {"name": "authRXBN", "href": "/"}, 1: {"name": "Apps", "active": true}}; + - var title = "Apps"; + +mixin item(name, url, description) + .card.mb-5 + .card-body + h5.font-weight-bold.card-title=name + p.card-text=description + a(href=url) Login + +mixin items() + .flex + if(apps) + each app in apps + +item(app.name, app.access, app.description) + else + p.text-center No applications were found. + +append content + .uk-container + h1 Apps + +items() diff --git a/res/web/css/stylesheet.css b/res/web/css/stylesheet.css new file mode 100644 index 0000000..fbec99d --- /dev/null +++ b/res/web/css/stylesheet.css @@ -0,0 +1,13 @@ +.uk-breadcrumb { + border: 1px solid #e6e6e6; + background-color: #f9f9f9; + border-radius: 5px; + padding: 5px 10px; + margin-top: 15px; +} +.uk-breadcrumb a { + color: #717171; +} +.uk-breadcrumb a:hover { + color: #666; +}