Skip to content

Commit

Permalink
Setting a room rank can no longer demote globals
Browse files Browse the repository at this point in the history
Previously, if you gave e.g. roomvoice to a global moderator, that
would demote their room rank to voice. Now, they will remain a
moderator, with "voice" only appearing in `/roomauth` and `/auth`.

(Includes a refactor of Config.groups)
  • Loading branch information
Zarel committed Apr 17, 2020
1 parent 286246d commit 99ee40d
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 104 deletions.
2 changes: 1 addition & 1 deletion server/chat-commands/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1242,7 +1242,7 @@ export const commands: ChatCommands = {
}
if (Config.pmmodchat) {
const userGroup = user.group;
if (Config.groupsranking.indexOf(userGroup) < Config.groupsranking.indexOf(Config.pmmodchat)) {
if (Config.groupsranking.indexOf(userGroup) < Config.groupsranking.indexOf(Config.pmmodchat as GroupSymbol)) {
const groupName = Config.groups[Config.pmmodchat].name || Config.pmmodchat;
this.popupReply(`Because moderated chat is set, you must be of rank ${groupName} or higher to challenge users.`);
return false;
Expand Down
6 changes: 3 additions & 3 deletions server/chat-commands/moderation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const commands: ChatCommands = {
return this.errorReply(`User '${name}' is unregistered, and so can't be promoted.`);
}

const currentGroup = room.getAuth({id: userid, group: (Users.usergroups[userid] || ' ').charAt(0)});
const currentGroup = room.auth[userid] || Config.groupsranking[0];
let nextGroup = target as GroupSymbol;
if (target === 'deauth') nextGroup = Config.groupsranking[0];
if (!nextGroup) {
Expand All @@ -106,7 +106,7 @@ export const commands: ChatCommands = {
}
if (!user.can('makeroom')) {
if (currentGroup !== ' ' && !user.can(`room${Config.groups[currentGroup]?.id || 'voice'}`, null, room)) {
return this.errorReply(`/${cmd} - Access denied for promoting/demoting from ${(Config.groups[currentGroup] ? Config.groups[currentGroup].name : "an undefined group")}.`);
return this.errorReply(`/${cmd} - Access denied for promoting/demoting from ${Config.groups[currentGroup]?.name || currentGroup}.`);
}
if (nextGroup !== ' ' && !user.can('room' + Config.groups[nextGroup].id, null, room)) {
return this.errorReply(`/${cmd} - Access denied for promoting/demoting to ${Config.groups[nextGroup].name}.`);
Expand Down Expand Up @@ -1087,7 +1087,7 @@ export const commands: ChatCommands = {
}
if (!cmd.startsWith('global')) {
let groupid = Config.groups[nextGroup].id;
if (!groupid && nextGroup === Config.groupsranking[0]) groupid = 'deauth';
if (!groupid && nextGroup === Config.groupsranking[0]) groupid = 'deauth' as ID;
if (Config.groups[nextGroup].globalonly) return this.errorReply(`Did you mean "/global${groupid}"?`);
if (Config.groups[nextGroup].roomonly) return this.errorReply(`Did you mean "/room${groupid}"?`);
return this.errorReply(`Did you mean "/room${groupid}" or "/global${groupid}"?`);
Expand Down
6 changes: 3 additions & 3 deletions server/chat-commands/room-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const commands: ChatCommands = {
user.can('modchatall', null, room) ? Config.groupsranking.indexOf(room.getAuth(user)) :
1;

if (room.modchat && room.modchat.length <= 1 && Config.groupsranking.indexOf(room.modchat) > threshold) {
if (room.modchat && room.modchat.length <= 1 && Config.groupsranking.indexOf(room.modchat as GroupSymbol) > threshold) {
return this.errorReply(`/modchat - Access denied for changing a setting higher than ${Config.groupsranking[threshold]}.`);
}
if ((room as GameRoom).requestModchat) {
Expand Down Expand Up @@ -100,7 +100,7 @@ export const commands: ChatCommands = {
this.errorReply(`The rank '${target}' was unrecognized as a modchat level.`);
return this.parse('/help modchat');
}
if (Config.groupsranking.indexOf(target) > threshold) {
if (Config.groupsranking.indexOf(target as GroupSymbol) > threshold) {
return this.errorReply(`/modchat - Access denied for setting higher than ${Config.groupsranking[threshold]}.`);
}
room.modchat = target;
Expand Down Expand Up @@ -1321,7 +1321,7 @@ export const roomSettings: SettingsHandler[] = [
'off',
'autoconfirmed',
'trusted',
...RANKS.slice(1, threshold + 1),
...RANKS.slice(1, threshold! + 1),
].map(rank => [rank, (rank === 'off' ? !room.modchat : rank === room.modchat) || `modchat ${rank || 'off'}`]);

return {
Expand Down
100 changes: 99 additions & 1 deletion server/config-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,113 @@

import * as defaults from '../config/config-example';

export interface GroupInfo {
symbol: GroupSymbol;
id: ID;
name: string;
rank: number;
inherit?: GroupSymbol;
jurisdiction?: string;
globalGroupInPersonalRoom?: GroupSymbol;
[k: string]: string | true | number | undefined;
}

export type ConfigType = typeof defaults & {
groups: {[symbol: string]: GroupInfo},
groupsranking: GroupSymbol[],
greatergroupscache: {[combo: string]: GroupSymbol},
[k: string]: any,
};

const CONFIG_PATH = require.resolve('../config/config');

export function load(invalidate = false) {
if (invalidate) delete require.cache[CONFIG_PATH];
// eslint-disable-next-line @typescript-eslint/no-var-requires
const config: Config = Object.assign({}, defaults, require('../config/config'));
const config = Object.assign({}, defaults, require('../config/config')) as ConfigType;
// config.routes is nested - we need to ensure values are set for its keys as well.
config.routes = Object.assign({}, defaults.routes, config.routes);
cacheGroupData(config);
return config;
}

export function cacheGroupData(config: ConfigType) {
if (config.groups) {
// Support for old config groups format.
// Should be removed soon.
console.error(
`You are using a deprecated version of user group specification in config.\n` +
`Support for this will be removed soon.\n` +
`Please ensure that you update your config.js to the new format (see config-example.js, line 220).\n`
);
} else {
config.punishgroups = Object.create(null);
config.groups = Object.create(null);
config.groupsranking = [];
config.greatergroupscache = Object.create(null);
}

const groups = config.groups;
const punishgroups = config.punishgroups;
const cachedGroups: {[k: string]: 'processing' | true} = {};

function cacheGroup(sym: string, groupData: GroupInfo) {
if (cachedGroups[sym] === 'processing') return false; // cyclic inheritance.

if (cachedGroups[sym] !== true && groupData['inherit']) {
cachedGroups[sym] = 'processing';
const inheritGroup = groups[groupData['inherit']];
if (cacheGroup(groupData['inherit'], inheritGroup)) {
// Add lower group permissions to higher ranked groups,
// preserving permissions specifically declared for the higher group.
for (const key in inheritGroup) {
if (key in groupData) continue;
groupData[key] = inheritGroup[key];
}
}
delete groupData['inherit'];
}
return (cachedGroups[sym] = true);
}

if (config.grouplist) { // Using new groups format.
const grouplist = config.grouplist as any;
const numGroups = grouplist.length;
for (let i = 0; i < numGroups; i++) {
const groupData = grouplist[i];

// punish groups
if (groupData.punishgroup) {
punishgroups[groupData.id] = groupData;
continue;
}

groupData.rank = numGroups - i - 1;
groups[groupData.symbol] = groupData;
config.groupsranking.unshift(groupData.symbol);
}
}

for (const sym in groups) {
const groupData = groups[sym];
cacheGroup(sym, groupData);
}

// hardcode default punishgroups.
if (!punishgroups.locked) {
punishgroups.locked = {
name: 'Locked',
id: 'locked',
symbol: '\u203d',
};
}
if (!punishgroups.muted) {
punishgroups.muted = {
name: 'Muted',
id: 'muted',
symbol: '!',
};
}
}

export const Config = load();
6 changes: 2 additions & 4 deletions server/global-variables.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import * as TeamValidatorAsyncType from './team-validator-async';
import * as UsersType from './users';
import * as VerifierType from './verifier';

import * as ConfigType from "../config/config-example";

import * as StreamsType from '../lib/streams';
import {ConfigType} from "../server/config-loader";

import {IPTools as IPToolsType} from './ip-tools';
import {LadderStore as LadderStoreType} from './ladders-remote';
Expand All @@ -30,7 +28,7 @@ declare global {
__version: {head: string, origin?: string, tree?: string};
}
}
const Config: typeof ConfigType & AnyObject;
const Config: ConfigType;
const Chat: typeof ChatType.Chat;
const IPTools: typeof IPToolsType;
const Ladders: typeof LaddersType;
Expand Down
1 change: 0 additions & 1 deletion server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ if (Config.watchconfig) {
FS(require.resolve('../config/config')).onModify(() => {
try {
global.Config = ConfigLoader.load(true);
if (global.Users) Users.cacheGroupData();
Monitor.notice('Reloaded ../config/config.js');
} catch (e) {
Monitor.adminlog("Error reloading ../config/config.js: " + e.stack);
Expand Down
25 changes: 20 additions & 5 deletions server/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,16 +328,31 @@ export abstract class BasicRoom {
* Gets the group symbol of a user in the room.
*/
getAuth(user: {id: ID, group: GroupSymbol} | User): GroupSymbol {
const globalGroup = this.auth && this.isPrivate === true ? ' ' : user.group;

if (this.auth && user.id in this.auth) {
return this.auth[user.id];
// room has roomauth
// authority is whichever is higher between roomauth and global auth
const roomGroup = this.auth[user.id];
let greaterGroup = Config.greatergroupscache[`${roomGroup}${globalGroup}`];
if (!greaterGroup) {
const roomRank = (Config.groups[roomGroup] || {rank: 0}).rank;
const globalRank = (Config.groups[globalGroup] || {rank: 0}).rank;
if (roomGroup === Users.PLAYER_SYMBOL || roomGroup === Users.HOST_SYMBOL || roomGroup === '#') {
// Player, Host, and Room Owner always trump higher global rank
greaterGroup = roomGroup;
} else {
greaterGroup = (roomRank > globalRank ? roomGroup : globalGroup);
}
Config.greatergroupscache[`${roomGroup}${globalGroup}`] = greaterGroup;
}
return greaterGroup;
}

if (this.parent) {
return this.parent.getAuth(user);
}
if (this.auth && this.isPrivate === true) {
return ' ';
}
return user.group;
return globalGroup;
}
checkModjoin(user: User) {
if (this.staffRoom && !user.isStaff && (!this.auth || (this.auth[user.id] || ' ') === ' ')) return false;
Expand Down
93 changes: 7 additions & 86 deletions server/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,86 +193,6 @@ function exportUsergroups() {
}
void importUsergroups();

function cacheGroupData() {
if (Config.groups) {
// Support for old config groups format.
// Should be removed soon.
console.error(
`You are using a deprecated version of user group specification in config.\n` +
`Support for this will be removed soon.\n` +
`Please ensure that you update your config.js to the new format (see config-example.js, line 220).\n`
);
} else {
Config.punishgroups = Object.create(null);
Config.groups = Object.create(null);
Config.groupsranking = [];
}

const groups = Config.groups;
const punishgroups = Config.punishgroups;
const cachedGroups: {[k: string]: 'processing' | true} = {};

function cacheGroup(sym: string, groupData: AnyObject) {
if (cachedGroups[sym] === 'processing') return false; // cyclic inheritance.

if (cachedGroups[sym] !== true && groupData['inherit']) {
cachedGroups[sym] = 'processing';
const inheritGroup = groups[groupData['inherit']];
if (cacheGroup(groupData['inherit'], inheritGroup)) {
// Add lower group permissions to higher ranked groups,
// preserving permissions specifically declared for the higher group.
for (const key in inheritGroup) {
if (key in groupData) continue;
groupData[key] = inheritGroup[key];
}
}
delete groupData['inherit'];
}
return (cachedGroups[sym] = true);
}

if (Config.grouplist) { // Using new groups format.
const grouplist = Config.grouplist;
const numGroups = grouplist.length;
for (let i = 0; i < numGroups; i++) {
const groupData = grouplist[i];

// punish groups
if (groupData.punishgroup) {
punishgroups[groupData.id] = groupData;
continue;
}

// @ts-ignore - dyanmically assigned property
groupData.rank = numGroups - i - 1;
groups[groupData.symbol] = groupData;
Config.groupsranking.unshift(groupData.symbol);
}
}

for (const sym in groups) {
const groupData = groups[sym];
cacheGroup(sym, groupData);
}

// hardcode default punishgroups.
if (!punishgroups.locked) {
punishgroups.locked = {
name: 'Locked',
id: 'locked',
symbol: '\u203d',
};
}
if (!punishgroups.muted) {
punishgroups.muted = {
name: 'Muted',
id: 'muted',
symbol: '!',
};
}
}
cacheGroupData();

function setOfflineGroup(name: string, group: GroupSymbol, forceTrusted?: boolean) {
if (!group) throw new Error(`Falsy value passed to setOfflineGroup`);
const userid = toID(name);
Expand Down Expand Up @@ -641,11 +561,11 @@ export class User extends Chat.MessageContext {
}

let group: GroupSymbol;
let targetGroup = '';
let targetGroup: GroupSymbol | '' = '';
let targetUser = null;

if (typeof target === 'string') {
targetGroup = target;
targetGroup = target as GroupSymbol;
} else {
targetUser = target;
}
Expand Down Expand Up @@ -686,7 +606,7 @@ export class User extends Chat.MessageContext {
if (jurisdiction.includes('s') && targetUser === this) {
return true;
}
if (jurisdiction.includes('u') && Config.groupsranking.indexOf(group) > Config.groupsranking.indexOf(targetGroup)) {
if (jurisdiction.includes('u') && Config.groupsranking.indexOf(group) > Config.groupsranking.indexOf(targetGroup as GroupSymbol)) {
return true;
}
}
Expand Down Expand Up @@ -1167,7 +1087,8 @@ export class User extends Chat.MessageContext {
this.avatar = Config.customavatars[this.id];
}

this.isStaff = Config.groups[this.group] && (Config.groups[this.group].lock || Config.groups[this.group].root);
const groupInfo = Config.groups[this.group];
this.isStaff = !!(groupInfo && (groupInfo.lock || groupInfo.root));
if (!this.isStaff) {
const staffRoom = Rooms.get('staff');
this.isStaff = !!staffRoom?.auth?.[this.id];
Expand Down Expand Up @@ -1198,7 +1119,8 @@ export class User extends Chat.MessageContext {
if (!group) throw new Error(`Falsy value passed to setGroup`);
this.group = group;
const wasStaff = this.isStaff;
this.isStaff = Config.groups[this.group] && (Config.groups[this.group].lock || Config.groups[this.group].root);
const groupInfo = Config.groups[this.group];
this.isStaff = !!(groupInfo && (groupInfo.lock || groupInfo.root));
if (!this.isStaff) {
const staffRoom = Rooms.get('staff');
this.isStaff = !!(staffRoom?.auth?.[this.id]);
Expand Down Expand Up @@ -1761,7 +1683,6 @@ export const Users = {
isUsernameKnown,
isTrusted,
importUsergroups,
cacheGroupData,
PLAYER_SYMBOL,
HOST_SYMBOL,
connections,
Expand Down

0 comments on commit 99ee40d

Please sign in to comment.