From a3b8a865fde13805e4c26e9133c6a052ad9ace92 Mon Sep 17 00:00:00 2001 From: Valentin PUCCETTI Date: Sun, 10 Sep 2023 20:52:09 +0200 Subject: [PATCH] admin dashboard, database updated (members, groups), group setup and permissions --- index.js | 37 +++++++++-- model/group.model.js | 17 +++++ model/key.model.js | 29 ++++++++ model/member.model.js | 25 +++++++ model/user.model.js | 11 +++- package-lock.json | 54 ++++++++++++--- package.json | 1 + routes/admin.route.js | 124 +++++++++++++++++++++++++++++++++++ routes/auth.route.js | 2 +- routes/keys.route.js | 40 +++++++++++ services/database.service.js | 4 +- services/group.service.js | 59 +++++++++++++++++ services/keys.service.js | 49 ++++++++++++++ services/users.service.js | 21 ++++++ views/admin/group_edit.ejs | 94 ++++++++++++++++++++++++++ views/admin/group_new.ejs | 22 +++++++ views/admin/groups.ejs | 51 ++++++++++++++ views/admin/users.ejs | 54 +++++++++++++++ views/footer.ejs | 21 ++++++ views/keys.ejs | 25 +++---- views/navbar.ejs | 17 +++-- 21 files changed, 723 insertions(+), 34 deletions(-) create mode 100644 model/group.model.js create mode 100644 model/key.model.js create mode 100644 model/member.model.js create mode 100644 routes/admin.route.js create mode 100644 routes/keys.route.js create mode 100644 services/group.service.js create mode 100644 services/keys.service.js create mode 100644 services/users.service.js create mode 100644 views/admin/group_edit.ejs create mode 100644 views/admin/group_new.ejs create mode 100644 views/admin/groups.ejs create mode 100644 views/admin/users.ejs diff --git a/index.js b/index.js index 82794eb..9e2e44d 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,13 @@ const express = require("express"); const app = express(); const session = require('express-session'); const databaseService = require('./services/database.service'); +const bodyParser = require('body-parser'); + +const groupService = require("./services/group.service"); + +const User = require('./model/user.model') +const Key = require('./model/key.model') +const {makeAdmin} = require("./services/users.service"); databaseService.sync().then(() => { console.log("Database ready"); @@ -11,6 +18,11 @@ databaseService.sync().then(() => { require('dotenv').config() +app.use(bodyParser.urlencoded({ extended: false })) + +// parse application/json +app.use(bodyParser.json()) + app.use( session({ secret: process.env.SESSION_SECRET, @@ -19,10 +31,22 @@ app.use( }) ); + app.set('trust proxy', 1) app.set('view engine', 'ejs'); app.use('/static', express.static('public')); +app.use(function(req, res, next) { + if (req.session.loggedin === true) { + User.findOne({ where: { id: req.session.user.id } }).then((result) => { + res.locals.session_user = result + next() + }); + } else { + next() + } +}); + app.get("/", (req, res) => { if (req.session.loggedin === true) { res.render('index', { user: req.session.user }) @@ -31,17 +55,20 @@ app.get("/", (req, res) => { } }); + app.get("/login", (req, res) => { res.render('login') }); -app.get("/keys", (req, res) => { - res.render('keys') -}); -var auth_route = require('./routes/auth.route'); -app.use('/auth/', auth_route); + + + + +app.use('/admin/', require('./routes/admin.route')); +app.use('/auth/', require('./routes/auth.route')); +app.use('/keys/', require('./routes/keys.route')); app.listen(8080, () => { console.log("running"); diff --git a/model/group.model.js b/model/group.model.js new file mode 100644 index 0000000..70b2e46 --- /dev/null +++ b/model/group.model.js @@ -0,0 +1,17 @@ +const { Model, DataTypes} = require('sequelize'); +const sequelize = require('../services/database.service'); + +class Group extends Model {} + +Group.init({ + name: { + type: DataTypes.STRING, + primaryKey: true, + required: true, + } +}, { + sequelize, + modelName: 'group' +}) + +module.exports = Group; \ No newline at end of file diff --git a/model/key.model.js b/model/key.model.js new file mode 100644 index 0000000..bfd99cb --- /dev/null +++ b/model/key.model.js @@ -0,0 +1,29 @@ +const { Model, DataTypes} = require('sequelize'); +const sequelize = require('../services/database.service'); + +class Key extends Model {} + +Key.init({ + idKey: { + type: DataTypes.STRING, + primaryKey: true, + required: true, + }, + idOwner: { + type: DataTypes.INTEGER, + required: true, + }, + content: { + type: DataTypes.STRING, + required: true, + }, + name: { + type: DataTypes.STRING, + required: true, + }, +}, { + sequelize, + modelName: 'key' +}) + +module.exports = Key; \ No newline at end of file diff --git a/model/member.model.js b/model/member.model.js new file mode 100644 index 0000000..f6e53b2 --- /dev/null +++ b/model/member.model.js @@ -0,0 +1,25 @@ +const { Model, DataTypes} = require('sequelize'); +const sequelize = require('../services/database.service'); + +class Member extends Model {} + +Member.init({ + groupName: { + type: DataTypes.STRING, + required: true, + }, + userId: { + type: DataTypes.INTEGER, + required: true, + }, + role: { + type: DataTypes.STRING, + required: true, + defaultValue: 'member' + } +}, { + sequelize, + modelName: 'member' +}) + +module.exports = Member; \ No newline at end of file diff --git a/model/user.model.js b/model/user.model.js index e29034b..ba00100 100644 --- a/model/user.model.js +++ b/model/user.model.js @@ -7,16 +7,25 @@ User.init({ id: { type: DataTypes.INTEGER, primaryKey: true, + required: true, + }, + admin: { + type: DataTypes.BOOLEAN, + defaultValue: false, + required: true, }, login: { type: DataTypes.STRING, + required: true, }, avatar: { type: DataTypes.STRING, + required: true, }, displayName: { type: DataTypes.STRING, - } + required: true, + }, }, { sequelize, modelName: 'user' diff --git a/package-lock.json b/package-lock.json index e265886..f550d93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "GPL-3.0-or-later", "dependencies": { "axios": "^1.5.0", + "body-parser": "^1.20.2", "dotenv": "^16.3.1", "ejs": "^3.1.9", "express": "^4.18.2", @@ -253,12 +254,12 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -266,7 +267,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -652,6 +653,43 @@ "node": ">= 0.6" } }, + "node_modules/express/node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -1671,9 +1709,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", diff --git a/package.json b/package.json index 398a4a2..d62228e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "homepage": "https://github.com/itsmrval/accessgate#readme", "dependencies": { "axios": "^1.5.0", + "body-parser": "^1.20.2", "dotenv": "^16.3.1", "ejs": "^3.1.9", "express": "^4.18.2", diff --git a/routes/admin.route.js b/routes/admin.route.js new file mode 100644 index 0000000..b6c98da --- /dev/null +++ b/routes/admin.route.js @@ -0,0 +1,124 @@ +const express = require('express'); +const User = require("../model/user.model"); +const Group = require("../model/group.model"); +const keyService = require("../services/keys.service"); +groupService = require("../services/group.service"); +const Member = require("../model/member.model"); + +var router = express.Router(); + +router.use('*', (req, res, next) => { + if (req.session.loggedin === true) { + User.findOne({ where: { id: req.session.user.id } }).then((result) => { + if (result.admin === true) { + next() + } else { + res.redirect('/') + } + }) + } else { + res.redirect('/') + } +}); + + +router.get("/users", (req, res) => { + User.findAll().then((users) => { + res.render('admin/users', { "users": users }) + }) +}) + +router.get("/groups", (req, res) => { + Group.findAll().then((groups) => { + res.render('admin/groups', { "groups": groups }) + }) +}) + +router.post("/groups/add", (req, res) => { + if (req.body.group_name) { + groupService.addGroup(req.body.group_name).then((result) => { + res.redirect("/admin/groups") + }) + } else { + res.redirect("/admin/groups") + } +}) + +router.get("/groups/delete/:group", (req, res) => { + groupService.delGroup(req.params.group).then((result) => { + res.redirect("/admin/groups") + }) + +}); + + +router.get("/groups/:name", async (req, res) => { + if (req.params.name === "new") { + res.render('admin/group_new') + } else { + Group.findOne({ where: { name: req.params.name } }).then((group) => { + groupService.groupUserList(req.params.name).then((result) => { + User.findAll().then((users) => { + for (user in users) { + if (JSON.stringify(result).includes(users[user].dataValues.id)) { + delete users[user] + } + } + res.render('admin/group_edit', { "group": group, "inGroup": result, "outGroup": users }) + + }); + + }) + }); + + } +}) + +router.get('/members/:name/add/:user', (req, res) => { + Group.findOne({ where: { name: req.params.name } }).then((result) => { + if (result) { + User.findOne({ where: { id: req.params.user } }).then((user) => { + if (user) { + Member.findOne({ where: { groupname: result.name, userId: user.id } }).then((member) => { + if (!member) { + Member.create({ + userId: user.id, + groupName: result.name + }).then((member) => { + console.log('member added to database' + '(' + member.userId + ',' + member.groupName + ')') + res.redirect('/admin/groups/' + result.name) + }); + } + }) + } + + }) + } + + }) +}) + +router.get('/members/:name/delete/:user', (req, res) => { + Group.findOne({ where: { name: req.params.name } }).then((result) => { + if (result) { + User.findOne({ where: { id: req.params.user } }).then((user) => { + if (user) { + Member.findOne({ where: { groupname: result.name, userId: user.id } }).then((member) => { + if (member) { + Member.destroy({ where: { groupname: result.name, userId: user.id }}).then((member) => { + console.log('member deleted from database' + '(' + member.userId + ',' + member.groupName + ')') + res.redirect('/admin/groups/' + result.name) + }); + } + }) + } + + }) + } + + }) +}) + + + +module.exports = router; \ No newline at end of file diff --git a/routes/auth.route.js b/routes/auth.route.js index 29ca955..23da411 100644 --- a/routes/auth.route.js +++ b/routes/auth.route.js @@ -1,5 +1,4 @@ const express = require('express'); -const {default: axios} = require("axios"); const authService = require("../services/auth.service"); var router = express.Router(); @@ -18,6 +17,7 @@ router.get("/callback", async (req, res) => { req.session.access_token = access_token; req.session.user = user; req.session.loggedin = true; + req.session.admin = user.admin; res.redirect("/"); } else { res.send("An error occured"); diff --git a/routes/keys.route.js b/routes/keys.route.js new file mode 100644 index 0000000..c333960 --- /dev/null +++ b/routes/keys.route.js @@ -0,0 +1,40 @@ +const express = require('express'); +const keyService = require("../services/keys.service"); +const Key = require("../model/key.model"); + +var router = express.Router(); + +router.get('*', (req, res, next) => { + if (req.session.loggedin === true) { + next() + } else { + res.redirect('/') + } +}); + +router.post("/add", (req, res) => { + if (req.body.key_content && req.body.key_name) { + keyService.addKey(req.body.key_content, req.body.key_name, req.session.user.id).then((result) => { + res.redirect("/keys") + }) + } else { + res.redirect("/keys") + } + +}) + +router.get("/delete/:key", (req, res) => { + keyService.delKey(req.params.key, req.session.user.id).then((result) => { + res.redirect("/keys") + }) + +}); + + +router.get("/", (req, res) => { + Key.findAll({where: {idOwner: req.session.user.id}}).then((keys) => { + res.render('keys', { "keys": keys }) + }) +}); + +module.exports = router; \ No newline at end of file diff --git a/services/database.service.js b/services/database.service.js index eb7b79b..44f8c7f 100644 --- a/services/database.service.js +++ b/services/database.service.js @@ -2,7 +2,9 @@ const Sequelize = require('sequelize'); const sequelize = new Sequelize('accessgate', 'user', 'password', { dialect: 'sqlite', - host: './database.db' + host: './database.db', + logging: false }) + module.exports = sequelize; \ No newline at end of file diff --git a/services/group.service.js b/services/group.service.js new file mode 100644 index 0000000..f54fceb --- /dev/null +++ b/services/group.service.js @@ -0,0 +1,59 @@ +const Group = require('../model/group.model') +const Members = require('../model/member.model') +const regexp = /^\S*$/; +const User = require('../model/user.model') + +async function addGroup(name) { + Group.findOne({where: { name: name}}).then((result) => { + if (result) { + return false; + } else { + if (name && regexp.test(name)) { + Group.create({ + name: name, + }).then((result) => { + console.log('Group ' + result.name + ' added to database') + }); + } else { + return false; + } + + } + }); +} + +async function delGroup(name) { + Group.findOne({where: { name: name}}).then((result) => { + if (result && regexp.test(name)) { + result.destroy() + .then(() => { + console.log('group ' + result.name + ' added to database') + }); + } else { + return false; + + + } + }); +} + +async function groupUserList(groupName) { + User.hasMany(Members); + Members.belongsTo(User); + const users = await User.findAll({ include: Members }); + var result = [] + for (x in users) { + try { + if (users[x].dataValues.members[0].dataValues.groupName === groupName) { + result[x] = (users[x].dataValues) + } + } catch (error) { + }} + return result +}; + +module.exports = { + addGroup, + delGroup, + groupUserList +}; \ No newline at end of file diff --git a/services/keys.service.js b/services/keys.service.js new file mode 100644 index 0000000..ae167ba --- /dev/null +++ b/services/keys.service.js @@ -0,0 +1,49 @@ +const Key = require('../model/key.model') + +const regexp = /^\S*$/; + +async function addKey(content, name, idOwner) { + const id_key = idOwner.toString() + name; + Key.findOne({where: { idKey: id_key}}).then((result) => { + if (result) { + return false; + } else { + if (content && name && idOwner && regexp.test(name, idOwner, content)) { + Key.create({ + idKey: id_key, + idOwner: idOwner, + content: content, + name: name, + }).then((key) => { + console.log('key ' + key.idKey + ' added to database') + }); + } else { + return false; + } + + } + }); +} + +async function delKey(id, idOwner) { + Key.findOne({where: { idKey: id}}).then((result) => { + if (result && regexp.test(id,idOwner)) { + if (result.idOwner !== idOwner) { + return false; + } else { + result.destroy() + .then(() => { + console.log('key ' + result.idKey + ' added to database') + }); + } + } else { + return false; + + } + }); +} + +module.exports = { + addKey, + delKey +}; \ No newline at end of file diff --git a/services/users.service.js b/services/users.service.js new file mode 100644 index 0000000..ba8ffd7 --- /dev/null +++ b/services/users.service.js @@ -0,0 +1,21 @@ +const User = require('../model/user.model') + +async function userList(code) { + return await User.findAll() +} + +function makeAdmin(userId) { + User.findOne({ where: { id: userId } }).then((result) => { + if (result) { + result.admin = true; + result.save().then(() => { + console.log('user ' + result.login + ' is now admin') + }); + } + }); +} + +module.exports = { + userList, + makeAdmin +}; \ No newline at end of file diff --git a/views/admin/group_edit.ejs b/views/admin/group_edit.ejs new file mode 100644 index 0000000..5ed3d19 --- /dev/null +++ b/views/admin/group_edit.ejs @@ -0,0 +1,94 @@ +<%- include('../navbar', {active: "admin-groups"}); %> +
+

<%= group.name %>

+
+
+

Group editing

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + <% outGroup.forEach(function (member) { %> + + + + + + <% }) %> + + + + +
Display namegithub id
<%= member.displayName %><%= member.id %>
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + <% inGroup.forEach(function (member) { %> + + + + + + + <% }) %> + + + + +
Display namegithub id
<%= member.displayName %><%= member.id %>
+
+
+
+
+
+
+
+ + <%- include('../footer'); %> \ No newline at end of file diff --git a/views/admin/group_new.ejs b/views/admin/group_new.ejs new file mode 100644 index 0000000..3ddf61a --- /dev/null +++ b/views/admin/group_new.ejs @@ -0,0 +1,22 @@ +<%- include('../navbar', {active: "admin-groups"}); %> +
+

New group:

+
+
+

Group editing

+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + <%- include('../footer'); %> \ No newline at end of file diff --git a/views/admin/groups.ejs b/views/admin/groups.ejs new file mode 100644 index 0000000..af4af55 --- /dev/null +++ b/views/admin/groups.ejs @@ -0,0 +1,51 @@ +<%- include('../navbar', {active: "admin-groups"}); %> + +
+

Admin: Groups

+
+
+

User list

+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + <% groups.forEach(function (group) { %> + + + + + + + <% }) %> + + + + + +
NameMember countServer countActions
<%= group.name %>undefinedundefined
+
+
+
+
+ + + + +<%- include('../footer'); %> \ No newline at end of file diff --git a/views/admin/users.ejs b/views/admin/users.ejs new file mode 100644 index 0000000..a3518b5 --- /dev/null +++ b/views/admin/users.ejs @@ -0,0 +1,54 @@ +<%- include('../navbar', {active: "admin-users"}); %> +
+

Admin: Users

+
+
+

User list

+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + <% users.forEach(function (user) { %> + + + + + + + + <% }) %> + + + + +
Display namegithub idAdminGroupsLast login
<%= user.displayName %><%= user.id %><%= user.admin %> + admin + <%= user.updatedAt %>
+
+
+
+
+ + <%- include('../footer'); %> \ No newline at end of file diff --git a/views/footer.ejs b/views/footer.ejs index 6a14808..ea29d4d 100644 --- a/views/footer.ejs +++ b/views/footer.ejs @@ -5,7 +5,28 @@ + + \ No newline at end of file diff --git a/views/keys.ejs b/views/keys.ejs index 201efd7..9d0ab8a 100644 --- a/views/keys.ejs +++ b/views/keys.ejs @@ -48,25 +48,28 @@ Name Key + - - MacBook Pro - Lorem ipsum - - - PC - Lorem ipsum - + <% keys.forEach(function (key) { %> + + <%= key.name %> + + <%= key.content %> + + + + <% }) %> +
-
-
-
+ +
+
diff --git a/views/navbar.ejs b/views/navbar.ejs index 3faba80..281ff58 100644 --- a/views/navbar.ejs +++ b/views/navbar.ejs @@ -4,17 +4,19 @@ - Dashboard - AccessGate + AccessGate + + + -