mirror of
https://github.com/hack-chat/main.git
synced 2024-03-22 13:20:33 +08:00
Removed legacylayer, baked into modules instead
Originally I wanted the legacy layer to be a seperate module, allowing a server owner to remove legacy support by removing the module (for preformance). However, I didn't like the 39 thousand hooks that would be required and what this would do for latency. I dont like this alternative either though. /shrug Added channel helper functions. Added constants for warnings and errors. Started updating warning to have an id for i18l. Added legacy helper functions Moved the UAC module into the utility directory and renamed to _UAC.
This commit is contained in:
parent
72324050f5
commit
5eb695bc53
|
@ -14,7 +14,7 @@ export async function run({ core, server, socket }) {
|
|||
// attempt save, notify of failure
|
||||
if (!core.configManager.save()) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Failed to save config, check logs.',
|
||||
}, socket);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ export async function run({
|
|||
}) {
|
||||
if (server.police.frisk(socket.address, 6)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are changing nicknames too fast. Wait a moment before trying again.',
|
||||
}, socket);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export async function run({
|
|||
const newNick = payload.nick.trim();
|
||||
if (!UAC.verifyNickname(newNick)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
|
||||
}, socket);
|
||||
}
|
||||
|
@ -39,14 +39,14 @@ export async function run({
|
|||
server.police.frisk(socket.address, 4);
|
||||
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are not the admin, liar!',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
if (newNick == previousNick) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You already have that name',
|
||||
}, socket);
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ export async function run({
|
|||
if (userExists.length > 0) {
|
||||
// That nickname is already in that channel
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Nickname taken',
|
||||
}, socket);
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ export function nickCheck({
|
|||
// If there is no nickname target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Refer to `/help nick` for instructions on how to use this command.',
|
||||
}, socket);
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ export async function run({
|
|||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket.address, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
}, socket);
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ export async function run({
|
|||
const outgoingPayload = {
|
||||
cmd: 'chat',
|
||||
nick: socket.nick, /* @legacy */
|
||||
uType: socket.uType, /* @legacy */
|
||||
userid: socket.userid,
|
||||
channel: socket.channel,
|
||||
text,
|
||||
|
@ -111,7 +112,7 @@ export function finalCmdCheck({ server, socket, payload }) {
|
|||
}
|
||||
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: `Unknown command: ${payload.text}`,
|
||||
}, socket);
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ export async function run({ server, socket, payload }) {
|
|||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket.address, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
}, socket);
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ export function emoteCheck({
|
|||
// If there is no emote target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Refer to `/help emote` for instructions on how to use this command.',
|
||||
}, socket);
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ export async function run({
|
|||
// check for spam
|
||||
if (server.police.frisk(socket.address, 2)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
}, socket);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,17 @@
|
|||
Description: Generates a semi-unique channel name then broadcasts it to each client
|
||||
*/
|
||||
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants';
|
||||
import {
|
||||
legacyInviteOut,
|
||||
legacyInviteReply,
|
||||
} from '../utility/_LegacyFunctions';
|
||||
|
||||
// module support functions
|
||||
/**
|
||||
* Returns the channel that should be invited to.
|
||||
|
@ -22,34 +33,35 @@ export async function run({
|
|||
// check for spam
|
||||
if (server.police.frisk(socket.address, 2)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn',
|
||||
text: 'You are sending invites too fast. Wait a moment before trying again.',
|
||||
id: Errors.Invite.RATELIMIT,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user input
|
||||
if (typeof payload.userid !== 'number' || typeof payload.channel !== 'string') {
|
||||
// if this is a legacy client add missing params to payload
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof socket.channel === 'undefined' || typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// why would you invite yourself?
|
||||
if (payload.userid === socket.userid) {
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number' || typeof payload.channel !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// @todo Verify this socket is part of payload.channel - multichannel patch
|
||||
// find target user
|
||||
let targetClient = server.findSockets({ channel: payload.channel, userid: payload.userid });
|
||||
|
||||
if (targetClient.length === 0) {
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
[targetClient] = targetClient;
|
||||
|
||||
// generate common channel
|
||||
const channel = getChannel(payload.to);
|
||||
|
||||
|
@ -58,15 +70,23 @@ export async function run({
|
|||
cmd: 'invite',
|
||||
channel: socket.channel,
|
||||
from: socket.userid,
|
||||
to: payload.userid,
|
||||
to: targetUser.userid,
|
||||
inviteChannel: channel,
|
||||
};
|
||||
|
||||
// send invite notice to target client
|
||||
server.reply(outgoingPayload, targetClient);
|
||||
if (targetUser.hcProtocol === 1) {
|
||||
server.reply(legacyInviteOut(outgoingPayload, socket.nick), targetUser);
|
||||
} else {
|
||||
server.reply(outgoingPayload, targetUser);
|
||||
}
|
||||
|
||||
// send invite notice to this client
|
||||
if (socket.hcProtocol === 1) {
|
||||
server.reply(legacyInviteReply(outgoingPayload, targetUser.nick), socket);
|
||||
} else {
|
||||
server.reply(outgoingPayload, socket);
|
||||
}
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('invites-sent');
|
||||
|
|
|
@ -1,139 +1,118 @@
|
|||
/* eslint no-param-reassign: 0 */
|
||||
|
||||
/*
|
||||
Description: Initial entry point, applies `channel` and `nick` to the calling socket
|
||||
Description: Adds requested channel into the calling clients "subscribed channels"
|
||||
*/
|
||||
|
||||
import * as UAC from '../utility/UAC/_info';
|
||||
|
||||
// module support functions
|
||||
const crypto = require('crypto');
|
||||
|
||||
const hash = (password) => {
|
||||
const sha = crypto.createHash('sha256');
|
||||
sha.update(password);
|
||||
return sha.digest('base64').substr(0, 6);
|
||||
};
|
||||
|
||||
// exposed "login" function to allow hooks to verify user join events
|
||||
// returns object containing user info or string if error
|
||||
export function parseNickname(core, data) {
|
||||
const userInfo = {
|
||||
nick: data.nick,
|
||||
uType: 'user', /* @legacy */
|
||||
trip: null,
|
||||
level: UAC.levels.default,
|
||||
};
|
||||
|
||||
if (!UAC.verifyNickname(userInfo.nick)) {
|
||||
// return error as string
|
||||
// @todo Remove english and change to numeric id
|
||||
return 'Nickname must consist of up to 24 letters, numbers, and underscores';
|
||||
}
|
||||
|
||||
const password = data.pass || false;
|
||||
|
||||
if (hash(password + core.config.tripSalt) === core.config.adminTrip) {
|
||||
userInfo.uType = 'admin'; /* @legacy */
|
||||
userInfo.trip = 'Admin';
|
||||
userInfo.level = UAC.levels.admin;
|
||||
} else if (userInfo.nick.toLowerCase() === core.config.adminName.toLowerCase()) {
|
||||
// they've got the main-admin name while not being an admin
|
||||
// @todo Remove english and change to numeric id
|
||||
return 'You are not the admin, liar!';
|
||||
} else if (password) {
|
||||
userInfo.trip = hash(password + core.config.tripSalt);
|
||||
}
|
||||
|
||||
// @todo disallow moderator impersonation
|
||||
// for (const mod of core.config.mods) {
|
||||
core.config.mods.forEach((mod) => {
|
||||
if (userInfo.trip === mod.trip) {
|
||||
userInfo.uType = 'mod'; /* @legacy */
|
||||
userInfo.level = UAC.levels.moderator;
|
||||
}
|
||||
});
|
||||
|
||||
return userInfo;
|
||||
}
|
||||
// import * as UAC from '../utility/UAC/_info';
|
||||
import {
|
||||
canJoinChannel,
|
||||
} from '../utility/_Channels';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants';
|
||||
import {
|
||||
upgradeLegacyJoin,
|
||||
legacyLevelToLabel,
|
||||
} from '../utility/_LegacyFunctions';
|
||||
import {
|
||||
verifyNickname,
|
||||
getUserPerms,
|
||||
} from '../utility/_UAC';
|
||||
|
||||
// module main
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check for spam
|
||||
}) { // check for spam
|
||||
if (server.police.frisk(socket.address, 3)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn',
|
||||
text: 'You are joining channels too fast. Wait a moment and try again.',
|
||||
id: Errors.Join.RATELIMIT,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// `join` is the legacy entry point, check if it needs to be upgraded
|
||||
if (typeof socket.hcProtocol === 'undefined') {
|
||||
payload = upgradeLegacyJoin(server, socket, payload);
|
||||
}
|
||||
|
||||
// store payload values
|
||||
const { channel, nick, pass } = payload;
|
||||
|
||||
// check if a client is able to join target channel
|
||||
const mayJoin = canJoinChannel(channel, socket);
|
||||
if (mayJoin !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You may not join that channel.',
|
||||
id: mayJoin,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// calling socket already in a channel
|
||||
// @todo Multichannel update
|
||||
// @todo multichannel update, will remove
|
||||
if (typeof socket.channel !== 'undefined') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove this
|
||||
text: 'Joining more than one channel is not currently supported',
|
||||
id: Errors.Join.ALREADY_JOINED,
|
||||
}, socket);
|
||||
}
|
||||
// end todo
|
||||
|
||||
// check user input
|
||||
if (typeof payload.channel !== 'string' || typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const channel = payload.channel.trim();
|
||||
if (!channel) {
|
||||
// must join a non-blank channel
|
||||
return true;
|
||||
}
|
||||
|
||||
const userInfo = this.parseNickname(core, payload);
|
||||
if (typeof userInfo === 'string') {
|
||||
// validates the user input for `nick`
|
||||
const validName = verifyNickname(nick, socket);
|
||||
if (validName !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
text: userInfo,
|
||||
cmd: 'warn',
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
|
||||
id: Errors.Join.INVALID_NICK,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// get trip and level
|
||||
const { trip, level } = getUserPerms(pass, core.config, channel);
|
||||
// store the user values
|
||||
const userInfo = {
|
||||
nick,
|
||||
trip,
|
||||
uType: legacyLevelToLabel(level),
|
||||
hash: socket.hash,
|
||||
level,
|
||||
userid: socket.userid,
|
||||
channel,
|
||||
};
|
||||
|
||||
// prevent admin impersonation
|
||||
if (nick.toLowerCase() === core.config.adminName.toLowerCase()) {
|
||||
if (userInfo.trip !== 'Admin') {
|
||||
userInfo.nick = `Fake${userInfo.nick}`;
|
||||
}
|
||||
}
|
||||
|
||||
// check if the nickname already exists in the channel
|
||||
const userExists = server.findSockets({
|
||||
channel: payload.channel,
|
||||
channel,
|
||||
nick: (targetNick) => targetNick.toLowerCase() === userInfo.nick.toLowerCase(),
|
||||
});
|
||||
|
||||
if (userExists.length > 0) {
|
||||
// that nickname is already in that channel
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn',
|
||||
text: 'Nickname taken',
|
||||
id: Errors.Join.NAME_TAKEN,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// populate final userinfo fields
|
||||
// @todo this could be move into parseNickname, changing the function name to match
|
||||
userInfo.hash = server.getSocketHash(socket);
|
||||
userInfo.userid = socket.userid;
|
||||
|
||||
// @todo place this within it's own function allowing import
|
||||
// prepare to notify channel peers
|
||||
const newPeerList = server.findSockets({ channel: payload.channel });
|
||||
const newPeerList = server.findSockets({ channel });
|
||||
const nicks = []; /* @legacy */
|
||||
const users = [];
|
||||
const joinAnnouncement = { ...{ cmd: 'onlineAdd' }, ...userInfo };
|
||||
|
||||
const joinAnnouncement = {
|
||||
cmd: 'onlineAdd',
|
||||
nick: userInfo.nick,
|
||||
trip: userInfo.trip || 'null',
|
||||
utype: userInfo.uType, /* @legacy */
|
||||
hash: userInfo.hash,
|
||||
level: userInfo.level,
|
||||
userid: userInfo.userid,
|
||||
channel: payload.channel,
|
||||
};
|
||||
|
||||
// send join announcement and prep online set
|
||||
// send join announcement and prep online set reply
|
||||
for (let i = 0, l = newPeerList.length; i < l; i += 1) {
|
||||
server.reply(joinAnnouncement, newPeerList[i]);
|
||||
nicks.push(newPeerList[i].nick); /* @legacy */
|
||||
|
@ -141,34 +120,26 @@ export async function run({
|
|||
users.push({
|
||||
nick: newPeerList[i].nick,
|
||||
trip: newPeerList[i].trip,
|
||||
utype: newPeerList[i].uType, /* @legacy */
|
||||
uType: newPeerList[i].uType, /* @legacy */
|
||||
hash: newPeerList[i].hash,
|
||||
level: newPeerList[i].level,
|
||||
userid: newPeerList[i].userid,
|
||||
channel: payload.channel,
|
||||
channel,
|
||||
isme: false,
|
||||
});
|
||||
}
|
||||
|
||||
// store user info
|
||||
socket.uType = userInfo.uType; /* @legacy */
|
||||
socket.nick = userInfo.nick;
|
||||
socket.trip = userInfo.trip;
|
||||
socket.channel = payload.channel; /* @legacy */
|
||||
socket.hash = userInfo.hash;
|
||||
socket.level = userInfo.level;
|
||||
socket.uType = userInfo.uType; /* @legacy */
|
||||
socket.channel = channel; /* @legacy */
|
||||
// @todo multi-channel patch
|
||||
// socket.channels.push(channel);
|
||||
|
||||
nicks.push(socket.nick); /* @legacy */
|
||||
users.push({
|
||||
nick: socket.nick,
|
||||
trip: socket.trip,
|
||||
utype: socket.uType,
|
||||
hash: socket.hash,
|
||||
level: socket.level,
|
||||
userid: socket.userid,
|
||||
channel: payload.channel,
|
||||
isme: true,
|
||||
});
|
||||
nicks.push(userInfo.nick); /* @legacy */
|
||||
users.push({ ...{ isme: true }, ...userInfo });
|
||||
|
||||
// reply with channel peer list
|
||||
server.reply({
|
||||
|
@ -186,7 +157,7 @@ export async function run({
|
|||
export const requiredData = []; // ['channel', 'nick'];
|
||||
export const info = {
|
||||
name: 'join',
|
||||
description: 'Place calling socket into target channel with target nick & broadcast event to channel',
|
||||
description: 'Join the target channel using the supplied nick and password',
|
||||
usage: `
|
||||
API: { cmd: 'join', nick: '<your nickname>', pass: '<optional password>', channel: '<target channel>' }`,
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ export async function run({ server, socket, payload }) {
|
|||
// check for spam
|
||||
if (server.police.frisk(socket.address, 6)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are changing channels too fast. Wait a moment before trying again.',
|
||||
}, socket);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ export async function run({ server, socket, payload }) {
|
|||
|
||||
if (payload.channel === '') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Cannot move to an empty channel.',
|
||||
}, socket);
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ export function moveCheck({
|
|||
// If there is no channel target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Refer to `/help move` for instructions on how to use this command.',
|
||||
}, socket);
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ export async function run({ server, socket }) {
|
|||
socket.sessionID = createSessionID();
|
||||
socket.hcProtocol = 2;
|
||||
socket.userid = Math.floor(Math.random() * 9999999999999);
|
||||
socket.hash = server.getSocketHash(socket);
|
||||
|
||||
// dispatch info
|
||||
server.reply({
|
||||
|
|
|
@ -38,7 +38,7 @@ export async function run({ server, socket, payload }) {
|
|||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket.address, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
}, socket);
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ export async function run({ server, socket, payload }) {
|
|||
|
||||
if (targetClient.length === 0) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Could not find user in channel',
|
||||
}, socket);
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ export function whisperCheck({
|
|||
// If there is no nickname target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Refer to `/help whisper` for instructions on how to use this command.',
|
||||
}, socket);
|
||||
|
||||
|
@ -126,7 +126,7 @@ export function whisperCheck({
|
|||
if (payload.text.startsWith('/r ')) {
|
||||
if (typeof socket.whisperReply === 'undefined') {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Cannot reply to nobody',
|
||||
}, socket);
|
||||
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
/* eslint no-param-reassign: 0 */
|
||||
|
||||
/*
|
||||
Description: This module adjusts outgoing data, making it compatible with legacy clients
|
||||
Dear god this module is horrifying
|
||||
*/
|
||||
|
||||
// import * as UAC from '../utility/UAC/_info';
|
||||
|
||||
// module main
|
||||
export async function run({ server, socket }) {
|
||||
return server.police.frisk(socket.address, 20);
|
||||
}
|
||||
|
||||
// module hook functions
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'join', this.joinCheck.bind(this), 10);
|
||||
server.registerHook('in', 'invite', this.inviteInCheck.bind(this), 10);
|
||||
server.registerHook('out', 'invite', this.inviteOutCheck.bind(this), 10);
|
||||
server.registerHook('in', 'ban', this.banCheck.bind(this), 10);
|
||||
server.registerHook('in', 'dumb', this.dumbCheck.bind(this), 10);
|
||||
server.registerHook('in', 'kick', this.kickCheck.bind(this), 10);
|
||||
}
|
||||
|
||||
// hook incoming join events, if session was not invoked, default proto to 1
|
||||
export function joinCheck({ socket, payload }) {
|
||||
if (typeof socket.hcProtocol === 'undefined') {
|
||||
socket.hcProtocol = 1;
|
||||
|
||||
const nickArray = payload.nick.split('#', 2);
|
||||
payload.nick = nickArray[0].trim();
|
||||
if (nickArray[1] && typeof payload.pass === 'undefined') {
|
||||
payload.pass = nickArray[1]; // eslint-disable-line prefer-destructuring
|
||||
}
|
||||
|
||||
// dunno how this happened on the legacy version
|
||||
if (typeof payload.password !== 'undefined') {
|
||||
payload.pass = payload.password;
|
||||
}
|
||||
|
||||
if (typeof socket.userid === 'undefined') {
|
||||
socket.userid = Math.floor(Math.random() * 9999999999999);
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
// if legacy client sent an invite, downgrade request
|
||||
export function inviteInCheck({ server, socket, payload }) {
|
||||
if (socket.hcProtocol === 1) {
|
||||
let targetClient = server.findSockets({ channel: socket.channel, nick: payload.nick });
|
||||
|
||||
if (targetClient.length === 0) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[targetClient] = targetClient;
|
||||
|
||||
payload.userid = targetClient.userid;
|
||||
payload.channel = socket.channel;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
//
|
||||
export function inviteOutCheck({ server, socket, payload }) {
|
||||
if (socket.hcProtocol === 1) {
|
||||
payload.cmd = 'info';
|
||||
if (socket.userid === payload.from) {
|
||||
let toClient = server.findSockets({ channel: socket.channel, userid: payload.from });
|
||||
[toClient] = toClient;
|
||||
payload.type = 'invite';
|
||||
payload.from = toClient.nick;
|
||||
payload.text = `You invited ${toClient.nick} to ?${payload.inviteChannel}`;
|
||||
} else if (socket.userid === payload.to) {
|
||||
let fromClient = server.findSockets({ channel: socket.channel, userid: payload.from });
|
||||
[fromClient] = fromClient;
|
||||
payload.type = 'invite';
|
||||
payload.from = fromClient.nick;
|
||||
payload.text = `${fromClient.nick} invited you to ?${payload.inviteChannel}`;
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function banCheck({ server, socket, payload }) {
|
||||
if (socket.hcProtocol === 1) {
|
||||
let targetClient = server.findSockets({ channel: socket.channel, nick: payload.nick });
|
||||
|
||||
if (targetClient.length === 0) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[targetClient] = targetClient;
|
||||
|
||||
payload.userid = targetClient.userid;
|
||||
payload.channel = socket.channel;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function dumbCheck({ server, socket, payload }) {
|
||||
if (socket.hcProtocol === 1) {
|
||||
let targetClient = server.findSockets({ channel: socket.channel, nick: payload.nick });
|
||||
|
||||
if (targetClient.length === 0) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[targetClient] = targetClient;
|
||||
|
||||
payload.userid = targetClient.userid;
|
||||
payload.channel = socket.channel;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function kickCheck({ server, socket, payload }) {
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof payload.nick !== 'number') {
|
||||
if (typeof payload.nick !== 'object' && !Array.isArray(payload.nick)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const targetClient = server.findSockets({ channel: socket.channel, nick: payload.nick });
|
||||
|
||||
if (targetClient.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
payload.userid = [];
|
||||
for (let i = 0, j = targetClient.length; i < j; i += 1) {
|
||||
payload.userid.push(targetClient[i].userid);
|
||||
}
|
||||
|
||||
payload.channel = socket.channel;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
export const info = {
|
||||
name: 'legacylayer',
|
||||
description: 'This module adjusts outgoing data, making it compatible with legacy clients',
|
||||
};
|
|
@ -11,7 +11,7 @@ export async function run({ server, socket, payload }) {
|
|||
|
||||
// send warning to target socket
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: payload.text,
|
||||
}, socket);
|
||||
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
*/
|
||||
|
||||
import * as UAC from '../utility/UAC/_info';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants';
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels';
|
||||
|
||||
// module main
|
||||
export async function run({
|
||||
|
@ -15,33 +21,38 @@ export async function run({
|
|||
}
|
||||
|
||||
// check user input
|
||||
if (typeof payload.userid !== 'number') {
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// find target user
|
||||
let badClient = server.findSockets({ channel: socket.channel, userid: payload.userid });
|
||||
|
||||
if (badClient.length === 0) {
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
text: 'Could not find user in channel',
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
[badClient] = badClient;
|
||||
const targetNick = badClient.nick;
|
||||
const targetNick = targetUser.nick;
|
||||
|
||||
// i guess banning mods or admins isn't the best idea?
|
||||
if (badClient.level >= socket.level) {
|
||||
if (targetUser.level >= socket.level) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn',
|
||||
text: 'Cannot ban other users of the same level, how rude',
|
||||
id: Errors.Global.PERMISSION,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// commit arrest record
|
||||
server.police.arrest(badClient.address, badClient.hash);
|
||||
server.police.arrest(targetUser.address, targetUser.hash);
|
||||
|
||||
console.log(`${socket.nick} [${socket.trip}] banned ${targetNick} in ${socket.channel}`);
|
||||
|
||||
|
@ -49,20 +60,20 @@ export async function run({
|
|||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Banned ${targetNick}`,
|
||||
user: UAC.getUserDetails(badClient),
|
||||
user: UAC.getUserDetails(targetUser),
|
||||
}, { channel: socket.channel, level: (level) => level < UAC.levels.moderator });
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick}#${socket.trip} banned ${targetNick} in ${payload.channel}, userhash: ${badClient.hash}`,
|
||||
text: `${socket.nick}#${socket.trip} banned ${targetNick} in ${payload.channel}, userhash: ${targetUser.hash}`,
|
||||
channel: payload.channel,
|
||||
user: UAC.getUserDetails(badClient),
|
||||
user: UAC.getUserDetails(targetUser),
|
||||
banner: UAC.getUserDetails(socket),
|
||||
}, { level: UAC.isModerator });
|
||||
|
||||
// force connection closed
|
||||
badClient.terminate();
|
||||
targetUser.terminate();
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('users-banned');
|
||||
|
|
|
@ -7,6 +7,12 @@
|
|||
*/
|
||||
|
||||
import * as UAC from '../utility/UAC/_info';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants';
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels';
|
||||
|
||||
// module constructor
|
||||
export function init(core) {
|
||||
|
@ -25,32 +31,37 @@ export async function run({
|
|||
}
|
||||
|
||||
// check user input
|
||||
if (typeof payload.userid !== 'number') {
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
payload.channel = socket.channel;
|
||||
} else if (typeof payload.userid !== 'number') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// find target user
|
||||
let badClient = server.findSockets({ channel: payload.channel, userid: payload.userid });
|
||||
|
||||
if (badClient.length === 0) {
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
text: 'Could not find user in channel',
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
[badClient] = badClient;
|
||||
|
||||
// likely dont need this, muting mods and admins is fine
|
||||
if (badClient.level >= socket.level) {
|
||||
if (targetUser.level >= socket.level) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn',
|
||||
text: 'This trick wont work on users of the same level',
|
||||
id: Errors.Global.PERMISSION,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// store hash in mute list
|
||||
const record = core.muzzledHashes[badClient.hash] = {
|
||||
const record = core.muzzledHashes[targetUser.hash] = {
|
||||
dumb: true,
|
||||
};
|
||||
|
||||
|
@ -62,7 +73,7 @@ export async function run({
|
|||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick}#${socket.trip} muzzled ${badClient.nick} in ${payload.channel}, userhash: ${badClient.hash}`,
|
||||
text: `${socket.nick}#${socket.trip} muzzled ${targetUser.nick} in ${payload.channel}, userhash: ${targetUser.hash}`,
|
||||
}, { level: UAC.isModerator });
|
||||
|
||||
return true;
|
||||
|
@ -129,7 +140,7 @@ export function inviteCheck({ core, socket, payload }) {
|
|||
/* const nickValid = Invite.checkNickname(payload.nick);
|
||||
if (nickValid !== null) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: nickValid,
|
||||
}, socket);
|
||||
return false;
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
*/
|
||||
|
||||
import * as UAC from '../utility/UAC/_info';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants';
|
||||
import {
|
||||
findUsers,
|
||||
} from '../utility/_Channels';
|
||||
|
||||
// module main
|
||||
export async function run({
|
||||
|
@ -16,27 +22,28 @@ export async function run({
|
|||
}
|
||||
|
||||
// check user input
|
||||
if (typeof payload.userid !== 'number') {
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof payload.nick !== 'string') {
|
||||
if (typeof payload.nick !== 'object' && !Array.isArray(payload.nick)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number') {
|
||||
// @todo create multi-ban ui
|
||||
if (typeof payload.userid !== 'object' && !Array.isArray(payload.userid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
let destChannel;
|
||||
if (typeof payload.to === 'string' && !!payload.to.trim()) {
|
||||
destChannel = payload.to;
|
||||
} else {
|
||||
destChannel = Math.random().toString(36).substr(2, 8);
|
||||
}
|
||||
|
||||
// find target user(s)
|
||||
const badClients = server.findSockets({ channel: payload.channel, userid: payload.userid });
|
||||
|
||||
const badClients = findUsers(server, payload);
|
||||
if (badClients.length === 0) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
text: 'Could not find user(s) in channel',
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user(s) in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
|
@ -45,8 +52,9 @@ export async function run({
|
|||
for (let i = 0, j = badClients.length; i < j; i += 1) {
|
||||
if (badClients[i].level >= socket.level) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn',
|
||||
text: 'Cannot kick other users with the same level, how rude',
|
||||
id: Errors.Global.PERMISSION,
|
||||
}, socket);
|
||||
} else {
|
||||
kicked.push(badClients[i]);
|
||||
|
@ -57,6 +65,13 @@ export async function run({
|
|||
return true;
|
||||
}
|
||||
|
||||
let destChannel;
|
||||
if (typeof payload.to === 'string' && !!payload.to.trim()) {
|
||||
destChannel = payload.to;
|
||||
} else {
|
||||
destChannel = Math.random().toString(36).substr(2, 8);
|
||||
}
|
||||
|
||||
// Announce the kicked clients arrival in destChannel and that they were kicked
|
||||
// Before they arrive, so they don't see they got moved
|
||||
for (let i = 0; i < kicked.length; i += 1) {
|
||||
|
@ -64,12 +79,17 @@ export async function run({
|
|||
cmd: 'onlineAdd',
|
||||
nick: kicked[i].nick,
|
||||
trip: kicked[i].trip || 'null',
|
||||
uType: 'user',
|
||||
hash: kicked[i].hash,
|
||||
level: UAC.levels.default,
|
||||
userid: kicked[i].userid,
|
||||
channel: destChannel,
|
||||
}, { channel: destChannel });
|
||||
}
|
||||
|
||||
// Move all kicked clients to the new channel
|
||||
for (let i = 0; i < kicked.length; i += 1) {
|
||||
// @todo multi-channel update
|
||||
kicked[i].channel = destChannel;
|
||||
|
||||
server.broadcast({
|
||||
|
@ -84,6 +104,7 @@ export async function run({
|
|||
for (let i = 0, j = kicked.length; i < j; i += 1) {
|
||||
server.broadcast({
|
||||
cmd: 'onlineRemove',
|
||||
userid: kicked[i].userid,
|
||||
nick: kicked[i].nick,
|
||||
}, { channel: socket.channel });
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Description: Removes the target socket from the current channel and forces a join event in another
|
||||
@deprecated This module will be removed or replaced
|
||||
*/
|
||||
|
||||
import * as UAC from '../utility/UAC/_info';
|
||||
|
@ -25,7 +26,7 @@ export async function run({ server, socket, payload }) {
|
|||
|
||||
if (badClients.length === 0) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Could not find user in channel',
|
||||
}, socket);
|
||||
}
|
||||
|
@ -34,7 +35,7 @@ export async function run({ server, socket, payload }) {
|
|||
|
||||
if (badClient.level >= socket.level) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Cannot move other users of the same level, how rude',
|
||||
}, socket);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ export async function run({
|
|||
// check user input
|
||||
if (typeof payload.ip !== 'string' && typeof payload.hash !== 'string') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: "hash:'targethash' or ip:'1.2.3.4' is required",
|
||||
}, socket);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ export async function run({
|
|||
// check user input
|
||||
if (typeof payload.ip !== 'string' && typeof payload.hash !== 'string') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove english and change to numeric id
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: "hash:'targethash' or ip:'1.2.3.4' is required",
|
||||
}, socket);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// NOTE: this has been moved into /utility/ as _UAC.js
|
||||
/**
|
||||
* User Account Control information containing level constants
|
||||
* and simple helper functions related to users
|
||||
|
|
85
server/src/commands/utility/_Channels.js
Normal file
85
server/src/commands/utility/_Channels.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
import {
|
||||
Errors,
|
||||
} from './_Constants';
|
||||
|
||||
/**
|
||||
* Checks if a client can join `channel`, returns numeric error code or true if
|
||||
* able to join
|
||||
* @public
|
||||
* @param {string} channel Target channel
|
||||
* @param {object} socket Target client to evaluate
|
||||
* @return {boolean||error id}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export function canJoinChannel(channel, socket) {
|
||||
if (typeof channel !== 'string') return Errors.Channel.INVALID_NAME;
|
||||
if (channel === '') return Errors.Channel.INVALID_NAME;
|
||||
if (channel.length > 120) return Errors.Channel.INVALID_LENGTH;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing info about the specified channel,
|
||||
* including if it is owned, mods, permissions
|
||||
* @public
|
||||
* @param {string} config Server config object
|
||||
* @param {string} channel Target channel
|
||||
* @return {object}
|
||||
*/
|
||||
export function getChannelSettings(config, channel) {
|
||||
if (typeof config.channels !== 'undefined') {
|
||||
if (typeof config.channels[channel] !== 'undefined') {
|
||||
return config.channels[channel];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
owned: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing info about the specified channel,
|
||||
* including if it is owned, mods, permissions
|
||||
* @public
|
||||
* @param {MainServer} server Main server reference
|
||||
* @param {object} payload Object containing `userid` or `nick`
|
||||
* @param {number} limit Optional return limit
|
||||
* @return {array}
|
||||
*/
|
||||
export function findUsers(server, payload, limit = 0) {
|
||||
let targetClients;
|
||||
|
||||
if (typeof payload.userid !== 'undefined') {
|
||||
targetClients = server.findSockets({
|
||||
channel: payload.channel,
|
||||
userid: payload.userid,
|
||||
});
|
||||
} else if (typeof payload.nick !== 'undefined') {
|
||||
targetClients = server.findSockets({
|
||||
channel: payload.channel,
|
||||
nick: payload.nick,
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (limit !== 0 && targetClients.length > limit) {
|
||||
return targetClients.splice(0, limit);
|
||||
}
|
||||
|
||||
return targetClients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload for `findUsers` when only 1 user is expected
|
||||
* @public
|
||||
* @param {MainServer} server Main server reference
|
||||
* @param {object} payload Object containing `userid` or `nick`
|
||||
* @param {number} limit Optional return limit
|
||||
* @return {boolean||object}
|
||||
*/
|
||||
export function findUser(server, payload) {
|
||||
return findUsers(server, payload, 1)[0] || false;
|
||||
}
|
34
server/src/commands/utility/_Constants.js
Normal file
34
server/src/commands/utility/_Constants.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
/* Base error ranges */
|
||||
const GlobalErrors = 10;
|
||||
const JoinErrors = 20;
|
||||
const ChannelErrors = 30;
|
||||
const InviteErrors = 40;
|
||||
|
||||
/**
|
||||
* Holds the numeric id values for each error type
|
||||
* @typedef {object} Errors
|
||||
*/
|
||||
exports.Errors = {
|
||||
Global: {
|
||||
RATELIMIT: GlobalErrors + 1,
|
||||
UNKNOWN_USER: GlobalErrors + 2,
|
||||
PERMISSION: GlobalErrors + 3,
|
||||
},
|
||||
|
||||
Join: {
|
||||
RATELIMIT: JoinErrors + 1,
|
||||
INVALID_NICK: JoinErrors + 2,
|
||||
ALREADY_JOINED: JoinErrors + 3,
|
||||
NAME_TAKEN: JoinErrors + 4,
|
||||
},
|
||||
|
||||
Channel: {
|
||||
INVALID_NAME: ChannelErrors + 1,
|
||||
INVALID_LENGTH: ChannelErrors + 2,
|
||||
},
|
||||
|
||||
Invite: {
|
||||
RATELIMIT: InviteErrors + 1,
|
||||
INVALID_LENGTH: InviteErrors + 2,
|
||||
},
|
||||
};
|
90
server/src/commands/utility/_LegacyFunctions.js
Normal file
90
server/src/commands/utility/_LegacyFunctions.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
/* eslint no-param-reassign: 0 */
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
} from './_UAC';
|
||||
|
||||
/**
|
||||
* Marks the socket as using the legacy protocol and
|
||||
* applies the missing `pass` property to the payload
|
||||
* @param {MainServer} server Main server reference
|
||||
* @param {WebSocket} socket Target client socket
|
||||
* @param {object} payload The original `join` payload
|
||||
* @returns {object}
|
||||
*/
|
||||
export function upgradeLegacyJoin(server, socket, payload) {
|
||||
const newPayload = payload;
|
||||
|
||||
// `join` is the legacy entry point, so apply protocol version
|
||||
socket.hcProtocol = 1;
|
||||
|
||||
// this would have been applied in the `session` module, apply it now
|
||||
socket.hash = server.getSocketHash(socket);
|
||||
|
||||
// pull the password from the nick
|
||||
const nickArray = payload.nick.split('#', 2);
|
||||
newPayload.nick = nickArray[0].trim();
|
||||
if (nickArray[1] && typeof payload.pass === 'undefined') {
|
||||
newPayload.pass = nickArray[1]; // eslint-disable-line prefer-destructuring
|
||||
}
|
||||
|
||||
// dunno how this happened on the legacy version
|
||||
if (typeof payload.password !== 'undefined') {
|
||||
newPayload.pass = payload.password;
|
||||
}
|
||||
|
||||
// apply the missing `userid` prop
|
||||
if (typeof socket.userid === 'undefined') {
|
||||
socket.userid = Math.floor(Math.random() * 9999999999999);
|
||||
}
|
||||
|
||||
return newPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the correct `uType` label for the specific level
|
||||
* @param {number} level Numeric level to find the label for
|
||||
*/
|
||||
export function legacyLevelToLabel(level) {
|
||||
if (isAdmin(level)) return 'admin';
|
||||
if (isModerator(level)) return 'mod';
|
||||
|
||||
return 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the outgoing payload to an `info` cmd and add/change missing props
|
||||
* @param {object} payload Numeric level to find the label for
|
||||
* @param {string} nick Numeric level to find the label for
|
||||
* @return {object}
|
||||
*/
|
||||
export function legacyInviteOut(payload, nick) {
|
||||
return {
|
||||
...payload,
|
||||
...{
|
||||
cmd: 'info',
|
||||
type: 'invite',
|
||||
from: nick,
|
||||
text: `${nick} invited you to ?${payload.inviteChannel}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the outgoing payload to an `info` cmd and add/change missing props
|
||||
* @param {object} payload Numeric level to find the label for
|
||||
* @param {string} nick Numeric level to find the label for
|
||||
* @return {object}
|
||||
*/
|
||||
export function legacyInviteReply(payload, nick) {
|
||||
return {
|
||||
...payload,
|
||||
...{
|
||||
cmd: 'info',
|
||||
type: 'invite',
|
||||
from: '',
|
||||
text: `You invited ${nick} to ?${payload.inviteChannel}`,
|
||||
},
|
||||
};
|
||||
}
|
186
server/src/commands/utility/_UAC.js
Normal file
186
server/src/commands/utility/_UAC.js
Normal file
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
* User Account Control information containing level constants
|
||||
* and simple helper functions related to users
|
||||
* @property {Object} levels - Defines labels for default permission ranges
|
||||
* @author MinusGix ( https://github.com/MinusGix )
|
||||
* @version v1.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*/
|
||||
|
||||
import {
|
||||
getChannelSettings,
|
||||
} from './_Channels';
|
||||
|
||||
const crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* Object defining labels for default permission ranges
|
||||
* @typedef {Object} levels
|
||||
* @property {number} admin Global administrator range
|
||||
* @property {number} moderator Global moderator range
|
||||
* @property {number} channelOwner Local administrator range
|
||||
* @property {number} channelModerator Local moderator range
|
||||
* @property {number} channelTrusted Local (non-public) channel trusted
|
||||
* @property {number} trustedUser Public channel trusted
|
||||
* @property {number} default Default user level
|
||||
*/
|
||||
export const levels = {
|
||||
admin: 9999999,
|
||||
moderator: 999999,
|
||||
|
||||
channelOwner: 99999,
|
||||
channelModerator: 9999,
|
||||
channelTrusted: 8999,
|
||||
|
||||
trustedUser: 500,
|
||||
default: 100,
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if target level is equal or greater than the global admin level
|
||||
* @public
|
||||
* @param {number} level Level to verify
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isAdmin(level) {
|
||||
return level >= levels.admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if target level is equal or greater than the global moderator level
|
||||
* @public
|
||||
* @param {number} level Level to verify
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isModerator(level) {
|
||||
return level >= levels.moderator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if target level is equal or greater than the channel owner level
|
||||
* @public
|
||||
* @param {number} level Level to verify
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isChannelOwner(level) {
|
||||
return level >= levels.channelOwner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if target level is equal or greater than the channel moderator level
|
||||
* @public
|
||||
* @param {number} level Level to verify
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isChannelModerator(level) {
|
||||
return level >= levels.channelModerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if target level is equal or greater than the channel trust level
|
||||
* @public
|
||||
* @param {number} level Level to verify
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isChannelTrusted(level) {
|
||||
return level >= levels.channelTrusted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if target level is equal or greater than a trusted user
|
||||
* @public
|
||||
* @param {number} level Level to verify
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isTrustedUser(level) {
|
||||
return level >= levels.trustedUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object containing public information about the socket
|
||||
* @public
|
||||
* @param {WebSocket} socket Target client
|
||||
* @return {Object}
|
||||
*/
|
||||
export function getUserDetails(socket) {
|
||||
return {
|
||||
uType: socket.uType,
|
||||
nick: socket.nick,
|
||||
trip: socket.trip || 'null',
|
||||
hash: socket.hash,
|
||||
level: socket.level,
|
||||
userid: socket.userid,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the nickname is valid
|
||||
* @public
|
||||
* @param {string} nick Nickname to verify
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function verifyNickname(nick) {
|
||||
if (typeof nick === 'undefined') return false;
|
||||
|
||||
return /^[a-zA-Z0-9_]{1,24}$/.test(nick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes a user's password, returning a trip code
|
||||
* or a blank string
|
||||
* @public
|
||||
* @param {string} pass User's password
|
||||
* @param {string} config Server config object
|
||||
* @param {string} channel Channel-level permissions check
|
||||
* @return {string}
|
||||
*/
|
||||
export function getUserPerms(pass, config, channel) {
|
||||
if (!pass) {
|
||||
return {
|
||||
trip: '',
|
||||
level: levels.default,
|
||||
};
|
||||
}
|
||||
|
||||
const sha = crypto.createHash('sha256');
|
||||
sha.update(pass + config.tripSalt);
|
||||
const trip = sha.digest('base64').substr(0, 6);
|
||||
|
||||
// check if user is global admin
|
||||
if (trip === config.adminTrip) {
|
||||
return {
|
||||
trip: 'Admin',
|
||||
level: levels.admin,
|
||||
};
|
||||
}
|
||||
|
||||
// check if user is global mod
|
||||
config.mods.forEach((mod) => { // eslint-disable-line consistent-return
|
||||
if (trip === mod.trip) {
|
||||
return {
|
||||
trip,
|
||||
level: levels.moderator,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// check if user is global trusted
|
||||
// @todo channel ownership patch
|
||||
|
||||
return {
|
||||
trip,
|
||||
level: levels.default,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user