ban management and other changes

This commit is contained in:
Evert Prants 2017-08-29 01:36:13 +03:00
parent de3f8498c2
commit 2899f48d95
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
9 changed files with 246 additions and 18 deletions

View File

@ -26,8 +26,8 @@ console.warn = function () {
const realConsoleError = console.error
console.error = function () {
process.stdout.write('\x1b[2K\r')
process.stdout.write('[ err] [' + dateFormat(new Date()) + '] ')
process.stderr.write('\x1b[2K\r')
process.stderr.write('[ err] [' + dateFormat(new Date()) + '] ')
realConsoleError.apply(this, arguments)
}

View File

@ -3,7 +3,7 @@ import Models from './models'
const perPage = 6
function cleanUserObject (dbe) {
function cleanUserObject (dbe, admin) {
return {
id: dbe.id,
username: dbe.username,
@ -12,10 +12,11 @@ function cleanUserObject (dbe) {
avatar_file: dbe.avatar_file,
activated: dbe.activated === 1,
locked: dbe.locked === 1,
ip_addess: dbe.ip_addess,
ip_address: dbe.ip_address,
password: dbe.password !== null,
nw_privilege: dbe.nw_privilege,
created_at: dbe.created_at
created_at: dbe.created_at,
bannable: dbe.nw_privilege < admin.nw_privilege && dbe.id !== admin.id
}
}
@ -40,8 +41,29 @@ async function cleanClientObject (dbe) {
}
}
async function cleanBanObject (dbe) {
let user = await Users.User.get(dbe.user_id)
let admin = await Users.User.get(dbe.admin_id)
return {
id: dbe.id,
reason: dbe.reason,
user: {
id: user.id,
display_name: user.display_name
},
admin: {
id: admin.id,
display_name: admin.display_name
},
expires_at: dbe.expires_at,
created_at: dbe.created_at,
ip_address: dbe.associated_ip,
expired: dbe.expires_at && new Date(dbe.expires_at).getTime() < Date.now()
}
}
const API = {
getAllUsers: async function (page) {
getAllUsers: async function (page, adminId) {
let count = await Models.User.query().count('id as ids')
if (!count.length || !count[0]['ids'] || isNaN(page)) {
return {error: 'No users found'}
@ -50,12 +72,13 @@ const API = {
count = count[0].ids
let paginated = Users.Pagination(perPage, parseInt(count), page)
let raw = await Models.User.query().offset(paginated.offset).limit(perPage)
let admin = await Users.User.get(adminId)
let users = []
for (let i in raw) {
let entry = raw[i]
users.push(cleanUserObject(entry))
users.push(cleanUserObject(entry, admin))
}
return {
@ -110,6 +133,7 @@ const API = {
try {
await Models.OAuth2Client.query().patchAndFetchById(id, data)
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
} catch (e) {
return {error: 'No such client'}
}
@ -159,6 +183,54 @@ const API = {
await Models.OAuth2AccessToken.query().delete().where('client_id', id)
await Models.OAuth2RefreshToken.query().delete().where('client_id', id)
return true
},
getAllBans: async function (page) {
let count = await Models.Ban.query().count('id as ids')
if (!count.length || !count[0]['ids'] || isNaN(page)) {
return {error: 'No bans on record'}
}
count = count[0].ids
let paginated = Users.Pagination(perPage, parseInt(count), page)
let raw = await Models.Ban.query().offset(paginated.offset).limit(perPage)
let bans = []
for (let i in raw) {
let entry = raw[i]
bans.push(await cleanBanObject(entry))
}
return {
page: paginated,
bans: bans
}
},
removeBan: async function (banId) {
if (isNaN(banId)) return {error: 'Invalid number'}
return Models.Ban.query().delete().where('id', banId)
},
addBan: async function (data, adminId) {
let user = await Users.User.get(parseInt(data.user_id))
if (!user) return {error: 'No such user.'}
if (user.id === adminId) return {error: 'Cannot ban yourself!'}
let admin = await Users.User.get(adminId)
if (user.nw_privilege > admin.nw_privilege) return {error: 'Cannot ban user.'}
let banAdd = {
reason: data.reason || 'Unspecified ban',
admin_id: adminId,
user_id: user.id,
expires_at: data.expires_at != null ? new Date(data.expires_at) : null,
created_at: new Date(),
associated_ip: data.ip_address || user.ip_address || null
}
await Models.Ban.query().insert(banAdd)
return {}
}
}

View File

@ -49,7 +49,7 @@ router.post('/', wrap(async (req, res, next) => {
let passReady = await User.Login.password(req.session.user, req.body.password)
if (passReady) {
req.session.accesstime = Date.now() + 300000 // 5 minutes
req.session.accesstime = Date.now() + 600000 // 10 minutes
return res.redirect('/admin')
} else {
req.flash('message', {error: true, text: 'Invalid password'})
@ -62,7 +62,7 @@ router.post('/', wrap(async (req, res, next) => {
router.use(wrap(async (req, res, next) => {
if (req.session.accesstime) {
if (req.session.accesstime > Date.now()) {
req.session.accesstime = Date.now() + 300000
req.session.accesstime = Date.now() + 600000
return next()
}
@ -96,10 +96,14 @@ apiRouter.get('/users', wrap(async (req, res) => {
page = 1
}
let users = await API.getAllUsers(page)
let users = await API.getAllUsers(page, req.session.user.id)
res.jsonp(users)
}))
/* ===============
* OAuth2 Data
* ===============
*/
apiRouter.get('/clients', wrap(async (req, res) => {
let page = parseInt(req.query.page)
if (isNaN(page) || page < 1) {
@ -177,6 +181,49 @@ apiRouter.post('/client/delete/:id', wrap(async (req, res) => {
res.jsonp(client)
}))
/* ========
* Bans
* ========
*/
apiRouter.get('/bans', wrap(async (req, res) => {
let page = parseInt(req.query.page)
if (isNaN(page) || page < 1) {
page = 1
}
let bans = await API.getAllBans(page)
res.jsonp(bans)
}))
apiRouter.post('/ban/pardon/:id', wrap(async (req, res) => {
let id = parseInt(req.params.id)
if (isNaN(id)) {
return res.status(400).jsonp({error: 'Invalid number'})
}
let ban = await API.removeBan(id)
if (ban.error) {
return res.status(400).jsonp({error: ban.error})
}
res.jsonp(ban)
}))
apiRouter.post('/ban', wrap(async (req, res) => {
if (!req.body.user_id) return res.status(400).jsonp({error: 'ID missing'})
if (req.body.csrf !== req.session.csrf) {
return res.status(400).jsonp({error: 'Invalid session'})
}
let result = await API.addBan(req.body, req.session.user.id)
if (result.error) {
return res.status(400).jsonp({error: result.error})
}
res.jsonp(result)
}))
apiRouter.use((err, req, res, next) => {
console.error(err)
return res.status(500).jsonp({error: 'Internal server error'})

View File

@ -53,7 +53,9 @@ router.use(wrap(async (req, res, next) => {
}
if (req.session.user.session_refresh < Date.now()) {
// Check for ban
console.debug('User session update')
// Check for bans
let banStatus = await API.User.getBanStatus(req.session.user.id)
if (banStatus.length) {
@ -63,6 +65,9 @@ router.use(wrap(async (req, res, next) => {
// Update user session
let udata = await API.User.get(req.session.user.id)
// Update IP address
await API.User.update(udata, {ip_address: req.realIP})
setSession(req, udata)
}
}

View File

@ -25,6 +25,67 @@ function paginationButton (pages) {
return html
}
function banUser (id) {
window.Dialog.openTemplate('Ban User', 'banNew', {id: id})
$('#fnsubmit').submit(function (e) {
e.preventDefault()
$.post({
url: '/admin/api/ban',
data: $(this).serialize(),
success: function (data) {
window.Dialog.close()
loadBans(1)
},
error: function (e) {
if (e.responseJSON && e.responseJSON.error) {
$('form .message').show()
$('form .message').text(e.responseJSON.error)
}
}
})
})
}
function loadBans (page) {
$.ajax({
type: 'get',
url: '/admin/api/bans',
data: {page: page},
success: function (data) {
$('#banlist').html('')
if (data.error) {
$('#banlist').html('<div class="message">' + data.error + '</div>')
return
}
var pgbtn = paginationButton(data.page)
$('#banlist').append(pgbtn)
$('#banlist .pgn .button').click(function (e) {
var pgnum = $(this).data('page')
if (pgnum == null) return
loadBans(parseInt(pgnum))
})
for (var u in data.bans) {
var ban = data.bans[u]
ban.created_at = new Date(ban.created_at)
ban.expires_at = ban.expires_at === null ? 'Never' : new Date(ban.expires_at)
var tmp = buildTemplateScript('ban', ban)
$('#banlist').append(tmp)
}
$('#banlist .remove').click(function (e) {
$.post({
url: '/admin/api/ban/pardon/' + parseInt($(this).data('id')),
success: function (data) {
loadBans(1)
}
})
})
}
})
}
function loadUsers (page) {
$.ajax({
type: 'get',
@ -39,7 +100,7 @@ function loadUsers (page) {
var pgbtn = paginationButton(data.page)
$('#userlist').append(pgbtn)
$('.pgn .button').click(function (e) {
$('#userlist .pgn .button').click(function (e) {
var pgnum = $(this).data('page')
if (pgnum == null) return
loadUsers(parseInt(pgnum))
@ -51,6 +112,10 @@ function loadUsers (page) {
var tmp = buildTemplateScript('user', user)
$('#userlist').append(tmp)
}
$('#userlist .ban').click(function (e) {
banUser(parseInt($(this).data('id')))
})
}
})
}
@ -110,7 +175,7 @@ function loadClients (page) {
var pgbtn = paginationButton(data.page)
$('#clientlist').append(pgbtn)
$('.pgn .button').click(function (e) {
$('#clientlist .pgn .button').click(function (e) {
var pgnum = $(this).data('page')
if (pgnum == null) return
loadClients(parseInt(pgnum))
@ -123,17 +188,17 @@ function loadClients (page) {
$('#clientlist').append(tmp)
}
$('.edit').click(function (e) {
$('#clientlist .edit').click(function (e) {
var client = $(this).data('client')
editClient(parseInt(client))
})
$('.delete').click(function (e) {
$('#clientlist .delete').click(function (e) {
var client = $(this).data('client')
deleteClient(parseInt(client))
})
$('.newsecret').click(function (e) {
$('#clientlist .newsecret').click(function (e) {
var client = $(this).data('client')
$.post({
url: '/admin/api/client/new_secret/' + parseInt(client),
@ -175,6 +240,10 @@ $(document).ready(function () {
loadUsers(1)
}
if ($('#banlist').length) {
loadBans(1)
}
if ($('#clientlist').length) {
loadClients(1)

View File

@ -40,6 +40,8 @@ nav
display: inline-block
.right
float: right
.stamps
float: right
.user
min-height: 180px

View File

@ -8,6 +8,10 @@ block body
.users
h3 Registered Users
#userlist
.right
.users
h3 Bans
#banlist
.templates
script(type="x-tmpl-mustache" id="user").
<div class="user" id="user-{{id}}">
@ -22,7 +26,7 @@ block body
<div class="info">
<div class="stamps">
{{^activated}}
<div class="noactive"><i class="fa fa-fw fa-envelope"></i></div>
<div class="noactive" title="Not activated"><i class="fa fa-fw fa-envelope"></i></div>
{{/activated}}
</div>
<div class="display_name">{{display_name}}</div>
@ -33,5 +37,33 @@ block body
{{^password}}
<div class="external"><b>Used external login</b></div>
{{/password}}
{{#bannable}}
<div class="button ban" data-id="{{id}}"><i class="fa fa-fw fa-ban"></i>Ban User</div>
{{/bannable}}
</div>
</div>
script(type="x-tmpl-mustache" id="ban").
<div class="ban" id="ban-{{user.id}}">
<div class="stamps">
{{#expired}}
<div class="noactive" title="Expired"><i class="fa fa-fw fa-ban"></i></div>
{{/expired}}
</div>
<div class="display_name">User: {{user.display_name}}</div>
<div class="display_name">Admin: {{admin.display_name}}</div>
<div class="description">Reason: {{reason}}</div>
<div class="timestamp">Placed {{created_at}}</div>
<div class="timestamp">Expires {{expires_at}}</div>
<div class="button remove" data-id="{{id}}">Pardon</div>
</div>
script(type="x-tmpl-mustache" id="banNew").
<form id="fnsubmit">
<div class="message error"></div>
<input type="hidden" name="csrf" value="#{csrf}">
<input type="hidden" name="user_id" value="{{id}}">
<label for="reason">Reason</label>
<input type="text" id="reason" name="reason">
<label for="expires_at">Expires</label>
<input type="date" id="expires_at" name="expires_at">
<input type="submit" value="Create">
</form>

View File

@ -5,6 +5,7 @@ html
link(rel="stylesheet", type="text/css", href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css")
link(rel="stylesheet", type="text/css", href="/style/main.css")
link(rel="stylesheet", type="text/css", href="/style/admin.css")
block links
script.
window.variables = {
server_time: parseInt('#{server_time}')

View File

@ -1,7 +1,7 @@
extends layout.pug
block title
|Icy Network - Legal Notice
|Icy Network
block body
.document