Remove User#authAtLeast and add more sysop bypasses (#7046)

This commit is contained in:
Annika
2020-08-06 00:12:30 -07:00
committed by GitHub
parent 7f3e55deb9
commit 8287cbc7fb
8 changed files with 51 additions and 64 deletions

View File

@@ -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;
/**

View File

@@ -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);
},

View File

@@ -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|<div class="broadcast-red"><strong>Moderated join is set to autoconfirmed!</strong><br />Users must be rank autoconfirmed or invited with <code>/invite</code> to join</div>`);
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 `<h2>This room does not exist or does not support permissions.</h2>`;
if (!user.authAtLeast('%', room)) return `<h2>Access denied.</h2>`;
if (!room.auth.atLeast(user, '%')) return `<h2>Access denied.</h2>`;
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 += `<tr><td><strong>${permission}</strong></td><td>`;
if (user.authAtLeast('#', room)) {
if (room.auth.atLeast(user, '#')) {
buf += roomGroups.map(group => (
requiredRank === group ?
Utils.html`<button class="button disabled" style="font-weight:bold;color:#575757;background:#d3d3d3">${group}</button>` :

View File

@@ -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);

View File

@@ -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';

View File

@@ -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;

View File

@@ -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<ID, GroupSymbol | ''> {
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<ID, GroupSymbol | ''> {
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<ID, GroupSymbol | ''> {
}
}
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<ID, GroupSymbol | ''> {
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;
}

View File

@@ -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
}