UNPKG

stumble

Version:
713 lines (586 loc) 18.8 kB
'use strict'; const gutil = require('../gutil'); const format = gutil.format; const minmax = gutil.minmax; const defaults = gutil.defaults; const unalias = { handle: 'permissions::unalias', exec: function unalias (data) { if (data.handle && this.aliases.has(data.handle)) { const alias = data.handle; const handle = data.handle = this.aliases.get(alias); if (data.notifyOfAlias) data.notifyOfAlias.sendMessage(`Note: [ ${alias} ] is an alias for [ ${handle} ]`); } } }; const groups = { handle: 'permissions::groups', exec: function groups (data) { const db = this.execute('database::use'); db.all('SELECT * FROM permgroups', (err, rows) => { if (err) this.emit('error', err); if (data.done) data.done(err, rows); }); }, commands: [{ handle: 'groups', exec: function groupscmd (data) { const done = (error, rows) => { if (error || !rows.length) return data.user.sendMessage( error ? 'A database error occurred.' : 'No groups.' ); const tableTemplate = (` <table cellpadding="4"> <tr> <th>Group Name</th> <th>Group Level</th> </tr> %s </table> `); const rowTemplate = (` <tr> <td width="60%" align="center">%s</td> <td width="40%" align="center">%s</td> </tr> `); const cushion = tableTemplate.length; const bufferSize = this.execute('messenger::textlength', { cushion }); const parts = rows.sort((a, b) => a.name.localeCompare(b.name)); this.execute('messenger::buffered', { bufferSize, parts, createPartString: part => format( rowTemplate, part.name, part.level ), error: e => { this.emit('error', new Error( `${e.message} \ BufferSize ( ${bufferSize + cushion} ) \ Entry ( table:[ permgroups ] name:[ ${e.part.name} ] )` )); data.user.sendMessage('A message transmission error occurred.'); }, send: message => data.user.sendMessage(format(tableTemplate, message)) }); }; this.execute('permissions::groups', { done }); }, info: () => `<pre> USAGE: groups Lists all groups, and their levels. </pre>` }] }; const groupmod = { handle: 'permissions::groupmod', exec: function groupmod (data) { const db = this.execute('database::use'); const args = [data.name, data.level]; db.run('INSERT OR REPLACE INTO permgroups values(?, ?)', args, err => { if (err) this.emit('error', err); if (data.done) data.done(err); }); }, commands: [{ handle: 'groupmod', exec: function groupmod (data) { if (!data.message) return; const parsed = this.execute('parser::htmltotext', { html: data.message }); if (!parsed) return; const pieces = parsed.split(' '); let level = parseInt(pieces.pop(), 10); if (Number.isNaN(level)) return void data.user.sendMessage('Needs level.'); const name = pieces.join(' '); if (!name) return void data.user.sendMessage('Needs name.'); const conf = this.config.extensions.permissions; level = minmax(0, level, conf.maxlevel || 100); this.execute('permissions::groupmod', { name, level, done: error => data.user.sendMessage( error ? 'A database error occurred.' : `Group [ ${name} ] set. Using level [ ${level} ].` ) }); }, info: () => `<pre> USAGE: groupmod GROUP_NAME GROUP_LEVEL Modifies the permissions level of GROUP_NAME to the new value of GROUP_LEVEL. If GROUP_NAME does not exist, it will be created. </pre>` }] }; const groupdel = { handle: 'permissions::groupdel', exec: function groupdel (data) { const db = this.execute('database::use'); const args = [data.name]; const reply = (error, message) => { if (error) this.emit('error', error); if (data.done) data.done(error, message); }; db.run('DELETE FROM permusers WHERE permgroup = ?', args, err => { reply(err, !err && `Deleted all users with group [ ${data.name} ].`); }); db.run('DELETE FROM permcommands WHERE permgroup = ?', args, err => { reply(err, !err && `Deleted all commands with group [ ${data.name} ].`); }); db.run('DELETE FROM permgroups WHERE name = ?', args, err => { reply(err, !err && `Deleted group [ ${data.name} ].`); }); }, commands: [{ handle: 'groupdel', exec: function groupdelcmd (data) { if (!data.message) return; const name = this.execute('parser::htmltotext', { html: data.message }); if (!name) return; const done = (err, msg) => data.user.sendMessage( err ? 'A database error occurred.' : msg ); this.execute('permissions::groupdel', { name, done }); }, info: () => `<pre> USAGE: groupdel GROUP_NAME Deletes all users and commands belonging to GROUP_NAME before deleting GROUP_NAME. </pre>` }] }; const users = { handle: 'permissions::users', exec: function users (data) { const db = this.execute('database::use'); db.all('SELECT * FROM permusers', (err, rows) => { if (err) this.emit('error', err); if (data.done) data.done(err, rows); }); }, commands: [{ handle: 'users', exec: function userscmd (data) { const done = (error, rows) => { if (error || !rows.length) return data.user.sendMessage( error ? 'A database error occurred.' : 'No users.' ); const tableTemplate = (` <table cellpadding="4"> <tr> <th>User Name</th> <th>Group Name</th> </tr> %s </table> `); const rowTemplate = (` <tr> <td width="60%" align="center">%s</td> <td width="40%" align="center">%s</td> </tr> `); const cushion = tableTemplate.length; const bufferSize = this.execute('messenger::textlength', { cushion }); const parts = rows.sort((a, b) => a.name.localeCompare(b.name)); this.execute('messenger::buffered', { bufferSize, parts, createPartString: part => format( rowTemplate, part.name, part.permgroup ), error: e => { this.emit('error', new Error( `${e.message} \ BufferSize ( ${bufferSize + cushion} ) \ Entry ( table:[ permusers ] name:[ ${e.part.name} ] )` )); data.user.sendMessage('A message transmission error occurred.'); }, send: message => data.user.sendMessage(format(tableTemplate, message)) }); }; this.execute('permissions::users', { done }); }, info: () => `<pre> USAGE: users Lists all users, and their group. </pre>` }] }; const usermod = { handle: 'permissions::usermod', exec: function usermod (data) { const db = this.execute('database::use'); const args = [data.group]; const iferror = error => error && this.emit('error', error); db.get('SELECT 1 FROM permgroups WHERE name = ?', args, (gerr, res) => { iferror(gerr); if (gerr || !res) return ( data.done && data.done(gerr, !gerr && `The group [ ${data.group} ] does not exist.`) ); args.unshift(data.name); args.unshift(data.hash); db.run('INSERT OR REPLACE INTO permusers values(?, ?, ?)', args, err => { iferror(err); if (data.done) data.done(err, !err && `User [ ${data.name} ] set. Using group [ ${data.group} ].`); }); }); }, commands: [{ handle: 'usermod', exec: function usermod (data) { if (!data.message) return; const name = this.execute('parser::htmltotext', { html: data.message }); const user = this.client.userByName(name); if (!user) return void data.user.sendMessage('User not found.'); if (!user.hash) return void data.user.sendMessage('User has no certificate.'); let timer = null; const followup = (msg, usr) => { if (usr !== data.user) return; this.removeListener('message', followup); clearTimeout(timer); if (msg.startsWith(this.config.operator)) return usr.sendMessage('Unsafe operation sequence. Aborting [ usermod ].'); const group = this.execute('parser::htmltotext', { html: msg }); this.execute('permissions::usermod', { hash: user.hash, name, group, done: (error, status) => data.user.sendMessage( error ? 'A database error occurred.' : status ) }); }; this.on('message', followup); timer = setTimeout(() => this.removeListener('message', followup), 5000); data.user.sendMessage(`The contents of your next message will set the group for [ ${user.name} ]. You have five seconds.`); }, info: () => `<pre> USAGE: usermod USER_NAME Modifies the group USER_NAME belongs to. New value is taken from the next message. </pre>` }] }; const userdel = { handle: 'permissions::userdel', exec: function userdel (data) { const db = this.execute('database::use'); const args = [data.name]; db.run('DELETE FROM permusers WHERE name = ?', args, err => { if (err) this.emit('error', err); if (data.done) data.done(err); }); }, commands: [{ handle: 'userdel', exec: function userdelcmd (data) { if (!data.message) return; const name = this.execute('parser::htmltotext', { html: data.message }); if (!name) return void data.user.sendMessage('Name needed.'); this.execute('permissions::userdel', { name, done: error => data.user.sendMessage( error ? 'A database error occurred.' : `Removed user [ ${name} ].` ) }); }, info: () => `<pre> USAGE: userdel USER_NAME Removes a user identified by USER_NAME. </pre>` }] }; const commands = { handle: 'permissions::commands', exec: function commands (data) { const db = this.execute('database::use'); db.all('SELECT * FROM permcommands', (err, rows) => { if (err) this.emit('error', err); if (data.done) data.done(err, rows); }); }, commands: [{ handle: 'commands', exec: function commandscmd (data) { const done = (error, rows) => { if (error || !rows.length) return data.user.sendMessage( error ? 'A database error occurred.' : 'No commands.' ); const tableTemplate = (` <table cellpadding="4"> <tr> <th>Command</th> <th>Group Name</th> </tr> %s </table> `); const rowTemplate = (` <tr> <td width="60%" align="center">%s</td> <td width="40%" align="center">%s</td> </tr> `); const cushion = tableTemplate.length; const bufferSize = this.execute('messenger::textlength', { cushion }); const parts = ( rows .filter(row => this.commands.has(row.name)) .sort((a, b) => a.name.localeCompare(b.name)) ); this.execute('messenger::buffered', { bufferSize, parts, createPartString: part => format( rowTemplate, part.name, part.permgroup ), error: e => { this.emit('error', new Error( `${e.message} \ BufferSize ( ${bufferSize + cushion} ) \ Entry ( table:[ permcommands ] name:[ ${e.part.name} ] )` )); data.user.sendMessage('A message transmission error occurred.'); }, send: message => data.user.sendMessage(format(tableTemplate, message)) }); }; this.execute('permissions::commands', { done }); }, info: () => `<pre> USAGE: commands Lists all commands, and their group. </pre>` }] }; const commandmod = { handle: 'permissions::commandmod', hooks: ['permissions::unalias'], exec: function commandmod (data) { const db = this.execute('database::use'); const args = [data.group]; const iferror = error => error && this.emit('error', error); db.get('SELECT 1 FROM permgroups WHERE name = ?', args, (gerr, res) => { iferror(gerr); if (gerr || !res) return ( data.done && data.done(gerr, !gerr && `The group [ ${data.group} ] does not exist.`) ); args.unshift(data.handle); db.run('INSERT OR REPLACE INTO permcommands values(?, ?)', args, err => { iferror(err); if (data.done) data.done(err, !err && `Command [ ${data.handle} ] set. Using group [ ${data.group} ].`); }); }); }, commands: [{ handle: 'commandmod', exec: function commandmodcmd (data) { if (!data.message) return; const parsed = this.execute('parser::htmltotext', { html: data.message }); if (!parsed) return; const pieces = parsed.split(' '); const handle = pieces.shift(); if (!(this.commands.has(handle) || this.aliases.has(handle))) return void data.user.sendMessage(`Command [ ${handle} ] not found.`); const group = pieces.join(' '); if (!group) return void data.user.sendMessage('Needs group.'); const done = (error, status) => data.user.sendMessage( error ? 'A database error occurred.' : status ); this.execute('permissions::commandmod', { notifyOfAlias: data.user, handle, group, done }); }, info: () => `<pre> USAGE: commandmod COMMAND_HANDLE GROUP_NAME Modifies the group COMMAND_HANDLE belongs to, using GROUP_NAME. </pre>` }] }; const commanddel = { handle: 'permissions::commanddel', hooks: ['permissions::unalias'], exec: function commanddel (data) { const db = this.execute('database::use'); const args = [data.handle]; db.run('DELETE FROM permcommands WHERE name = ?', args, err => { if (err) this.emit('error', err); if (data.done) data.done(err, !err && data.handle); }); }, commands: [{ handle: 'commanddel', exec: function commanddelcmd (data) { if (!data.message) return; const handle = this.execute('parser::htmltotext', { html: data.message }); if (!handle) return void data.user.sendMessage('Name needed.'); this.execute('permissions::commanddel', { notifyOfAlias: data.user, handle, done: (error, removed) => data.user.sendMessage( error ? 'A database error occurred.' : `Removed command [ ${removed} ].` ) }); }, info: () => `<pre> USAGE: commanddel COMMAND_HANDLE Removes a command identified by COMMAND_HANDLE. </pre>` }] }; const invokingerror = (stumble, error) => { console.warn('Permissions extension snagged on a database error during invocation.'); stumble.emit('error', error); }; const invoke = { handle: 'permissions::invoke', hooks: ['permissions::unalias'], exec: function invoke (data) { const conf = this.config.extensions.permissions; if (conf.hasOwnProperty('superuser') && conf.superuser === data.user.name) { if (this.space.get('permissions.warnsuperuser')) { const wtime = defaults(conf.warningtime, 60000); data.user.sendMessage(`<pre> Warning: You are acting as the super user. You can currently invoke any command. Try not to break everything. This warning will be suppressed for ${(wtime / 1000) >> 0} seconds. </pre>`); this.space.set('permissions.warnsuperuser', false); setTimeout(() => this.space.set('permissions.warnsuperuser', true), wtime); } return void this.invoke(data.handle, data); } if (!data.user.hash) return void data.user.sendMessage(` Permission denied. Permissions system is in place. You are not certified, and therefore can not use commands. Seek your local administrator for help. `); const db = this.execute('database::use'); const args = [data.handle]; db.get('SELECT * FROM permcommands WHERE name = ?', args, (cerr, crow) => { if (cerr) return invokingerror(this, cerr); if (!crow) return this.invoke(data.handle, data); db.get('SELECT * FROM permusers WHERE name = ? AND hash = ?', [data.user.name, data.user.hash], (uerr, urow) => { if (uerr) return invokingerror(this, uerr); if (!urow) return data.user.sendMessage(` Permission denied. Could not find you in the permissions system. `); db.get(` SELECT 1 FROM permgroups WHERE name = (?) AND level >= ( SELECT level FROM permgroups WHERE name = (?) ) `, [urow.permgroup, crow.permgroup], (ferr, res) => { if (ferr) return invokingerror(this, ferr); if (res) this.invoke(data.handle, data); else data.user.sendMessage(` Permission denied. You do not have the adequate group level to use [ ${data.handle} ]. `); }); }); }); } }; module.exports = { handle: 'permissions', needs: ['database', 'messenger', 'parser'], init: stumble => { stumble.space.set('_STANDARD_PERMISSIONS_', true); stumble.space.set('permissions.warnsuperuser', true); const db = stumble.execute('database::use'); db.run(` CREATE TABLE IF NOT EXISTS permgroups( name TEXT UNIQUE, level INTEGER ) `); db.run(` CREATE TABLE IF NOT EXISTS permusers( hash TEXT UNIQUE, name TEXT UNIQUE, permgroup TEXT ) `); db.run(` CREATE TABLE IF NOT EXISTS permcommands( name TEXT UNIQUE, permgroup TEXT ) `); }, term: stumble => { stumble.space.delete('_STANDARD_PERMISSIONS_'); stumble.space.delete('permissions.warnsuperuser'); }, extensions: [ unalias, invoke, groups, groupmod, groupdel, users, usermod, userdel, commands, commandmod, commanddel ] };