Compare commits

...

6 Commits

  1. 1
      app.js
  2. 3
      bin/config.js
  3. 21
      bin/database/models.js
  4. 253
      bin/database/module.js
  5. 36
      bin/web/module.js
  6. 16
      bin/web/routes/api.js
  7. 47
      bin/web/routes/rules.js
  8. 104
      bin/web/routes/static.js
  9. 8
      bin/web/views/blocks/error/login.pug
  10. 5
      bin/web/views/blocks/nav.pug
  11. 9
      res/web/js/custom.js

1
app.js

@ -8,7 +8,6 @@
// GDS: Global Data System
global['gds'] = {
debug: (process.env.NODE_ENV === 'debug') ? true : false,
cache: {},
cfg: require(__dirname+'/bin/config')
};
global['__dirname'] = __dirname;

3
bin/config.js

@ -28,7 +28,8 @@ module.exports = {
app: {
locale: 'de-DE', // default locale (de-DE & en-EN should be available)
name: 'authRXBN',
passhashDelimiter: '|'
passhashDelimiter: '|',
adminGroupname: "Administration"
},
mongoose: {
uri: process.env.DB_URL,

21
bin/database/models.js

@ -34,12 +34,17 @@ models.user = new Schema({
},
mfa: { // multi factor authentication
active: {type: Boolean, default: false},
type: {type: String, default: ""},
data: {type: String, default: ""} // tel number or secret token
data: {type: Array, default: [ // add each mfa type
//{
// no: 0,
// type: "TOTP"||"HOTP"||"WebAuthn",
// data: "32CharHex"||"32CharHex"||"UserPublicKey"
//}, ...
]}
},
settings: {type: Object, default: {}}, // custom settings (theme etc. pp.)
roles: {type: String, default: ""}, // user-defined roles and permissions
group: {type: Number, default: 0}, // group-id for group-defined roles and permissions
group: Schema.Types.ObjectId, // reference to group
reg_date: {type: Date, default: Date.now}, // registration date
last_action: {type: Date, default: Date.now}, // last action (activity date)
});
@ -51,6 +56,15 @@ models.group = new Schema({
roles: {type: String, default: ""} // roles; separated by commas "a,b,a.b,c.*,d.z.*"
});
// pathRules for access management
models.pathRules = new Schema({
group: Schema.Types.ObjectId, // reference to group
expression: String, // path expression; e.g.: "(/blocks/.*)"
rule: String, // e.g.: block
type: String, // e.g: "404", "missing_permission", "login",
options: {type: Object, default: {}} // more options...
});
// application | service
models.application = new Schema({
name: String, // recognizable application name; ex. "passRXBN - Password Manager"
@ -81,6 +95,7 @@ module.exports = (con) => {
// initialize models
mdls.user = con.model('User', models.user);
mdls.group = con.model('Group', models.group);
mdls.pathRules = con.model('PathRules', models.pathRules);
mdls.application = con.model('Application', models.application);
mdls.activity = con.model('Activity', models.activity);
mdls.authCode = con.model('AuthCode', models.authCode);

253
bin/database/module.js

@ -73,8 +73,8 @@ methods.getConnection = () => {
* @param {String} nick nickname
* @param {String} email email
* @param {String} passhash hashed password
* @param {Number} group Group id (normally 0 -> user)
* @return {Array} async (reply, err)
* @param {Number} group Group ObjectId
* @return {Object} async (reply, err)
*/
methods.addUser = async (nick, email, passhash, group) => {
if(typeof nick !== 'string') return {err: new TypeError('nick is not a string::database.addUser('+nick+','+email+','+passhash+','+group+')', module.filename)};
@ -105,7 +105,7 @@ methods.addUser = async (nick, email, passhash, group) => {
* @async
* @TODO add functionality
* @param {String} haystack email or nick
* @return {Array} async(reply, err)
* @return {Object} async(reply, err)
*/
methods.delUser = async (haystack) => {
if(typeof haystack !== 'string') return {err: new TypeError('haystack is not a string::database.delUser('+haystack+')', module.filename)};
@ -129,7 +129,7 @@ methods.delUser = async (haystack) => {
* get all users
* @author Ruben Meyer
* @async
* @return {Array} async(reply, err)
* @return {Object} async(reply, err)
*/
methods.getUsers = async () => {
let userModel = models.user;
@ -150,10 +150,10 @@ methods.getUsers = async () => {
* @author Ruben Meyer
* @async
* @param {String|String[]} haystack email or nick
* @return async(reply, err)
* @return {Object} async(reply, err)
*/
methods.getUser = async (haystack) => {
if(typeof haystack !== 'string' && typeof haystack !== 'object') return {err: new TypeError('email or nickname is not a string|object::database.getUser('+haystack+')', module.filename)};
if(typeof haystack !== 'string' && typeof haystack !== 'object') return {err: new TypeError('haystack is not a string|object::database.getUser('+haystack+')', module.filename)};
let userModel = models.user;
@ -161,14 +161,17 @@ methods.getUser = async (haystack) => {
haystack = sanitize(haystack);
let or = [];
if(typeof haystack === 'string') {
if(haystack instanceof mongoose.Types.ObjectId) {
or.push({_id: haystack});
}
else if(typeof haystack === 'string') {
or = [{nickname: haystack}, {email: haystack}, {token: haystack}];
if(haystack.match(/^[0-9a-fA-F]{24}$/)) or.push({_id: haystack});
if(haystack.match(/^[0-9a-fA-F]{24}$/)) or.push({_id: mongoose.Types.ObjectId(haystack)});
}
else {
or = [];
for(let i = 0; i < haystack.length; i++) {
if(haystack[i].match(/^[0-9a-fA-F]{24}$/)) or.push({_id: haystack[i]});
if(haystack[i].match(/^[0-9a-fA-F]{24}$/)) or.push({_id: mongoose.Types.ObjectId(haystack[i])});
or.push({nickname: haystack[i]});
or.push({email: haystack[i]});
or.push({token: haystack[i]});
@ -178,8 +181,8 @@ methods.getUser = async (haystack) => {
try {
users = await userModel.find().or(or).exec();
if(users.length > 0)
return {reply: users};
if(users.length == 1)
return {reply: users[0]};
else
return {reply: false};
} catch(err) {
@ -194,17 +197,17 @@ methods.getUser = async (haystack) => {
* @async
* @param {Number} id User ID
* @param {Object} obj data
* @return {Array} async(reply, err)
* @return {Object} async(reply, err)
*/
methods.updateUser = async (id, obj) => {
if(typeof id !== 'string') return {err: new TypeError('id is not a string::database.updateUser('+id+','+JSON.stringify(obj)+')', module.filename)};
if(!(typeof id === 'string' || id instanceof mongoose.Types.ObjectId)) return {err: new TypeError('id is not a string::database.updateUser('+id+','+JSON.stringify(obj)+')', module.filename)};
if(typeof obj !== 'object') return {err: new TypeError('obj is not an object::database.updateUser('+id+','+JSON.stringify(obj)+')', module.filename)};
let userModel = models.user;
try {
data = await userModel.findByIdAndUpdate(id, obj).exec();
return {data: data};
return {reply: data};
} catch(err) {
return {err: err};
}
@ -219,10 +222,10 @@ methods.updateUser = async (id, obj) => {
* @TODO UPDATE METHOD; PROBABLY OUTDATED
* @param {Number} id User ID
* @param {Object} data data JSON -> remember
* @return {Array} async({date => 'Login Date', token => 'RememberMe Cookie Token'}, err)
* @return {Object} async({date => 'Login Date', token => 'RememberMe Cookie Token'}, err)
*/
methods.addActivity = async (id, data) => {
if(typeof id !== 'string') return {err: new TypeError('id is not a string::database.updateNewAction('+id+','+JSON.stringify(options)+')', module.filename)};
if(!(typeof id === 'string' || id instanceof mongoose.Types.ObjectId)) return {err: new TypeError('id is not a string::database.updateNewAction('+id+','+JSON.stringify(options)+')', module.filename)};
if(typeof options !== 'object' && options !== null) return {err: new TypeError('obj is not an object::database.updateUserProfile('+id+','+JSON.stringify(obj)+')', module.filename)};
let date = new Date().toISOString();
@ -260,6 +263,204 @@ methods.addActivity = async (id, data) => {
}
};
// //////// /////// //////// // // /////// //////
// // // // // // // // // // //
// //////// /////// // // // // /////// //////
// // // // // // // // // // //
// //////// // // //////// //////// // //////
//
/////////////////////////////////////////////////////////////////
/**
* get all groups
* @author Ruben Meyer
* @async
* @return {Object} async(reply, err)
*/
methods.getGroups = async () => {
let groupModel = models.group;
try {
groups = await groupModel.find({}).exec();
if(groups.length > 0)
return {reply: groups};
else
return {reply: false};
} catch(err) {
return {err: err};
}
};
/**
* query groups by UUID or name
* @author Ruben Meyer
* @async
* @param {String|String[]} haystack UUID or name
* @return {Object} async(reply, err)
*/
methods.getGroup = async (haystack) => {
if(typeof haystack !== 'string' && typeof haystack !== 'object') return {err: new TypeError('haystack is not a string|object::database.getGroup('+haystack+')', module.filename)};
let groupModel = models.group;
// sanitize input
haystack = sanitize(haystack);
let or = [];
if(haystack instanceof mongoose.Types.ObjectId) {
or.push({_id: haystack});
}
else if(typeof haystack === 'string') {
or = [{name: haystack}];
if(haystack.match(/^[0-9a-fA-F]{24}$/)) or.push({_id: mongoose.Types.ObjectId(haystack)});
}
else {
or = [];
for(let i = 0; i < haystack.length; i++) {
if(haystack[i].match(/^[0-9a-fA-F]{24}$/)) or.push({_id: mongoose.Types.ObjectId(haystack[i])});
or.push({name: haystack[i]});
}
}
try {
groups = await groupModel.find().or(or).exec();
if(groups.length == 1)
return {reply: groups[0]};
else
return {reply: false};
} catch(err) {
return {err: err};
}
};
/**
* Adds Group to Database
* @author Ruben Meyer
* @async
* @param {String} name name
* @param {String} roles roles
* @return {Object} async (reply, err)
*/
methods.addGroup = async (name, roles) => {
if(typeof name !== 'string') return {err: new TypeError('name is not a string::database.addGroup('+name+','+roles+')', module.filename)};
if(typeof roles !== 'string') return {err: new TypeError('roles is not a string::database.addGroup('+name+','+roles+')', module.filename)};
let groupModel = models.group;
let group = new groupModel();
// sanitize input
group.name = sanitize(nick);
group.roles = sanitize(email);
try {
reply = await group.save();
return {reply: 1};
} catch(err) {
return {err: err};
}
};
/**
* updates obj keys in group entry
* @author Ruben Meyer
* @async
* @param {Number} id Group ID
* @param {Object} obj data
* @return {Object} async(reply, err)
*/
methods.updateGroup = async (id, obj) => {
if(!(typeof id === 'string' || id instanceof mongoose.Types.ObjectId)) return {err: new TypeError('id is not a string::database.updateGroup('+id+','+JSON.stringify(obj)+')', module.filename)};
if(typeof obj !== 'object') return {err: new TypeError('obj is not an object::database.updateGroup('+id+','+JSON.stringify(obj)+')', module.filename)};
let groupModel = models.group;
try {
data = await groupModel.findByIdAndUpdate(id, obj).exec();
return {reply: data};
} catch(err) {
return {err: err};
}
};
/**
* delete group and set fallback group for users
* @author Ruben Meyer
* @async
* @param {String} id group id
* @param {String} fallbackId fallback group id
* @return {Boolean}
*/
methods.delGroup = async (id, fallbackId) => {
if(typeof id !== 'string') return {err: new TypeError('id is not a string::database.delGroup('+id+','+ fallbackId +')', module.filename)};
if(typeof fallbackId !== 'string') return {err: new TypeError('fallbackId is not a string::database.delGroup('+id+','+ fallbackId +')', module.filename)};
let groupModel = models.group;
let userModel = models.user;
let pathModel = models.pathRules;
// sanitize input
id = sanitize(id);
fallbackId = sanitize(fallbackId);
try {
try {
// find users
users = await userModel.find({group: id}).exec();
// set fallback group for each user
users.forEach(async (user) => {
await userModel.findByIdAndUpdate(user._id, {group: fallbackId}).exec();
});
// find rules
rules = await pathModel.find({group: id}).exec();
// set fallback group for each rule
rules.forEach(async (rule) => {
await pathModel.findByIdAndUpdate(rule._id, {group: fallbackId}).exec();
});
// remove group
reply = await groupModel.findByIdAndRemove(id).exec();
return {reply: 1};
} catch (e) {
return {err: e};
}
} catch(err) {
return {err: err};
}
};
// //////// //////// ////////// // // ////////
// // // // // // // // //
// //////// // // // // // //
// // //////// // //////// ////////
// // // // // // // //
// // // // // // // ////////
//
////////////////////////////////////////////////////////////
/**
* get pathrules
* @author Ruben Meyer
* @async
* @return {Object} async(rules, err)
*/
methods.getPathRules = async () => {
var PathRules = models.pathRules;
try {
rules = await PathRules.find({}).exec();
return {reply: rules};
} catch(err) {
return {err: err};
}
};
// //////// //////// //////// //////
// // // // // // // //
@ -269,11 +470,12 @@ methods.addActivity = async (id, data) => {
//
//////////////////////////////////////////////
/**
* get Applications
* @author Ruben Meyer
* @async
* @return {Array} async(apps, err)
* @return {Object} async(apps, err)
*/
methods.getApps = async () => {
var Application = models.application;
@ -289,8 +491,9 @@ methods.getApps = async () => {
* return auth obj
* @author Ruben Meyer
* @async
* @TODO
* @param {Object} obj data obj (aId, uId)
* @return {Array} async({timestamp, token}, err)
* @return {Object} async({timestamp, token}, err)
*/
methods.setAuthCode = async (obj) => {
if(typeof obj !== 'object') return {err: new TypeError('obj is not an object::database.setAuthCode('+JSON.stringify(obj)+')', module.filename)};
@ -322,8 +525,9 @@ methods.setAuthCode = async (obj) => {
* return auth obj
* @author Ruben Meyer
* @async
* @TODO
* @param {Object} obj data obj (aId, aSecret, uId, token)
* @return {Array} async(bool, err)
* @return {Object} async(bool, err)
*/
methods.getAuth = async (obj) => {
if(typeof obj !== 'object') return {err: new TypeError('obj is not an object::database.getAuthCode('+JSON.stringify(obj)+')', module.filename)};
@ -344,9 +548,11 @@ methods.getAuth = async (obj) => {
var Application = models.application;
try {
if(!(obj.aId instanceof mongoose.Types.ObjectId)) obj.aId = mongoose.Types.ObjectId(obj.aId);
data1 = await Application.findOne({
$and: [
{_id: mongoose.Types.ObjectId(obj.aId)},
{_id: obj.aId},
{secret: obj.aSecret}
]
}).exec();
@ -373,11 +579,12 @@ methods.getAuth = async (obj) => {
};
/**
* return if app is permitted to do access call
* return app permission
* @author Ruben Meyer
* @async
* @TODO
* @param {Object} obj data obj (aId, redirectUrl)
* @return {Array} async(bool, err)
* @return {Object} async(bool, err)
*/
methods.verifyAppCall = async (obj) => {
return {};
@ -395,7 +602,7 @@ methods.verifyAppCall = async (obj) => {
* returns user count
* @author Ruben Meyer
* @async
* @return {Array} async(int, err)
* @return {Object} async(int, err)
*/
methods.userCount = async () => {
let userModel = models.user;

36
bin/web/module.js

@ -45,35 +45,21 @@ methods.start = () => {
//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();
global['logs'].info("[web] (404) path not found: "+joined_path);
// 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();
global['logs'].info("[web] (404) path not found: "+joined_path);
}
});
}
fs.exists(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();
global['logs'].info("[web] (404) path not found: "+joined_path);
}
});
});
// BodyParser & CookieParser

16
bin/web/routes/api.js

@ -76,7 +76,7 @@ getRoutes = async () => {
}
// 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[0].passhash, pass)) {
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'
@ -88,8 +88,8 @@ getRoutes = async () => {
// add session data
req.session.user = {
'id': user.reply[0]._id,
'group': user.reply[0].group
'id': user.reply._id,
'group': user.reply.group
};
return res.type('json').end(JSON.stringify({
@ -233,16 +233,6 @@ getRoutes = async () => {
}
});
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));
});
}
return route;
};

47
bin/web/routes/rules.js

@ -7,11 +7,6 @@
/**
* EXPLANATIONS:
*
* groups: ["anon", "user", "admin"]
* - anon: not logged in; no cookies
* - user: logged in; non-special group
* - admin: logged in; admin group 999 or equivalent
*
* expressions: RegExp tested on req.path
* - ex.:
* - req.path = "/profile/456";
@ -25,44 +20,8 @@
* - 404: File not found
* - missing_permission: Missing Permission page
* - login: login page
*
* NOW ADDED TO DATABASE
*/
let rules = [
{
group: "anon",
expression: "(/blocks/.*)",
rule: "block",
type: "404"
},
{
group: "anon",
expression: "(/error/.*)",
rule: "block",
type: "404"
},
{
group: "anon",
expression: "(/admin/.*)",
rule: "block",
type: "login"
},
{
group: "user",
expression: "(/blocks/.*)",
rule: "block",
type: "404"
},
{
group: "user",
expression: "(/error/.*)",
rule: "block",
type: "404"
},
{
group: "user",
expression: "(/admin/.*)",
rule: "block",
type: "missing_permission"
}
];
module.exports = rules;
module.exports = [];

104
bin/web/routes/static.js

@ -13,26 +13,6 @@ path = require('path');
var cfg = require(global['__dirname']+'/bin/config');
// reduce IO file checks - save file state in cache
var fileCheck = (file) => {
if(typeof global['gds'].cache.web == 'undefined') global['gds'].cache.web = {};
let dir = global['__dirname'] + '/bin/web/views';
let path_j = path.join(dir, file.toLowerCase());
if(typeof global['gds'].cache.web[path_j] == 'undefined') {
if(fs.existsSync(path_j+'.pug')) {
global['gds'].cache.web[path_j] = true;
} else {
global['gds'].cache.web[path_j] = false;
}
}
if(global['gds'].cache.web[path_j] === true) {
return path_j;
} else {
return false;
}
};
let getRoutes = async () => {
let db = global['requireModule']('database');
await db.connect();
@ -40,22 +20,27 @@ let getRoutes = async () => {
/**
* main page
* @url /
* @method all
* @method GET
*/
route.all('/', asyncer(async (req, res, next) => {
// TODO: show login page or dashboard
// res.end('login or dashboard');
apps = await db.getApps();
res.render('index', {
route.get('/', asyncer(async (req, res, next) => {
obj = {
session: req.session,
apps: apps.reply,
cfg: cfg
});
};
// if user is logged in
if(req.session && req.session.user) {
obj.user = (await db.getUser(req.session.user.id)).reply;
obj.group = (await db.getGroup(obj.user.group)).reply;
apps = await db.getApps();
obj.apps = apps.reply;
}
res.render('index', obj);
}));
/**
* login page or apprequest page
* @url /
* @url /authenticate
* @method GET
*/
route.get('/authenticate', asyncer(async (req, res) => {
@ -75,25 +60,29 @@ let getRoutes = async () => {
// req.query.appId
// verify appId (if in rep)
req.session.appRequest.appId = req.query.appId;
// TODO: on accept, setAuthCode and redirect with token
// on cancel, redirect to dashboard
}
} else {
return res.redirect('/');
}
// if user is logged in, show request page
if(req.session && req.session.user) {
res.render('request', {
user = await db.getUser(req.session.user.id);
group = await db.getGroup(user.reply.group);
return res.render('request', {
session: req.session,
appRequest: req.session.appRequest,
apps: apps.reply,
cfg: cfg
cfg: cfg,
user: user.reply,
group: group.reply
});
// if user isnt logged in, show login page
} else {
if(!req.query.appId) req.session.appRequest = {};
let view_obj = { session: req.session };
let view_obj = { session: req.session, cfg: cfg };
if(req.query.appId) {
apps.reply.forEach((app) => {
if(app._id == req.query.appId)
@ -102,7 +91,10 @@ let getRoutes = async () => {
}
res.render('login', view_obj);
return res.render('login', view_obj);
}
}));
}
}));
@ -110,30 +102,36 @@ let getRoutes = async () => {
* all other routes
* @url /*
* @method all
* @TODO comments
*/
route.all('/*', asyncer(async (req, res, next) => {
route.get('/*', asyncer(async (req, res, next) => {
// passthrough to next route
if(req.path.startsWith('/api'))
return next();
if(req.path == "/request") return res.render('error/404');
let pathRules = require("./rules");
let pathRules = await db.getPathRules();
let group = "anon";
// retrieve guest group - set as default
let groups = await db.getGroups();
guestId = null;
groups.reply.forEach((group) => {
if(group.name == "Guest") guestId = group._id;
});
let group = guestId;
// set user group
if(req.session && req.session.user) {
group = "user";
if(req.session.user.group == 999) group = "admin";
group = req.session.user.group;
}
pathRules.forEach((rule) => {
for(i = 0; i < pathRules.reply.length; i++) {
rule = pathRules.reply[i];
if(rule.rule == "block") {
if(group == rule.group) {
if(group == String(rule.group)) {
let regex = new RegExp(rule.expression, "g");
if(regex.test(req.path)) {
if(rule.type == "404") {
global['logs'].info("[web] (404) path not found: "+req.path);
return res.status(404).render('error/404', {
error_code: 404,
error_msg: 'msg.request.file.not_found',
@ -146,7 +144,7 @@ let getRoutes = async () => {
session: req.session,
cfg: cfg
});
} else if(rule.type == "login") {
} else if(rule.type == "login" && (!req.session || !req.session.user)) {
return res.status(401).render('error/login', {
error_code: 401,
session: req.session,
@ -162,15 +160,13 @@ let getRoutes = async () => {
}
}
}
});
if(fileCheck(req.path)) {
// query apps
apps = await db.getApps();
};
let dir = global['__dirname'] + '/bin/web/views';
let path_j = path.join(dir, req.path.toLowerCase());
if(fs.existsSync(path_j+'.pug')) {
return res.render(req.path.replace(/^\//, ''), {
session: req.session,
apps: apps.reply,
cfg: cfg
});
} else {
@ -182,10 +178,6 @@ let getRoutes = async () => {
cfg: cfg
});
}
// TODO: try to login
// TODO: role-based authorization
// TODO: show login page or page
}));
return route;

8
bin/web/views/blocks/error/login.pug

@ -7,7 +7,10 @@ append var
div(class="uk-width-auto [email protected]")
.uk-flex.uk-flex-auto.uk-flex-column.uk-flex-center.uk-margin-left.uk-margin-right
h1 Please login
form.uk-form-horizontal
#login_msg.uk-alert(data-uk-alert).uk-hidden
a.uk-close-alt.uk-alert-close(href="#")
p
form.uk-form-horizontal(onsubmit="return loginEvent();")
.uk-margin
label.uk-form-label(for="login_user") Username / Email
.uk-form-controls
@ -16,5 +19,6 @@ append var
label.uk-form-label(for="login_pass") Password
.uk-form-controls
input.uk-input#login_pass(type="password")
a(href="/login").uk-button.uk-button-default Login
input(hidden,type="submit")
button(onclick="login()").uk-button.uk-button-default Login
div(class="uk-width-auto [email protected]")

5
bin/web/views/blocks/nav.pug

@ -1,7 +1,8 @@
mixin navItem(name, id, symbol, href)
li(title=name, id=id)
a(href=href)
i.fa-fw(class=symbol)
if(symbol !== "")
i.fa-fw(class=symbol)
span= name
// Navigation
@ -14,6 +15,8 @@ nav(uk-navbar).uk-navbar-container
.uk-navbar-right.uk-margin-right
ul.uk-navbar-nav
if(session && session.user)
if(group && group.name == cfg.app.adminGroupname)
+navItem("Administration", "admin", "", "/admin")
+navItem("Apps", "apps", "fas fa-tachometer-alt", "/")
+navItem("Settings", "settings", "fas fa-wrench", "/settings")
+navItem("Logout", "logout", "fas fa-sign-out-alt", "/logout")

9
res/web/js/custom.js

@ -1,7 +1,6 @@
function loginEvent() {
login();
return false;
}
// '/login', login user
function loginEvent() { login(); return false; } // btnEvent handling
function login() {
let user = document.getElementById("login_user").value;
let pass = document.getElementById("login_pass").value;
@ -42,6 +41,7 @@ function login() {
};
};
// '/logout', logout user
function logout() {
let ajax = new XMLHttpRequest();
ajax.open("GET", "/api/logout", true);
@ -58,6 +58,7 @@ setTimeout(function () {
}
}, 100);
// '/authenticate', cancels Authentication
function cancelRequest() {
let ajax = new XMLHttpRequest();
ajax.open("GET", "/api/cancel", true);

Loading…
Cancel
Save