351 lines
8.7 KiB
JavaScript
351 lines
8.7 KiB
JavaScript
import { User, Pagination, Hash, Login, Reset, Register } from './index'
|
|
import * as Models from './models'
|
|
|
|
const perPage = 6
|
|
|
|
async function cleanUserObject (dbe, admin) {
|
|
const totp = await Login.totpTokenRequired(dbe)
|
|
|
|
return {
|
|
id: dbe.id,
|
|
username: dbe.username,
|
|
display_name: dbe.display_name,
|
|
uuid: dbe.uuid,
|
|
email: dbe.email,
|
|
avatar_file: dbe.avatar_file,
|
|
activated: dbe.activated === 1,
|
|
locked: dbe.locked === 1,
|
|
ip_address: dbe.ip_address,
|
|
password: dbe.password !== null,
|
|
nw_privilege: dbe.nw_privilege,
|
|
created_at: dbe.created_at,
|
|
totp_enabled: totp,
|
|
bannable: admin ? (dbe.nw_privilege < admin.nw_privilege && dbe.id !== admin.id) : false
|
|
}
|
|
}
|
|
|
|
async function cleanClientObject (dbe) {
|
|
const user = await User.get(dbe.user_id)
|
|
return {
|
|
id: dbe.id,
|
|
title: dbe.title,
|
|
description: dbe.description,
|
|
url: dbe.url,
|
|
redirect_url: dbe.redirect_url,
|
|
grants: dbe.grants,
|
|
icon: dbe.icon,
|
|
user: {
|
|
id: user.id,
|
|
display_name: user.display_name
|
|
},
|
|
scope: dbe.scope,
|
|
secret: dbe.secret,
|
|
verified: dbe.verified === 1,
|
|
created_at: dbe.created_at
|
|
}
|
|
}
|
|
|
|
async function cleanBanObject (dbe) {
|
|
const user = await User.get(dbe.user_id)
|
|
const admin = await 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()
|
|
}
|
|
}
|
|
|
|
function dataFilter (data, fields, optional = []) {
|
|
// Remove keys not listed in `fields`
|
|
for (const i in data) {
|
|
if (fields.indexOf(i) === -1) {
|
|
delete data[i]
|
|
}
|
|
}
|
|
|
|
// Ensure all the entries in `fields` are present as keys in `data` or are `optional`
|
|
for (const j in fields) {
|
|
if (!data[fields[j]] && optional.indexOf(fields[j]) === -1) return null
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
// List all users (paginated)
|
|
export async function getAllUsers (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 in database' }
|
|
}
|
|
|
|
count = count[0].ids
|
|
const paginated = Pagination(perPage, parseInt(count), page)
|
|
const raw = await Models.User.query().offset(paginated.offset).limit(perPage)
|
|
const admin = await User.get(adminId)
|
|
|
|
const users = []
|
|
for (const i in raw) {
|
|
const entry = raw[i]
|
|
|
|
users.push(await cleanUserObject(entry, admin))
|
|
}
|
|
|
|
return {
|
|
page: paginated,
|
|
users: users
|
|
}
|
|
}
|
|
|
|
export async function getUser (id) {
|
|
const user = await User.get(id)
|
|
if (!user) throw new Error('No such user')
|
|
|
|
return cleanUserObject(user, null)
|
|
}
|
|
|
|
export async function editUser (id, data) {
|
|
const user = await User.get(id)
|
|
if (!user) throw new Error('No such user')
|
|
|
|
const fields = [
|
|
'username', 'display_name', 'email', 'nw_privilege', 'activated'
|
|
]
|
|
|
|
data = dataFilter(data, fields, ['nw_privilege', 'activated'])
|
|
if (!data) throw new Error('Missing fields')
|
|
|
|
await User.update(user, data)
|
|
|
|
return {}
|
|
}
|
|
|
|
export async function resendActivationEmail (id) {
|
|
const user = await User.get(id)
|
|
if (!user) throw new Error('No such user')
|
|
|
|
if (user.activated === 1) return {}
|
|
|
|
await Register.activationEmail(user)
|
|
|
|
return {}
|
|
}
|
|
|
|
export async function revokeTotpToken (id) {
|
|
const user = await User.get(id)
|
|
if (!user) throw new Error('No such user')
|
|
|
|
await Models.TotpToken.query().delete().where('user_id', user.id)
|
|
|
|
return {}
|
|
}
|
|
|
|
export async function sendPasswordEmail (id) {
|
|
const user = await User.get(id)
|
|
if (!user) throw new Error('No such user')
|
|
|
|
const token = await Reset.reset(user.email, false, true)
|
|
|
|
return { token }
|
|
}
|
|
|
|
// Search for users by terms and fields
|
|
export async function searchUsers (terms, fields = ['email']) {
|
|
let qb = Models.User.query()
|
|
|
|
terms = terms.replace(/_/g, '\\_').replace(/%/g, '\\%')
|
|
|
|
qb = qb.where(fields[0], 'like', '%' + terms + '%')
|
|
if (fields.length >= 1) {
|
|
for (let i = 1; i < fields.length; i++) {
|
|
qb = qb.orWhere(fields[i], 'like', '%' + terms + '%')
|
|
}
|
|
}
|
|
|
|
const rows = await qb.limit(8)
|
|
if (!rows.length) return { error: 'No results' }
|
|
|
|
const cleaned = []
|
|
for (const i in rows) {
|
|
const userRaw = rows[i]
|
|
cleaned.push(await cleanUserObject(userRaw, null))
|
|
}
|
|
|
|
return cleaned
|
|
}
|
|
|
|
// List all clients (paginated)
|
|
export async function getAllClients (page) {
|
|
let count = await Models.OAuth2Client.query().count('id as ids')
|
|
if (!count.length || !count[0].ids || isNaN(page)) {
|
|
return { error: 'No clients' }
|
|
}
|
|
|
|
count = count[0].ids
|
|
const paginated = Pagination(perPage, parseInt(count), page)
|
|
const raw = await Models.OAuth2Client.query().offset(paginated.offset).limit(perPage)
|
|
|
|
const clients = []
|
|
for (const i in raw) {
|
|
const entry = raw[i]
|
|
|
|
clients.push(await cleanClientObject(entry))
|
|
}
|
|
|
|
return {
|
|
page: paginated, clients
|
|
}
|
|
}
|
|
|
|
// Get information about a client via id
|
|
export async function getClient (id) {
|
|
const raw = await Models.OAuth2Client.query().where('id', id)
|
|
if (!raw.length) throw new Error('No such client')
|
|
|
|
return cleanClientObject(raw[0])
|
|
}
|
|
|
|
// Update a client `id` in database with `data`
|
|
export async function updateClient (id, data) {
|
|
const fields = [
|
|
'title', 'description', 'url', 'redirect_url', 'scope', 'verified'
|
|
]
|
|
|
|
data = dataFilter(data, fields, ['scope', 'verified'])
|
|
if (!data) throw new Error('Missing fields')
|
|
|
|
try {
|
|
await Models.OAuth2Client.query().patchAndFetchById(id, data)
|
|
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
|
} catch (e) {
|
|
throw new Error('No such client')
|
|
}
|
|
|
|
return {}
|
|
}
|
|
|
|
// Create a new secret for a client
|
|
export async function newSecret (id) {
|
|
if (isNaN(id)) throw new Error('Invalid client ID')
|
|
const secret = Hash(16)
|
|
|
|
try {
|
|
await Models.OAuth2Client.query().patchAndFetchById(id, { secret: secret })
|
|
} catch (e) {
|
|
throw new Error('No such client')
|
|
}
|
|
|
|
return {}
|
|
}
|
|
|
|
// Create a new client
|
|
export async function createClient (data, user) {
|
|
const fields = [
|
|
'title', 'description', 'url', 'redirect_url', 'scope'
|
|
]
|
|
|
|
data = dataFilter(data, fields, ['scope'])
|
|
if (!data) throw new Error('Missing fields')
|
|
|
|
const obj = Object.assign({
|
|
secret: Hash(16),
|
|
grants: 'authorization_code',
|
|
created_at: new Date(),
|
|
user_id: user.id
|
|
}, data)
|
|
|
|
return Models.OAuth2Client.query().insert(obj)
|
|
}
|
|
|
|
// Remove a client and all associated data
|
|
export async function removeClient (id) {
|
|
if (isNaN(id)) throw new Error('Invalid ID number')
|
|
await Models.OAuth2Client.query().delete().where('id', id)
|
|
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
|
await Models.OAuth2AccessToken.query().delete().where('client_id', id)
|
|
await Models.OAuth2RefreshToken.query().delete().where('client_id', id)
|
|
return true
|
|
}
|
|
|
|
// List all bans (paginated)
|
|
export async function getAllBans (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
|
|
const paginated = Pagination(perPage, parseInt(count), page)
|
|
const raw = await Models.Ban.query().offset(paginated.offset).limit(perPage)
|
|
|
|
const bans = []
|
|
for (const i in raw) {
|
|
const entry = raw[i]
|
|
|
|
bans.push(await cleanBanObject(entry))
|
|
}
|
|
|
|
return {
|
|
page: paginated, bans
|
|
}
|
|
}
|
|
|
|
// Remove a ban
|
|
export async function removeBan (banId) {
|
|
return Models.Ban.query().delete().where('id', banId)
|
|
}
|
|
|
|
// Create a ban
|
|
export async function addBan (data, adminId) {
|
|
const user = await User.get(parseInt(data.user_id))
|
|
|
|
if (!user) throw new Error('No such user.')
|
|
if (user.id === adminId) throw new Error('Cannot ban yourself!')
|
|
|
|
const admin = await User.get(adminId)
|
|
|
|
if (user.nw_privilege > admin.nw_privilege) throw new Error('Cannot ban user.')
|
|
|
|
const 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 {}
|
|
}
|
|
|
|
export async function lockAccount (userId) {
|
|
const user = await User.get(userId)
|
|
if (user.id === 1 || user.nw_privilege > 2) {
|
|
throw new Error('Cannot lock this user.')
|
|
}
|
|
|
|
const lockId = Hash(4)
|
|
const userObf = {
|
|
username: lockId,
|
|
display_name: user.username,
|
|
email: `${lockId}@icynet.eu`,
|
|
password: null,
|
|
activated: false,
|
|
locked: true,
|
|
avatar_file: null
|
|
}
|
|
|
|
return User.update(user, userObf)
|
|
}
|