From 8287cbc7fb6bbb2148bfff5a8ffd4215c4f56527 Mon Sep 17 00:00:00 2001
From: Annika <56906084+AnnikaCodes@users.noreply.github.com>
Date: Thu, 6 Aug 2020 00:12:30 -0700
Subject: [PATCH] Remove User#authAtLeast and add more sysop bypasses (#7046)
---
config/config-example.js | 6 +++---
server/chat-commands/core.ts | 26 +++++++++++---------------
server/chat-commands/room-settings.ts | 8 ++++----
server/chat.ts | 10 +++++-----
server/global-types.ts | 1 +
server/rooms.ts | 22 ++++++----------------
server/user-groups.ts | 22 ++++++++++++++++++----
server/users.ts | 20 +++-----------------
8 files changed, 51 insertions(+), 64 deletions(-)
diff --git a/config/config-example.js b/config/config-example.js
index 89921d3594..051fa892cc 100644
--- a/config/config-example.js
+++ b/config/config-example.js
@@ -288,12 +288,12 @@ exports.restrictLinks = false;
exports.chatmodchat = false;
/**
* battle modchat - default minimum group for speaking in battles; changeable with /modchat
- * @type {false | string}
+ * @type {false | AuthLevel}
*/
exports.battlemodchat = false;
/**
- * pm modchat - minimum group for PMing other users, challenging other users
- * @type {false | string}
+ * PM modchat - minimum group for sending private messages or challenges to other users
+ * @type {false | AuthLevel}
*/
exports.pmmodchat = false;
/**
diff --git a/server/chat-commands/core.ts b/server/chat-commands/core.ts
index 2d5a8920ee..992538233f 100644
--- a/server/chat-commands/core.ts
+++ b/server/chat-commands/core.ts
@@ -572,8 +572,8 @@ export const commands: ChatCommands = {
if (user.settings.blockPMs === (target || true)) {
return this.errorReply(this.tr("You are already blocking private messages! To unblock, use /unblockpms"));
}
- if (target in Config.groups) {
- user.settings.blockPMs = target as GroupSymbol;
+ if (Users.Auth.isAuthLevel(target)) {
+ user.settings.blockPMs = target;
this.sendReply(this.tr `You are now blocking private messages, except from staff and ${target}.`);
} else if (target === 'autoconfirmed' || target === 'trusted' || target === 'unlocked') {
user.settings.blockPMs = target;
@@ -728,7 +728,7 @@ export const commands: ChatCommands = {
for (const setting in user.settings) {
if (setting in raw) {
if (setting === 'blockPMs' &&
- (raw[setting] in Config.groups || ['autoconfirmed', 'trusted', 'unlocked'].includes(raw[setting]))) {
+ Users.Auth.isAuthLevel(raw[setting])) {
settings[setting] = raw[setting];
} else {
settings[setting as keyof UserSettings] = !!raw[setting];
@@ -1202,12 +1202,10 @@ export const commands: ChatCommands = {
async search(target, room, user, connection) {
if (target) {
- if (Config.laddermodchat) {
- if (!Users.globalAuth.atLeast(user, Config.laddermodchat)) {
- const groupName = Config.groups[Config.laddermodchat].name || Config.laddermodchat;
- this.popupReply(`On this server, you must be of rank ${groupName} or higher to search for a battle.`);
- return false;
- }
+ if (Config.laddermodchat && !Users.globalAuth.atLeast(user, Config.laddermodchat)) {
+ const groupName = Config.groups[Config.laddermodchat].name || Config.laddermodchat;
+ this.popupReply(`This server requires you to be rank ${groupName} or higher to search for a battle.`);
+ return false;
}
const ladder = Ladders(target);
if (!user.registered && Config.forceregisterelo && await ladder.getRating(user.id) >= Config.forceregisterelo) {
@@ -1245,12 +1243,10 @@ export const commands: ChatCommands = {
if (!user.named) {
return this.popupReply(`You must choose a username before you challenge someone.`);
}
- if (Config.pmmodchat) {
- if (Users.globalAuth.atLeast(user, 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;
- }
+ if (Config.pmmodchat && !user.hasSysopAccess() && !Users.globalAuth.atLeast(user, Config.pmmodchat as GroupSymbol)) {
+ const groupName = Config.groups[Config.pmmodchat].name || Config.pmmodchat;
+ this.popupReply(`This server requires you to be rank ${groupName} or higher to challenge users.`);
+ return false;
}
return Ladders(target).makeChallenge(connection, targetUser);
},
diff --git a/server/chat-commands/room-settings.ts b/server/chat-commands/room-settings.ts
index f6a5fd9704..28f7f0ecd7 100644
--- a/server/chat-commands/room-settings.ts
+++ b/server/chat-commands/room-settings.ts
@@ -93,7 +93,7 @@ export const commands: ChatCommands = {
target = Users.PLAYER_SYMBOL;
/* falls through */
default:
- if (!Config.groups[target]) {
+ if (!Users.Auth.isAuthLevel(target)) {
this.errorReply(`The rank '${target}' was unrecognized as a modchat level.`);
return this.parse('/help modchat');
}
@@ -203,7 +203,7 @@ export const commands: ChatCommands = {
this.add(`|raw|
Moderated join is set to autoconfirmed!
Users must be rank autoconfirmed or invited with /invite to join
`);
this.addModAction(`${user.name} set modjoin to autoconfirmed.`);
this.modlog('MODJOIN', null, 'autoconfirmed');
- } else if (target in Config.groups || target === 'trusted') {
+ } else if (Users.Auth.isAuthLevel(target)) {
if (room.battle && !user.can('makeroom') && !'+%'.includes(target)) {
return this.errorReply(`/modjoin - Access denied from setting modjoin past % in battles.`);
}
@@ -1563,7 +1563,7 @@ export const pages: PageTable = {
this.title = `[Permissions]`;
const room = this.extractRoom();
if (!room) return `This room does not exist or does not support permissions.
`;
- if (!user.authAtLeast('%', room)) return `Access denied.
`;
+ if (!room.auth.atLeast(user, '%')) return `Access denied.
`;
const roomGroups = ['default', ...Config.groupsranking.slice(1)];
const permissions = room.settings.permissions || {};
@@ -1576,7 +1576,7 @@ export const pages: PageTable = {
const requiredRank = permissions[permission];
atLeastOne = true;
buf += `| ${permission} | `;
- if (user.authAtLeast('#', room)) {
+ if (room.auth.atLeast(user, '#')) {
buf += roomGroups.map(group => (
requiredRank === group ?
Utils.html`` :
diff --git a/server/chat.ts b/server/chat.ts
index 6b6056abd8..cf26fee8c7 100644
--- a/server/chat.ts
+++ b/server/chat.ts
@@ -954,7 +954,7 @@ export class CommandContext extends MessageContext {
this.errorReply(this.tr(`You are muted and cannot talk in this room.`));
return null;
}
- if (room.settings.modchat && !user.authAtLeast(room.settings.modchat, room)) {
+ if (room.settings.modchat && !room.auth.atLeast(user, room.settings.modchat)) {
if (room.settings.modchat === 'autoconfirmed') {
this.errorReply(
this.tr(
@@ -996,14 +996,14 @@ export class CommandContext extends MessageContext {
this.errorReply(`The user "${targetUser.name}" is locked and cannot be PMed.`);
return null;
}
- if (Config.pmmodchat && !user.authAtLeast(Config.pmmodchat) &&
+ if (Config.pmmodchat && !Users.globalAuth.atLeast(user, Config.pmmodchat) &&
!Users.Auth.hasPermission(targetUser, 'promote', Config.pmmodchat as GroupSymbol)) {
const groupName = Config.groups[Config.pmmodchat] && Config.groups[Config.pmmodchat].name || Config.pmmodchat;
this.errorReply(`On this server, you must be of rank ${groupName} or higher to PM users.`);
return null;
}
if (targetUser.settings.blockPMs &&
- (targetUser.settings.blockPMs === true || !user.authAtLeast(targetUser.settings.blockPMs)) &&
+ (targetUser.settings.blockPMs === true || !Users.globalAuth.atLeast(user, targetUser.settings.blockPMs)) &&
!user.can('lock')) {
Chat.maybeNotifyBlocked('pm', targetUser, user);
if (!targetUser.can('lock')) {
@@ -1016,7 +1016,7 @@ export class CommandContext extends MessageContext {
}
}
if (user.settings.blockPMs && (user.settings.blockPMs === true ||
- !targetUser.authAtLeast(user.settings.blockPMs)) && !targetUser.can('lock')) {
+ !Users.globalAuth.atLeast(targetUser, user.settings.blockPMs)) && !targetUser.can('lock')) {
this.errorReply(`You are blocking private messages right now.`);
return null;
}
@@ -1132,7 +1132,7 @@ export class CommandContext extends MessageContext {
return false;
}
if (targetUser.settings.blockPMs &&
- (targetUser.settings.blockPMs === true || !this.user.authAtLeast(targetUser.settings.blockPMs)) &&
+ (targetUser.settings.blockPMs === true || !Users.globalAuth.atLeast(this.user, targetUser.settings.blockPMs)) &&
!this.user.can('lock')
) {
Chat.maybeNotifyBlocked('pm', targetUser, this.user);
diff --git a/server/global-types.ts b/server/global-types.ts
index 4b74572a3a..5b8441dfc7 100644
--- a/server/global-types.ts
+++ b/server/global-types.ts
@@ -1,6 +1,7 @@
type Config = typeof import('../config/config-example') & AnyObject;
type GroupSymbol = import('./user-groups').GroupSymbol;
+type AuthLevel = import('./user-groups').AuthLevel;
/** not actually guaranteed to be one of these */
type PunishType = '#chatfilter' | '#hostfilter' | '#dnsbl' | '#ipban';
diff --git a/server/rooms.ts b/server/rooms.ts
index b6e232cadb..0cebf5ff33 100644
--- a/server/rooms.ts
+++ b/server/rooms.ts
@@ -76,8 +76,8 @@ export interface RoomSettings {
aliases?: string[];
banwords?: string[];
isPrivate?: boolean | 'hidden' | 'voice';
- modjoin?: string | true | null;
- modchat?: string | null;
+ modjoin?: AuthLevel | true | null;
+ modchat?: AuthLevel | null;
staffRoom?: boolean;
language?: string | false;
slowchat?: number | false;
@@ -473,23 +473,13 @@ export abstract class BasicRoom {
if (!this.settings.modjoin) return true;
// users with a room rank can always join
if (this.auth.has(user.id)) return true;
- const userGroup = user.can('makeroom') ? user.group : this.auth.get(user.id);
const modjoinSetting = this.settings.modjoin !== true ? this.settings.modjoin : this.settings.modchat;
if (!modjoinSetting) return true;
- let modjoinGroup = modjoinSetting;
-
- if (modjoinGroup === 'trusted') {
- if (user.trusted) return true;
- modjoinGroup = Config.groupsranking[1];
+ if (!Users.Auth.isAuthLevel(modjoinSetting)) {
+ Monitor.error(`Invalid modjoin setting in ${this.roomid}: ${modjoinSetting}`);
}
- if (modjoinGroup === 'autoconfirmed') {
- if (user.autoconfirmed) return true;
- modjoinGroup = Config.groupsranking[1];
- }
- if (!(userGroup in Config.groups)) return false;
- if (!(modjoinGroup in Config.groups)) throw new Error(`Invalid modjoin setting in ${this.roomid}: ${modjoinGroup}`);
- return Config.groups[userGroup].rank >= Config.groups[modjoinGroup].rank;
+ return Users.globalAuth.atLeast(user, modjoinSetting);
}
mute(user: User, setTime?: number) {
const userid = user.id;
@@ -596,7 +586,7 @@ export abstract class BasicRoom {
this.destroy();
}
reportJoin(type: 'j' | 'l' | 'n', entry: string, user: User) {
- const canTalk = user.authAtLeast(this.settings.modchat ?? 'unlocked', this) && !this.isMuted(user);
+ const canTalk = this.auth.atLeast(user, this.settings.modchat ?? 'unlocked') && !this.isMuted(user);
if (this.reportJoins && (canTalk || this.auth.has(user.id))) {
this.add(`|${type}|${entry}`).update();
return;
diff --git a/server/user-groups.ts b/server/user-groups.ts
index d7aa4fe408..21b7ff9183 100644
--- a/server/user-groups.ts
+++ b/server/user-groups.ts
@@ -2,6 +2,7 @@ import {FS} from '../lib/fs';
export type GroupSymbol = '~' | '&' | '#' | '★' | '*' | '@' | '%' | '☆' | '+' | ' ' | '‽' | '!';
export type EffectiveGroupSymbol = GroupSymbol | 'whitelist';
+export type AuthLevel = EffectiveGroupSymbol | 'unlocked' | 'trusted' | 'autoconfirmed';
export const PLAYER_SYMBOL: GroupSymbol = '\u2606';
export const HOST_SYMBOL: GroupSymbol = '\u2605';
@@ -58,9 +59,16 @@ export abstract class Auth extends Map {
isStaff(userid: ID) {
return this.has(userid) && this.get(userid) !== '+';
}
- atLeast(user: User, group: GroupSymbol, isPermissionCheck?: boolean) {
- if (!Config.groups[group]) return false;
+ atLeast(user: User, group: AuthLevel, isPermissionCheck?: boolean) {
+ if (user.hasSysopAccess()) return true;
+ if (group === 'trusted' || group === 'autoconfirmed') {
+ if (user.trusted && group === 'trusted') return true;
+ if (user.autoconfirmed && group === 'autoconfirmed') return true;
+ group = Config.groupsranking[1];
+ }
if (user.locked || user.semilocked) return false;
+ if (group === 'unlocked') return true;
+ if (!Config.groups[group]) return false;
if (this.get(user.id) === ' ' && group !== ' ') return false;
if (Auth.atLeast(this.get(user.id, true), group)) {
return true;
@@ -109,7 +117,9 @@ export abstract class Auth extends Map {
const symbol = auth.getEffectiveSymbol(user, useVisualGroup);
let targetSymbol = (typeof target === 'string' || !target) ? target : auth.get(target);
- if (targetSymbol === 'whitelist') targetSymbol = Auth.defaultSymbol();
+ if (!targetSymbol || ['whitelist', 'trusted', 'autoconfirmed'].includes(targetSymbol)) {
+ targetSymbol = Auth.defaultSymbol();
+ }
const group = Auth.getGroup(symbol);
if (group['root']) return true;
@@ -129,7 +139,7 @@ export abstract class Auth extends Map {
}
}
- return Auth.hasJurisdiction(symbol, jurisdiction, targetSymbol);
+ return Auth.hasJurisdiction(symbol, jurisdiction, targetSymbol as GroupSymbol);
}
static atLeast(symbol: EffectiveGroupSymbol, symbol2: EffectiveGroupSymbol) {
return Auth.getGroup(symbol).rank >= Auth.getGroup(symbol2).rank;
@@ -183,6 +193,10 @@ export abstract class Auth extends Map {
if (symbol.length !== 1) return false;
return !/[A-Za-z0-9|,]/.test(symbol);
}
+ static isAuthLevel(level: string): level is AuthLevel {
+ if (this.isValidSymbol(level)) return true;
+ return ['unlocked', 'trusted', 'autoconfirmed', 'whitelist'].includes(level);
+ }
static ROOM_PERMISSIONS = ROOM_PERMISSIONS;
static GLOBAL_PERMISSIONS = GLOBAL_PERMISSIONS;
}
diff --git a/server/users.ts b/server/users.ts
index 5d0cf30300..949cc9bc03 100644
--- a/server/users.ts
+++ b/server/users.ts
@@ -343,7 +343,7 @@ export class User extends Chat.MessageContext {
settings: {
blockChallenges: boolean,
- blockPMs: boolean | GroupSymbol | 'autoconfirmed' | 'trusted' | 'unlocked',
+ blockPMs: boolean | AuthLevel,
ignoreTickets: boolean,
hideBattlesFromTrainerCard: boolean,
};
@@ -531,20 +531,6 @@ export class User extends Chat.MessageContext {
const status = statusMessage + (this.userMessage || '');
return status;
}
- authAtLeast(minAuth: string, room: BasicRoom | null = null) {
- if (!minAuth || minAuth === ' ') return true;
- if (this.locked || this.semilocked) return false;
- if (minAuth === 'unlocked') return true;
- if (minAuth === 'trusted' && this.trusted) return true;
- if (minAuth === 'autoconfirmed' && this.autoconfirmed) return true;
-
- if (minAuth === 'trusted' || minAuth === 'autoconfirmed') {
- minAuth = Config.groupsranking[1];
- }
- if (!(minAuth in Config.groups)) return false;
- const auth = (room && !this.can('makeroom') ? room.auth.get(this.id) : this.group);
- return auth in Config.groups && Config.groups[auth].rank >= Config.groups[minAuth].rank;
- }
can(permission: RoomPermission, target: User | null, room: BasicRoom, cmd?: string, useVisualGroup?: boolean): boolean;
can(
permission: GlobalPermission,
@@ -1360,12 +1346,12 @@ export class User extends Chat.MessageContext {
chat(message: string, room: Room | null, connection: Connection) {
const now = Date.now();
- if (message.startsWith('/cmd userdetails') || message.startsWith('>> ') || this.isSysop) {
+ if (message.startsWith('/cmd userdetails') || message.startsWith('>> ') || this.hasSysopAccess()) {
// certain commands are exempt from the queue
Monitor.activeIp = connection.ip;
Chat.parse(message, room, this, connection);
Monitor.activeIp = null;
- if (this.isSysop) return;
+ if (this.hasSysopAccess()) return;
return false; // but end the loop here
}
|