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) }