channel ownership base, part 1

Fleshing out the new channel ownership feature, along with a typo fix and small syntax updates.

A claim can now be staked with /claimchannel, a channel owner may change a trip's level using /setlevel

To do: unclaimchannel, setmotd, makeprivate, makepublic, renewclaim, garbage keeping, update mod commands to accept channelOwner and channelModerator, etc
This commit is contained in:
marzavec
2024-01-05 00:30:28 -08:00
parent a3bca6d000
commit 7ecb31c46c
117 changed files with 811 additions and 258 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+1 -1
View File
@@ -16,7 +16,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+1 -1
View File
@@ -13,7 +13,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+1 -1
View File
@@ -15,7 +15,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+1 -1
View File
@@ -13,7 +13,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+1 -1
View File
@@ -12,7 +12,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+187
View File
@@ -0,0 +1,187 @@
/**
* @author Marzavec
* @summary Take channel ownership
* @version 1.0.0
* @description Claim an unowned channel, enabling user management options
* @module claimchannel
*/
import captcha from 'ascii-captcha';
import {
isModerator,
getUserDetails,
levels,
} from '../utility/_UAC.js';
import {
// Errors,
ClaimExpirationDays,
} from '../utility/_Constants.js';
import {
getChannelSettings,
updateChannelSettings,
} from '../utility/_Channels.js';
/**
* Executes when invoked by a remote client
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
export async function run({
core, server, socket, payload,
}) {
// must be in a channel to run this command
if (typeof socket.channel === 'undefined') {
return server.police.frisk(socket, 10);
}
if (!socket.trip) {
return server.reply({
cmd: 'warn', // @todo Add numeric error code as `id`
text: 'Failed to take ownership: Invalid trip. Refer to `/help claimchannel` for instructions on how to use this command.',
channel: socket.channel, // @todo Multichannel
}, socket);
}
if (isModerator(socket.level)) {
return server.reply({
cmd: 'info', // @todo Add numeric error code as `id`
text: "You're already a global moderator; it's free real estate. . .",
channel: socket.channel, // @todo Multichannel
}, socket);
}
const channelSettings = getChannelSettings(core.appConfig.data, socket.channel);
if (channelSettings.owned) {
return server.reply({
cmd: 'warn', // @todo Add numeric error code as `id`
text: `This channel is already owned by the trip "${channelSettings.ownerTrip}", until ${channelSettings.claimExpires}`,
channel: socket.channel, // @todo Multichannel
}, socket);
}
socket.claimCaptcha = {
solution: captcha.generateRandomText(7),
};
server.reply({
cmd: 'warn',
text: 'Enter the following to take ownership (case-sensitive):',
channel: payload.channel, // @todo Multichannel
}, socket);
server.reply({
cmd: 'captcha',
text: captcha.word2Transformedstr(socket.claimCaptcha.solution),
channel: payload.channel, // @todo Multichannel
}, socket);
return true;
}
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
export function initHooks(server) {
server.registerHook('in', 'chat', this.chatHook.bind(this), 26);
}
/**
* Executes every time an incoming chat command is invoked
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {{Object|boolean|string}} Object = same/new payload, false = suppress, string = error
*/
export function chatHook({
core, server, socket, payload,
}) {
if (typeof payload.text !== 'string') {
return false;
}
if (typeof socket.claimCaptcha !== 'undefined') {
if (payload.text === socket.claimCaptcha.solution) {
socket.claimCaptcha = undefined;
const channelSettings = getChannelSettings(core.appConfig.data, socket.channel);
if (channelSettings.owned) {
return server.reply({
cmd: 'warn', // @todo Add numeric error code as `id`
text: `This channel is already owned by the trip "${channelSettings.ownerTrip}", until ${channelSettings.claimExpires}`,
channel: socket.channel, // @todo Multichannel
}, socket);
}
const expirationDate = new Date();
expirationDate.setDate(expirationDate.getDate() + ClaimExpirationDays);
channelSettings.claimExpires = expirationDate;
channelSettings.owned = true;
channelSettings.ownerTrip = socket.trip;
updateChannelSettings(core.appConfig.data, socket.channel, channelSettings);
server.broadcast({
cmd: 'info',
text: `Channel now owned by "${socket.trip}", until ${channelSettings.claimExpires}`,
channel: socket.channel,
}, { channel: socket.channel });
socket.level = levels.channelOwner;
const updateNotice = {
...getUserDetails(socket),
...{
cmd: 'updateUser',
channel: socket.channel,
},
};
server.broadcast(updateNotice, { channel: socket.channel });
return false;
}
server.police.frisk(socket, 7);
socket.terminate();
return false;
}
if (payload.text.startsWith('/claimchannel')) {
this.run({
core,
server,
socket,
payload: {
cmd: 'claimchannel',
},
});
return false;
}
return payload;
}
/**
* Module meta information
* @public
* @typedef {Object} claimchannel/info
* @property {string} name - Module command name
* @property {string} category - Module category name
* @property {string} description - Information about module
* @property {string} usage - Information about module usage
*/
export const info = {
name: 'claimchannel',
category: 'channels',
description: 'Claim an unowned channel, enabling user management options. You must have a trip code to run this command.',
usage: `
API: { cmd: 'claimchannel' }
Text: /claimchannel`,
};
+203
View File
@@ -0,0 +1,203 @@
/**
* @author Marzavec ( https://github.com/marzavec )
* @summary Change user level
* @version 1.0.0
* @description Alter the permission level a trip is allowed within current channel
* @module setlevel
*/
import {
levels,
isChannelModerator,
getUserDetails,
} from '../utility/_UAC.js';
import {
setChannelTripLevel,
} from '../utility/_Channels.js';
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core environment object
* @public
* @return {void}
*/
export async function init(core) {
core.levelLabels = Object.keys(levels);
}
/**
* Executes when invoked by a remote client
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
export async function run({
core, server, socket, payload,
}) {
// increase rate limit chance and ignore if not channel mod or better
if (!isChannelModerator(socket.level)) {
return server.police.frisk(socket, 10);
}
if (typeof payload.trip !== 'string' || payload.trip.length !== 6) {
return server.reply({
cmd: 'warn', // @todo Add numeric error code as `id`
text: 'Failed to set level: Invalid trip. Refer to `/help setlevel` for instructions on how to use this command.',
channel: socket.channel, // @todo Multichannel
}, socket);
}
if (typeof payload.level !== 'string') {
return server.reply({
cmd: 'warn', // @todo Add numeric error code as `id`
text: 'Failed to set level: Invalid level label. Refer to `/help setlevel` for instructions on how to use this command.',
channel: socket.channel, // @todo Multichannel
}, socket);
}
if (core.levelLabels.indexOf(payload.level) === -1) {
return server.reply({
cmd: 'warn', // @todo Add numeric error code as `id`
text: `Failed to set level: Invalid level label; choices are case sensitive: ${core.levelLabels.join(', ')}`,
channel: socket.channel, // @todo Multichannel
}, socket);
}
const newLevel = levels[payload.level];
if (newLevel >= socket.level) {
return server.reply({
cmd: 'warn', // @todo Add numeric error code as `id`
text: 'Failed to set level: New level may not be the same or greater than your own.',
channel: socket.channel, // @todo Multichannel
}, socket);
}
const setError = setChannelTripLevel(core.appConfig.data, socket.channel, payload.trip, newLevel);
if (setError !== '') {
return server.reply({
cmd: 'warn', // @todo Add numeric error code as `id`
text: `Failed to set level: ${setError}`,
channel: socket.channel, // @todo Multichannel
}, socket);
}
const targetClients = server.findSockets({
channel: socket.channel,
trip: payload.trip,
});
for (let i = 0, j = targetClients.length; i < j; i += 1) {
targetClients[i].level = newLevel;
server.broadcast({
...getUserDetails(targetClients[i]),
...{
cmd: 'updateUser',
channel: socket.channel,
},
}, { channel: socket.channel });
}
server.broadcast({
cmd: 'info', // @todo Add numeric error code as `id`
text: `Changed permission level of "${payload.trip}" to "${payload.level}"`,
channel: socket.channel, // @todo Multichannel
}, { channel: socket.channel });
return true;
}
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
export function initHooks(server) {
server.registerHook('in', 'chat', this.setlevelCheck.bind(this), 29);
}
/**
* Executes every time an incoming chat command is invoked
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
* string = error
*/
export function setlevelCheck({
core, server, socket, payload,
}) {
if (typeof payload.text !== 'string') {
return false;
}
if (payload.text.startsWith('/setlevel')) {
const input = payload.text.split(' ');
// If there is no trip parameter
if (!input[1]) {
server.reply({
cmd: 'warn', // @todo Add numeric error code as `id`
text: 'Failed to set level: Invalid trip. Refer to `/help setlevel` for instructions on how to use this command.',
channel: socket.channel, // @todo Multichannel
}, socket);
return false;
}
// If there is no level parameter
if (!input[2]) {
server.reply({
cmd: 'warn', // @todo Add numeric error code as `id`
text: 'Failed to set level: Invalid level label. Refer to `/help setlevel` for instructions on how to use this command.',
channel: socket.channel, // @todo Multichannel
}, socket);
return false;
}
this.run({
core,
server,
socket,
payload: {
cmd: 'setlevel',
trip: input[1],
level: input[2],
},
});
return false;
}
return payload;
}
/**
* The following payload properties are required to invoke this module:
* "trip", "level"
* @public
* @typedef {Array} setlevel/requiredData
*/
export const requiredData = ['trip', 'level'];
/**
* Module meta information
* @public
* @typedef {Object} setlevel/info
* @property {string} name - Module command name
* @property {string} category - Module category name
* @property {string} description - Information about module
* @property {string} usage - Information about module usage
*/
export const info = {
name: 'setlevel',
category: 'channels',
description: 'Alter the permission level a trip is allowed within current channel',
usage: `
API: { cmd: 'setlevel', trip: '[target trip]', level: '[level label]' }
Text: /setlevel <trip> <level label>`,
};
+3 -3
View File
@@ -21,7 +21,7 @@ const verifyColor = (color) => /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(color);
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -77,7 +77,7 @@ export async function run({
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
@@ -87,7 +87,7 @@ export function initHooks(server) {
/**
* Executes every time an incoming chat command is invoked
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
+3 -3
View File
@@ -15,7 +15,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -131,7 +131,7 @@ export async function run({
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
@@ -141,7 +141,7 @@ export function initHooks(server) {
/**
* Executes every time an incoming chat command is invoked
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
+4 -4
View File
@@ -76,7 +76,7 @@ export function addActiveMessage(customId, userid) {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -147,7 +147,7 @@ export async function run({
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
@@ -159,7 +159,7 @@ export function initHooks(server) {
/**
* Executes every time an incoming chat command is invoked;
* checks for miscellaneous '/' based commands
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
@@ -186,7 +186,7 @@ export function commandCheckIn({ server, socket, payload }) {
/**
* Executes every time an incoming chat command is invoked;
* assumes a failed chat command invocation and will reject with notice
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
+3 -3
View File
@@ -31,7 +31,7 @@ const parseText = (text) => {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -78,7 +78,7 @@ export async function run({ server, socket, payload }) {
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
@@ -89,7 +89,7 @@ export function initHooks(server) {
/**
* Executes every time an incoming chat command is invoked;
* hooks chat commands checking for /me
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
+8 -3
View File
@@ -6,9 +6,13 @@
* @module help
*/
import {
CodebaseVersion,
} from '../utility/_Constants.js';
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -43,6 +47,7 @@ export async function run({
}
reply += '---\nFor specific help on certain commands, use either:\nText: `/help <command name>`\nAPI: `{cmd: \'help\', command: \'<command name>\'}`';
reply += `\n\nHackChat ${CodebaseVersion}`;
} else {
const command = core.commands.get(payload.command);
@@ -73,7 +78,7 @@ export async function run({
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
@@ -84,7 +89,7 @@ export function initHooks(server) {
/**
* Executes every time an incoming chat command is invoked;
* hooks chat commands checking for /help
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
+1 -1
View File
@@ -32,7 +32,7 @@ export function getChannel(channel = undefined) {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+1 -1
View File
@@ -32,7 +32,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+3 -3
View File
@@ -29,7 +29,7 @@ const formatTime = (time) => {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -105,7 +105,7 @@ server-uptime: ${uptime}`,
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
@@ -116,7 +116,7 @@ export function initHooks(server) {
/**
* Executes every time an incoming chat command is invoked;
* hooks chat commands checking for /stats
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
+6 -5
View File
@@ -8,9 +8,10 @@
* @module session
*/
import fs from 'fs';
import {
readFileSync,
} from 'node:fs';
import jsonwebtoken from 'jsonwebtoken';
import {
isModerator,
verifyNickname,
@@ -66,7 +67,7 @@ function notifyFailure(server, socket) {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -171,14 +172,14 @@ export async function run({
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core enviroment object
* @param {Object} core - Reference to core environment object
* @public
* @return {void}
*/
export function init(core) {
// load the encryption key if required
if (typeof core.sessionKey === 'undefined') {
core.sessionKey = fs.readFileSync(SessionLocation);
core.sessionKey = readFileSync(SessionLocation);
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+1 -1
View File
@@ -20,7 +20,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+3 -3
View File
@@ -44,7 +44,7 @@ const parseText = (text) => {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -111,7 +111,7 @@ export async function run({ server, socket, payload }) {
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
@@ -122,7 +122,7 @@ export function initHooks(server) {
/**
* Executes every time an incoming chat command is invoked;
* hooks chat commands checking for /whisper
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
+1 -1
View File
@@ -12,7 +12,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+1 -1
View File
@@ -9,7 +9,7 @@
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+2 -3
View File
@@ -9,7 +9,6 @@
import {
isModerator,
getUserDetails,
levels,
} from '../utility/_UAC.js';
import {
Errors,
@@ -20,7 +19,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -76,7 +75,7 @@ export async function run({
text: `Banned ${targetNick}`,
user: getUserDetails(targetUser),
channel: socket.channel, // @todo Multichannel
}, { channel: socket.channel, level: (level) => level < levels.moderator });
}, { channel: socket.channel, level: (level) => isModerator(level) });
// notify mods
server.broadcast({
+2 -2
View File
@@ -13,7 +13,7 @@ import {
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core enviroment object
* @param {Object} core - Reference to core environment object
* @public
* @return {void}
*/
@@ -25,7 +25,7 @@ export async function init(core) {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+6 -6
View File
@@ -61,7 +61,7 @@ const parseText = (text) => {
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core enviroment object
* @param {Object} core - Reference to core environment object
* @public
* @return {void}
*/
@@ -73,7 +73,7 @@ export function init(core) {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -140,7 +140,7 @@ export async function run({
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
@@ -153,7 +153,7 @@ export function initHooks(server) {
/**
* Executes every time an incoming chat command is invoked;
* hook incoming chat commands, shadow-prevent chat if they are muzzled
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
@@ -216,7 +216,7 @@ export function chatCheck({
/**
* Executes every time an incoming chat command is invoked;
* shadow-prevent all invites from muzzled users
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
@@ -288,7 +288,7 @@ export function inviteCheck({
/**
* Executes every time an incoming chat command is invoked;
* shadow-prevent all whispers from muzzled users
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
+5 -5
View File
@@ -30,7 +30,7 @@ import {
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core enviroment object
* @param {Object} core - Reference to core environment object
* @public
* @return {void}
*/
@@ -42,7 +42,7 @@ export async function init(core) {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -87,7 +87,7 @@ export async function run({
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
@@ -99,7 +99,7 @@ export function initHooks(server) {
/**
* Executes every time an incoming chat command is invoked;
* hook incoming chat commands, check if they are answering a captcha
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
@@ -154,7 +154,7 @@ export function chatCheck({
/**
* Executes every time an incoming join command is invoked;
* hook incoming join commands, check if they are joining a captcha protected channel
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
+3 -3
View File
@@ -28,7 +28,7 @@ const verifyColor = (color) => /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(color);
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -105,7 +105,7 @@ export async function run({
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
@@ -116,7 +116,7 @@ export function initHooks(server) {
/**
* Executes every time an incoming chat command is invoked;
* hooks chat commands checking for /forcecolor
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
+2 -3
View File
@@ -8,7 +8,6 @@
import {
isModerator,
levels,
getUserDetails,
} from '../utility/_UAC.js';
import {
@@ -20,7 +19,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -126,7 +125,7 @@ export async function run({
cmd: 'info',
text: `Kicked ${kicked.map((k) => k.nick).join(', ')}`,
channel: socket.channel, // @todo Multichannel
}, { channel: socket.channel, level: (level) => level < levels.moderator });
}, { channel: socket.channel, level: (level) => isModerator(level) });
// stats are fun
core.stats.increment('users-kicked', kicked.length);
+8 -8
View File
@@ -50,7 +50,7 @@ const danteQuotes = [
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core enviroment object
* @param {Object} core - Reference to core environment object
* @public
* @return {void}
*/
@@ -62,7 +62,7 @@ export async function init(core) {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
@@ -109,7 +109,7 @@ export async function run({
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @param {Object} server - Reference to server environment object
* @public
* @return {void}
*/
@@ -124,7 +124,7 @@ export function initHooks(server) {
/**
* Executes every time an incoming changenick command is invoked;
* hook incoming changenick commands, reject them if the channel is 'purgatory'
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
@@ -143,7 +143,7 @@ export function changeNickCheck({
/**
* Executes every time an incoming whisper command is invoked;
* hook incoming whisper commands, reject them if the channel is 'purgatory'
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
@@ -162,7 +162,7 @@ export function whisperCheck({
/**
* Executes every time an incoming chat command is invoked;
* hook incoming chat commands, reject them if the channel is 'purgatory'
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
@@ -185,7 +185,7 @@ export function chatCheck({
/**
* Executes every time an incoming invite command is invoked;
* hook incoming invite commands, reject them if the channel is 'purgatory'
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
@@ -204,7 +204,7 @@ export function inviteCheck({
/**
* Executes every time an incoming join command is invoked;
* hook incoming join commands, shunt them to purgatory if needed
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {(Object|boolean|string)} Object = same/altered payload,
* false = suppress action,
+2 -2
View File
@@ -14,7 +14,7 @@ import {
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core enviroment object
* @param {Object} core - Reference to core environment object
* @public
* @return {void}
*/
@@ -26,7 +26,7 @@ export function init(core) {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+1 -1
View File
@@ -12,7 +12,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+1 -1
View File
@@ -12,7 +12,7 @@ import {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+2 -2
View File
@@ -14,7 +14,7 @@ import {
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core enviroment object
* @param {Object} core - Reference to core environment object
* @public
* @return {void}
*/
@@ -26,7 +26,7 @@ export async function init(core) {
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @param {Object} env - Environment object with references to core, server, socket & payload
* @public
* @return {void}
*/
+125 -6
View File
@@ -1,3 +1,5 @@
/* eslint import/no-cycle: [0, { ignoreExternal: true }] */
/**
* @author Marzavec ( https://github.com/marzavec )
* @summary Channel helper
@@ -6,8 +8,21 @@
* @module Channels
*/
import {
existsSync,
readFileSync,
writeFile,
} from 'node:fs';
import {
createHash,
} from 'node:crypto';
import {
levels,
} from './_UAC.js';
import {
Errors,
DefaultChannelSettings,
MaxChannelTrips,
} from './_Constants.js';
/**
@@ -28,6 +43,55 @@ export function canJoinChannel(channel, socket) {
return true;
}
/**
* Returns the target channel's hash
* @public
* @param {string} channel Target channel
* @return {string}
*/
export function getChannelHash(channel) {
return createHash('sha256').update(channel, 'utf8').digest('hex');
}
/**
* Caches the target channel settings to storage
* @public
* @param {string} config Server config object
* @param {string} channel Target channel
* @return {boolean}
*/
export function storeChannelSettings(config, channel) {
const channelHash = getChannelHash(channel);
const configPath = `../../channels/${channelHash[0]}/${channelHash}.json`;
delete config.permissions[channelHash].channelHash;
writeFile(configPath, JSON.stringify(config.permissions[channelHash] || DefaultChannelSettings));
return true;
}
/**
* Applies new settings into the specified channel settings
* @public
* @param {string} config Server config object
* @param {string} channel Target channel
* @param {string} newSettings Updated channel settings
* @return {object}
*/
export function updateChannelSettings(config, channel, newSettings) {
const channelHash = getChannelHash(channel);
const updatedSettings = {
...newSettings,
...config.permissions[channelHash],
};
config.permissions[channelHash] = updatedSettings;
config.permissions[channelHash].lastAccessed = new Date();
return updatedSettings;
}
/**
* Returns an object containing info about the specified channel,
* including if it is owned, mods, permissions
@@ -37,15 +101,70 @@ export function canJoinChannel(channel, socket) {
* @return {object}
*/
export function getChannelSettings(config, channel) {
if (typeof config.permissions !== 'undefined') {
if (typeof config.permissions[channel] !== 'undefined') {
return config.permissions[channel];
const channelHash = getChannelHash(channel);
if (typeof config.permissions[channelHash] === 'undefined') {
const configPath = `../../channels/${channelHash[0]}/${channelHash}.json`;
if (!existsSync(configPath)) {
return DefaultChannelSettings;
}
let configData;
try {
configData = JSON.parse(readFileSync(configPath, 'utf8'));
} catch (e) {
console.log(`Corrupted channel config: ${configPath}`);
return DefaultChannelSettings;
}
// Check last access date here, if too old; delete file and return DefaultChannelSettings
config.permissions[channelHash] = configData;
}
return {
owned: false,
};
config.permissions[channelHash].lastAccessed = new Date();
config.permissions[channelHash].channelHash = channelHash;
return config.permissions[channelHash];
}
/**
* Apply a new permission level to the provided trip, within the provided channel
* @public
* @param {string} config Server config object
* @param {string} channel Target channel name
* @param {string} trip Target trip
* @param {number} level New level
* @return {(string)}
*/
export function setChannelTripLevel(config, channel, trip, level) {
const channelSettings = getChannelSettings(config, channel);
if (!channelSettings.owned) {
return 'This channel has no owner.';
}
const currentTrips = Object.keys(config.permissions[channelSettings.channelHash].tripLevels);
if (currentTrips.length >= MaxChannelTrips) {
if (level !== levels.default) {
return 'Too many trips used. Remove trips by setting their level to default level.';
}
if (currentTrips.indexOf(trip) === -1) {
return 'Invalid trip';
}
delete config.permissions[channelSettings.channelHash].tripLevels[trip];
return '';
}
config.permissions[channelSettings.channelHash].tripLevels[trip] = level;
return '';
}
/**
+34 -2
View File
@@ -1,11 +1,17 @@
/**
* @author Marzavec ( https://github.com/marzavec )
* @summary Error ID list
* @summary App settings
* @version 1.0.0
* @description Exports an object that hold common global error IDs
* @description Exports an object that hold common constants
* @module Constants
*/
/**
* Internal version, used mainly for debugging
* @typedef {object} CodebaseVersion
*/
export const CodebaseVersion = '2.2.21b';
/* Base error ranges */
const GlobalErrors = 10;
const JoinErrors = 20;
@@ -46,4 +52,30 @@ export const Errors = {
},
};
/**
* The settings structure of a default, unowned channel
* @typedef {object} DefaultChannelSettings
*/
export const DefaultChannelSettings = {
owned: false,
ownerTrip: '',
lastAccessed: new Date(),
claimExpires: new Date(),
motd: '',
lockLevel: 0,
tripLevels: {},
};
/**
* Maximum number of specialized trip levels, per channel
* @typedef {number} MaxChannelTrips
*/
export const MaxChannelTrips = 250;
/**
* How many days until a claim will expire
* @typedef {number} ClaimExpirationDays
*/
export const ClaimExpirationDays = 7;
export default Errors;
+11 -18
View File
@@ -1,3 +1,5 @@
/* eslint import/no-cycle: [0, { ignoreExternal: true }] */
/**
* User Account Control information containing level constants
* and simple helper functions related to users
@@ -7,14 +9,13 @@
* @module UAC
*/
import {
createHash,
} from 'node:crypto';
import {
getChannelSettings,
} from './_Channels.js';
const {
createHash,
} = await import('crypto');
/**
* Object defining labels for default permission ranges
* @typedef {Object} levels
@@ -148,9 +149,7 @@ export function getUserPerms(pass, salt, config, channel) {
};
}
const sha = createHash('sha256');
sha.update(pass + salt);
const trip = sha.digest('base64').substr(0, 6);
const trip = createHash('sha256').update(pass + salt, 'utf8').digest('base64').substr(0, 6);
// check if user is global admin
if (trip === config.adminTrip) {
@@ -171,19 +170,13 @@ export function getUserPerms(pass, salt, config, channel) {
const channelSettings = getChannelSettings(config, channel);
if (channelSettings.owned) {
// check if user is channel owner
// @todo channel ownership patch
// check if user is channel mod
// @todo channel ownership patch
// check if user is channel trusted
// @todo channel ownership patch
if (channelSettings.ownerTrip === trip) {
level = levels.channelOwner;
} else if (typeof channelSettings.tripLevels[trip] !== 'undefined') {
level = channelSettings.tripLevels[trip];
}
}
// check if user is global trusted
// @todo channel ownership patch
return {
trip,
level,