diff --git a/package-lock.json b/package-lock.json index 69edebe..2b73f9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3585,6 +3585,12 @@ "fd-slicer": "1.0.1" } }, + "mustache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.0.tgz", + "integrity": "sha1-QCj3d4sXcIpImTCm5SrDvKDaQdA=", + "dev": true + }, "mute-stream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", diff --git a/package.json b/package.json index 86c8af4..29af1c8 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "concurrently": "^3.5.0", "eslint-plugin-import": "^2.7.0", "jquery": "^3.2.1", + "mustache": "^2.3.0", "standard": "^10.0.3", "uglify-js": "^1.3.5", "watch": "^1.0.2", diff --git a/server/api/admin.js b/server/api/admin.js new file mode 100644 index 0000000..65ecb56 --- /dev/null +++ b/server/api/admin.js @@ -0,0 +1,47 @@ +import Users from './index' +import Models from './models' + +const perPage = 6 + +function cleanUserObject (dbe) { + return { + id: dbe.id, + username: dbe.username, + display_name: dbe.display_name, + email: dbe.email, + avatar_file: dbe.avatar_file, + activated: dbe.activated === 1, + locked: dbe.locked === 1, + ip_addess: dbe.ip_addess, + password: dbe.password !== null, + nw_privilege: dbe.nw_privilege, + created_at: dbe.created_at + } +} + +const API = { + getAllUsers: async function (page) { + let count = await Models.User.query().count('id as ids') + if (!count.length || !count[0]['ids'] || isNaN(page)) { + return {error: 'No users found'} + } + + count = count[0].ids + let paginated = Users.Pagination(perPage, parseInt(count), page) + let raw = await Models.User.query().offset(paginated.offset).limit(perPage) + + let users = [] + for (let i in raw) { + let entry = raw[i] + + users.push(cleanUserObject(entry)) + } + + return { + page: paginated, + users: users + } + } +} + +module.exports = API diff --git a/server/api/index.js b/server/api/index.js index 03bb21e..37a54c5 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -55,6 +55,24 @@ const API = { Hash: (len) => { return crypto.randomBytes(len).toString('hex') }, + /* ppp - Posts Per Page; dcount - Post Count; page - number of current page */ + Pagination: (ppp, dcount, page) => { + if (!ppp) ppp = 5 + if (!dcount) return null + + let pageCount = Math.ceil(dcount / ppp) + if (page > pageCount) page = pageCount + + let offset = (page - 1) * ppp + + return { + page: page, + perPage: ppp, + pages: pageCount, + offset: offset, + total: dcount + } + }, User: { get: async function (identifier) { let scope = 'id' diff --git a/server/api/news.js b/server/api/news.js index 8856db2..41d2f92 100644 --- a/server/api/news.js +++ b/server/api/news.js @@ -7,25 +7,6 @@ function slugify (title) { return title.toLowerCase().replace(/\W/g, '-').substring(0, 16) } -/* ppp - Posts Per Page; dcount - Post Count; page - number of current page */ -function Pagination (ppp, dcount, page) { - if (!ppp) ppp = 5 - if (!dcount) return null - - let pageCount = Math.ceil(dcount / ppp) - if (page > pageCount) page = pageCount - - let offset = (page - 1) * ppp - - return { - page: page, - perPage: ppp, - pages: pageCount, - offset: offset, - total: dcount - } -} - async function cleanArticle (entry, shortenContent = false) { let poster = await API.User.get(entry.user_id) let article = { @@ -76,7 +57,7 @@ const News = { } count = count[0].ids - let paginated = Pagination(perPage, parseInt(count), page) + let paginated = API.Pagination(perPage, parseInt(count), page) let news = await Models.News.query().orderBy('created_at', 'desc').offset(paginated.offset).limit(perPage) let articles = [] diff --git a/server/routes/admin.js b/server/routes/admin.js index c8d42ec..4262ced 100644 --- a/server/routes/admin.js +++ b/server/routes/admin.js @@ -1,20 +1,19 @@ import express from 'express' -import multiparty from 'multiparty' import config from '../../scripts/load-config' import wrap from '../../scripts/asyncRoute' -import API from '../api' +import {User} from '../api' +import API from '../api/admin' import News from '../api/news' -import Image from '../api/image' -import APIExtern from '../api/external' const router = express.Router() const apiRouter = express.Router() +// Check for privilege required to access the admin panel router.use(wrap(async (req, res, next) => { if (!req.session.user) return res.redirect('/login') if (!req.session.privilege) { - let u = await API.User.get(req.session.user) + let u = await User.get(req.session.user) req.session.privilege = u.nw_privilege } @@ -26,6 +25,49 @@ router.use(wrap(async (req, res, next) => { next() })) +/* ================ + * ASK PASSWORD + * ================ + */ + +apiRouter.get('/access', (req, res) => { + if (!req.session.accesstime || req.session.accesstime < Date.now()) { + return res.status(401).jsonp({error: 'Access expired'}) + } + + res.jsonp({access: req.session.accesstime - Date.now()}) +}) + +// Post password to continue +router.post('/', wrap(async (req, res, next) => { + if (!req.body.password) return next() + + if (req.body.csrf !== req.session.csrf) { + req.flash('message', {error: true, text: 'Invalid session token'}) + return next() + } + + let passReady = await User.Login.password(req.session.user, req.body.password) + if (passReady) { + req.session.accesstime = Date.now() + 300000 // 5 minutes + return res.redirect('/admin') + } else { + req.flash('message', {error: true, text: 'Invalid password'}) + } + + next() +})) + +// Ensure that the admin panel is not kept open for prolonged time +router.use(wrap(async (req, res, next) => { + if (req.session.accesstime) { + if (req.session.accesstime > Date.now()) return next() + delete req.session.accesstime + } + + res.render('user/password', {post: '/admin'}) +})) + /* ========= * VIEWS * ========= @@ -44,6 +86,16 @@ router.get('/oauth2', wrap(async (req, res) => { * ======= */ +apiRouter.get('/users', wrap(async (req, res) => { + let page = parseInt(req.query.page) + if (isNaN(page) || page < 1) { + page = 1 + } + + let users = await API.getAllUsers(page) + res.jsonp(users) +})) + router.use('/api', apiRouter) module.exports = router diff --git a/server/routes/index.js b/server/routes/index.js index c4b0fb0..a402b9c 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -684,10 +684,7 @@ router.get('/partials/:view', wrap(async (req, res, next) => { */ router.get('/logout', wrap(async (req, res) => { - if (req.session.user) { - delete req.session.user - } - + req.session.destroy() res.redirect('/') })) diff --git a/src/script/admin.js b/src/script/admin.js index 33414da..732861d 100644 --- a/src/script/admin.js +++ b/src/script/admin.js @@ -1,3 +1,72 @@ window.$ = require('jquery') +var Mustache = require('mustache') -$(window).ready(function () {}) +function buildTemplateScript (id, ctx) { + var tmpl = $('#' + id) + if (!tmpl.length) return null + var data = tmpl.html() + Mustache.parse(data) + return Mustache.render(data, ctx) +} + +function paginationButton (pages) { + var html = '