Update permissions

Permissions have gotten out-of-date, so this commit syncs them.
Default permissions are now matched with Main, in particular including
the new & rank as admin (removing the old Leader rank and ~ symbol).

Relevant changes:
- Admin (~) and Leader (&) have been merged into Admin (&)
- The 'ban' permission was split into 'globalban' and 'ban'
- The 'broadcast' permission was renamed 'show' (going forward,
  "broadcast" should only refer to the big red/blue/green
  announcement bars.)
- Bots no longer have global moderation abilities, making it
  easier to give untrustworthy bots the "bot" rank.
This commit is contained in:
Guangcong Luo
2020-07-03 16:51:22 -04:00
parent a470fc48ca
commit d7ba455e2b
13 changed files with 108 additions and 76 deletions

View File

@@ -486,7 +486,8 @@ exports.chatlogreader = 'fs';
* - makeroom: Create/delete chatrooms, and set modjoin/roomdesc/privacy
* - editroom: Editing properties of rooms
* - editprivacy: Set modjoin/privacy only for battles
* - ban: Banning and unbanning.
* - globalban: Banning and unbanning from the entire server.
* - ban: Banning and unbanning in rooms.
* - mute: Muting and unmuting.
* - lock: locking (ipmute) and unlocking.
* - receivemutedpms: Receive PMs from muted users.
@@ -494,7 +495,8 @@ exports.chatlogreader = 'fs';
* - ip: IP checking.
* - alts: Alt checking.
* - modlog: view the moderator logs.
* - broadcast: Broadcast informational commands.
* - show: Show command output to other users.
* - showmedia: Show images and videos to other users.
* - declare: /declare command.
* - announce: /announce command.
* - modchat: Set modchat.
@@ -509,19 +511,17 @@ exports.chatlogreader = 'fs';
*/
exports.grouplist = [
{
symbol: '~',
symbol: '&',
id: "admin",
name: "Administrator",
root: true,
globalonly: true,
},
{
symbol: '&',
id: "leader",
name: "Leader",
inherit: '@',
jurisdiction: 'u',
promote: 'u',
globalonly: true,
console: true,
bypassall: true,
lockdown: true,
promote: '&u',
roomowner: true,
roombot: true,
roommod: true,
@@ -531,12 +531,12 @@ exports.grouplist = [
rangeban: true,
makeroom: true,
editroom: true,
editprivacy: true,
potd: true,
disableladder: true,
globalonly: true,
gdeclare: true,
gamemanagement: true,
exportinputlog: true,
editprivacy: true,
},
{
symbol: '#',
@@ -544,47 +544,57 @@ exports.grouplist = [
name: "Room Owner",
inherit: '@',
jurisdiction: 'u',
roomonly: true,
roombot: true,
roommod: true,
roomdriver: true,
editroom: true,
declare: true,
addhtml: true,
roomonly: true,
gamemanagement: true,
},
{
symbol: '*',
id: "bot",
name: "Bot",
inherit: '%',
jurisdiction: 'u',
addhtml: true,
declare: true,
bypassafktimer: true,
ip: false,
globalban: false,
lock: false,
alts: false,
},
{
symbol: '\u2605',
id: "host",
name: "Host",
inherit: '@',
jurisdiction: 'u',
declare: true,
addhtml: true,
modchat: true,
roomonly: true,
declare: true,
modchat: true,
gamemanagement: true,
joinbattle: true,
},
{
symbol: '*',
id: "bot",
name: "Bot",
inherit: '@',
jurisdiction: 'u',
declare: true,
addhtml: true,
bypassafktimer: true,
},
{
symbol: '@',
id: "mod",
name: "Moderator",
inherit: '%',
jurisdiction: 'u',
globalban: true,
ban: true,
modchatall: true,
roomvoice: true,
roomwhitelist: true,
forcerename: true,
ip: true,
alts: '@u',
@@ -597,10 +607,12 @@ exports.grouplist = [
name: "Driver",
inherit: '+',
jurisdiction: 'u',
globalGroupInPersonalRoom: '@',
announce: true,
warn: '\u2606u',
warn: '\u2605u',
kick: true,
mute: '\u2606u',
mute: '\u2605u',
lock: true,
forcerename: true,
timer: true,
@@ -619,22 +631,29 @@ exports.grouplist = [
id: "player",
name: "Player",
inherit: '+',
battleonly: true,
roomvoice: true,
modchat: true,
roomonly: true,
editprivacy: true,
gamemanagement: true,
tournaments: true,
joinbattle: true,
nooverride: true,
editprivacy: true,
exportinputlog: true,
},
{
symbol: '+',
id: "voice",
name: "Voice",
inherit: ' ',
alts: 's',
broadcast: true,
makegroupchat: true,
joinbattle: true,
show: true,
showmedia: true,
exportinputlog: true,
importinputlog: true,
},
{
symbol: 'whitelist',
@@ -643,12 +662,14 @@ exports.grouplist = [
inherit: ' ',
roomonly: true,
alts: 's',
broadcast: true,
importinputlog: true,
show: true,
showmedia: true,
exportinputlog: true,
importinputlog: true,
},
{
symbol: ' ',
ip: 's',
},
{
name: 'Locked',

View File

@@ -19,10 +19,12 @@ export const commands: ChatCommands = {
alt: 'whois',
alts: 'whois',
whoare: 'whois',
altsnorecurse: 'whois',
whois(target, room: Room | null, user, connection, cmd) {
if (room?.roomid === 'staff' && !this.runBroadcast()) return;
const targetUser = this.targetUserOrSelf(target, user.group === ' ');
const showAll = (cmd === 'ip' || cmd === 'whoare' || cmd === 'alt' || cmd === 'alts');
const showAll = (cmd === 'ip' || cmd === 'whoare' || cmd === 'alt' || cmd === 'alts' || cmd === 'altsnorecurse');
const showRecursiveAlts = showAll && (cmd !== 'altsnorecurse');
if (!targetUser) {
if (showAll) return this.parse('/offlinewhois ' + target);
return this.errorReply("User " + this.targetUsername + " not found.");
@@ -159,7 +161,7 @@ export const commands: ChatCommands = {
ips = ips.map(ip => {
const status = [];
const punishment = Punishments.ips.get(ip);
if (user.can('ip') && punishment) {
if (user.can('globalban') && punishment) {
const [punishType, userid] = punishment;
let punishMsg = Punishments.punishmentTypes.get(punishType) || 'punished';
if (userid !== targetUser.id) punishMsg += ` as ${userid}`;
@@ -227,6 +229,15 @@ export const commands: ChatCommands = {
}
}
this.sendReplyBox(buf);
if (showRecursiveAlts && canViewAlts) {
const targetId = toID(target);
for (const alt of Users.users.values()) {
if (alt !== targetUser && targetId in alt.prevNames) {
this.parse(`/altsnorecurse ${alt.name}`);
}
}
}
},
whoishelp: [
`/whois - Get details on yourself: alts, group, IP address, and rooms.`,
@@ -351,7 +362,7 @@ export const commands: ChatCommands = {
'!host': true,
host(target, room, user, connection, cmd) {
if (!target) return this.parse('/help host');
if (!this.can('ip')) return;
if (!this.can('globalban')) return;
target = target.trim();
if (!net.isIPv4(target)) return this.errorReply('You must pass a valid IPv4 IP to /host.');
void IPTools.lookup(target).then(({dnsbl, host, hostType}) => {

View File

@@ -844,7 +844,7 @@ export const commands: ChatCommands = {
unlockip(target, room, user) {
target = target.trim();
if (!target) return this.parse('/help unlock');
if (!this.can('ban')) return false;
if (!this.can('globalban')) return false;
const range = target.charAt(target.length - 1) === '*';
if (range && !this.can('rangeban')) return false;
@@ -890,7 +890,7 @@ export const commands: ChatCommands = {
if (!target && REQUIRE_REASONS) {
return this.errorReply("Global bans require a reason.");
}
if (!this.can('ban', targetUser)) return false;
if (!this.can('globalban', targetUser)) return false;
const name = targetUser.getLastName();
const userid = targetUser.getLastId();
@@ -963,7 +963,7 @@ export const commands: ChatCommands = {
globalunban: 'unglobalban',
unglobalban(target, room, user) {
if (!target) return this.parse(`/help unglobalban`);
if (!this.can('ban')) return false;
if (!this.can('globalban')) return false;
const name = Punishments.unban(target);
@@ -1851,7 +1851,7 @@ export const commands: ChatCommands = {
}
}
if (user.can('ban')) {
if (user.can('globalban')) {
const roomIps = Punishments.roomIps.get(room.roomid);
if (roomIps) {
@@ -1888,7 +1888,7 @@ export const commands: ChatCommands = {
markshared(target, room, user) {
if (!target) return this.parse('/help markshared');
if (!this.can('ban')) return false;
if (!this.can('globalban')) return false;
let [ip, note] = this.splitOne(target);
if (!/^[0-9.*]+$/.test(ip)) return this.errorReply("Please enter a valid IP address.");
@@ -1916,7 +1916,7 @@ export const commands: ChatCommands = {
unmarkshared(target, room, user) {
if (!target) return this.parse('/help unmarkshared');
if (!this.can('ban')) return false;
if (!this.can('globalban')) return false;
if (!/^[0-9.*]+$/.test(target)) return this.errorReply("Please enter a valid IP address.");
if (!Punishments.sharedIps.has(target)) return this.errorReply("This IP isn't marked as shared.");

View File

@@ -234,7 +234,7 @@ export const commands: ChatCommands = {
if (!lottery) {
return this.errorReply('This room does not have a lottery running.');
}
const canSeeIps = user.can('ban');
const canSeeIps = user.can('globalban');
const participants = Object.entries(lottery.participants).map(([ip, participant]) => {
return `- ${participant}${canSeeIps ? ' (IP: ' + ip + ')' : ''}`;
});

View File

@@ -1939,7 +1939,7 @@ export const commands: ChatCommands = {
if (!this.canTalk()) return;
if (!room || room.type !== 'chat') return this.errorReply(`This command is only meant to be used in chat rooms.`);
if (room.game) return this.errorReply(`There is already a game of ${room.game.title} in progress in this room.`);
if (!user.can('broadcast', null, room)) return this.errorReply(`/mafia ${cmd} - Access denied.`);
if (!user.can('show', null, room)) return this.errorReply(`/mafia ${cmd} - Access denied.`);
let nextHost = false;
if (room.roomid === 'mafia') {
@@ -2006,7 +2006,7 @@ export const commands: ChatCommands = {
if (room.roomid !== 'mafia') return this.errorReply(`This command can only be used in the Mafia room.`);
const args = target.split(',').map(toID);
if (['forceadd', 'add', 'remove', 'del', 'delete'].includes(args[0])) {
const permission = (user.id === args[1]) ? 'broadcast' : 'mute';
const permission = (user.id === args[1]) ? 'show' : 'mute';
if (['forceadd', 'add'].includes(args[0]) && !this.can(permission, null, room)) return;
if (['remove', 'del', 'delete'].includes(args[0]) && user.id !== args[1] && !this.can('mute', null, room)) return;
} else {
@@ -2220,7 +2220,7 @@ export const commands: ChatCommands = {
idea(target, room, user) {
const game = room.getGame(MafiaTracker);
if (!game) return this.errorReply(`There is no game of mafia running in this room.`);
if (!this.can('broadcast', null, room)) return;
if (!this.can('show', null, room)) return;
if (!user.can('mute', null, room) && game.hostid !== user.id && !game.cohosts.includes(user.id)) {
return this.errorReply(`/mafia idea - Access denied.`);
}
@@ -3127,7 +3127,7 @@ export const commands: ChatCommands = {
}
const game = targetRoom.getGame(MafiaTracker);
if (!game) return user.sendTo(targetRoom, `|error|There is no game of mafia running in this room.`);
if (game.hostid !== user.id && !game.cohosts.includes(user.id) && !this.can('broadcast', null, room)) return;
if (game.hostid !== user.id && !game.cohosts.includes(user.id) && !this.can('show', null, room)) return;
game.end();
this.room = targetRoom;
this.modlog('MAFIAEND', null);

View File

@@ -1537,7 +1537,7 @@ const ScavengerCommands: ChatCommands = {
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (
(!game.hosts.some(h => h.id === user.id) || !user.can('broadcast', null, room)) &&
(!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot edit the hints and answers if you are not the host.");
@@ -1554,7 +1554,7 @@ const ScavengerCommands: ChatCommands = {
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (
(!game.hosts.some(h => h.id === user.id) || !user.can('broadcast', null, room)) &&
(!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot add more hints if you are not the host.");
@@ -1575,7 +1575,7 @@ const ScavengerCommands: ChatCommands = {
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (
(!game.hosts.some(h => h.id === user.id) || !user.can('broadcast', null, room)) &&
(!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot remove hints if you are not the host.");
@@ -1598,7 +1598,7 @@ const ScavengerCommands: ChatCommands = {
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (
(!game.hosts.some(h => h.id === user.id) || !user.can('broadcast', null, room)) &&
(!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot edit hints if you are not the host.");

View File

@@ -1064,7 +1064,7 @@ class TriumvirateModeTrivia extends Trivia {
const commands: ChatCommands = {
new(target, room, user) {
if (!isTriviaRoom(room)) return this.errorReply("This command can only be used in the Trivia room.");
if (!this.can('broadcast', null, room)) return false;
if (!this.can('show', null, room)) return false;
if (!this.canTalk()) return;
if (room.game) {
return this.errorReply(`There is already a game of ${room.game.title} in progress.`);
@@ -1166,7 +1166,7 @@ const commands: ChatCommands = {
start(target, room) {
if (!isTriviaRoom(room)) return this.errorReply("This command can only be used in Trivia.");
if (!this.can('broadcast', null, room)) return false;
if (!this.can('show', null, room)) return false;
if (!this.canTalk()) return;
const game = getTriviaGame(this);
if (!game) return;
@@ -1196,7 +1196,7 @@ const commands: ChatCommands = {
end(target, room, user) {
if (!isTriviaRoom(room)) return this.errorReply("This command can only be used in Trivia.");
if (!this.can('broadcast', null, room)) return false;
if (!this.can('show', null, room)) return false;
if (!this.canTalk()) return;
const game = getTriviaGame(this);
if (!game) return;
@@ -1240,7 +1240,7 @@ const commands: ChatCommands = {
add(target, room, user, connection, cmd) {
if (room.roomid !== 'questionworkshop') return this.errorReply('This command can only be used in Question Workshop.');
if (cmd === 'add' && !this.can('mute', null, room)) return false;
if (cmd === 'submit' && !this.can('broadcast', null, room)) return false;
if (cmd === 'submit' && !this.can('show', null, room)) return false;
if (!target) return false;
if (!this.canTalk()) return false;
@@ -1565,7 +1565,7 @@ const commands: ChatCommands = {
search(target, room, user) {
if (room.roomid !== 'questionworkshop') return this.errorReply("This command can only be used in Question Workshop.");
if (!this.can('broadcast', null, room)) return false;
if (!this.can('show', null, room)) return false;
if (!target.includes(',')) return this.errorReply("No valid search arguments entered.");
let [type, ...query] = target.split(',');

View File

@@ -635,7 +635,7 @@ const cmds: ChatCommands = {
if (isNaN(parseInt(tid)) || tid.length < 5 || tid.length > 6) return this.errorReply("Invalid TID");
const targetUser = Users.get(giver);
if (!targetUser || !targetUser.connected) return this.errorReply(`User '${giver}' is not online.`);
if (!user.can('warn', null, room) && !(user.can('broadcast', null, room) && user === targetUser)) {
if (!user.can('warn', null, room) && !(user.can('show', null, room) && user === targetUser)) {
return this.errorReply("/qg - Access denied.");
}
if (!targetUser.autoconfirmed) {
@@ -693,7 +693,7 @@ const cmds: ChatCommands = {
if (isNaN(parseInt(tid)) || tid.length < 5 || tid.length > 6) return this.errorReply("Invalid TID");
const targetUser = Users.get(giver);
if (!targetUser || !targetUser.connected) return this.errorReply(`User '${giver}' is not online.`);
if (!user.can('warn', null, room) && !(user.can('broadcast', null, room) && user === targetUser)) {
if (!user.can('warn', null, room) && !(user.can('show', null, room) && user === targetUser)) {
return this.errorReply("/lg - Access denied.");
}
if (!targetUser.autoconfirmed) {
@@ -910,7 +910,7 @@ const cmds: ChatCommands = {
let reply = '';
switch (target) {
case 'staff':
if (!this.can('broadcast', null, room)) return;
if (!this.can('show', null, room)) return;
reply = '<strong>Staff commands:</strong><br />' +
'- question or qg <em>User | OT | TID | Prize | Question | Answer[ | Answer2 | Answer3]</em> - Start a new question giveaway (voices can only host for themselves, staff can for all users) (Requires: + % @ # &)<br />' +
'- lottery or lg <em>User | OT | TID | Prize[| Number of Winners]</em> - Starts a lottery giveaway (voices can only host for themselves, staff can for all users) (Requires: + % @ # &)<br />' +
@@ -922,7 +922,7 @@ const cmds: ChatCommands = {
'- count <em>Mon</em> - Displays how often a certain mon has been given away. Use <code>!giveaway count</code> to broadcast this to the entire room<br />';
break;
case 'gts':
if (!this.can('broadcast', null, room)) return;
if (!this.can('show', null, room)) return;
reply = '<strong>GTS giveaway commands:</strong><br />' +
'- gts start <em>User | Amount | Summary of given mon | What to deposit | What to look for</em> - Starts a gts giveaway (Requires: % @ # &)<br />' +
'- gts left <em>Amount</em> - Updates the amount left for the current GTS giveaway. Without an amount specified, shows how many Pokémon are left, and who the latest winners are.<br />' +

View File

@@ -168,7 +168,7 @@ export const commands: ChatCommands = {
const data = await YouTube.randChannel();
if (!data) return this.errorReply(`Error in getting channel data.`);
if (this.broadcasting) {
if (!this.can('broadcast', null, room)) return false;
if (!this.can('show', null, room)) return false;
this.addBox(data);
room.update();
} else {

View File

@@ -593,7 +593,7 @@ export class CommandContext extends MessageContext {
checkSlowchat(room: Room | null | undefined, user: User) {
if (!room || !room.settings.slowchat) return true;
if (user.can('broadcast', null, room)) return true;
if (user.can('show', null, room)) return true;
const lastActiveSeconds = (Date.now() - user.lastMessageTime) / 1000;
if (lastActiveSeconds < room.settings.slowchat) return false;
return true;
@@ -787,7 +787,7 @@ export class CommandContext extends MessageContext {
return true;
}
if (this.room && !this.user.can('broadcast', null, this.room)) {
if (this.room && !this.user.can('show', null, this.room)) {
this.errorReply(`You need to be voiced to broadcast this command's information.`);
this.errorReply(`To see it for yourself, use: /${this.message.slice(1)}`);
return false;
@@ -1034,7 +1034,7 @@ export class CommandContext extends MessageContext {
if (room?.settings.highTraffic &&
toID(message).replace(/[^a-z]+/, '').length < 2 &&
!user.can('broadcast', null, room)) {
!user.can('show', null, room)) {
this.errorReply(
this.tr('Due to this room being a high traffic room, your message must contain at least two letters.')
);

View File

@@ -1424,7 +1424,7 @@ export const Punishments = new class {
buf += `<th>Expire time</th>`;
buf += `<th>Reason</th>`;
buf += `<th>Alts</th>`;
if (user.can('ban')) buf += `<th>IPs</th>`;
if (user.can('globalban')) buf += `<th>IPs</th>`;
buf += `</tr>`;
for (const [userid, punishment] of punishments) {
const expiresIn = new Date(punishment.expireTime).getTime() - Date.now();
@@ -1436,7 +1436,7 @@ export const Punishments = new class {
buf += `<td>${expireString}</td>`;
buf += `<td>${punishment.reason || ' - '}</td>`;
buf += `<td>${punishment.userids.join(", ") || ' - '}</td>`;
if (user.can('ban')) buf += `<td>${punishment.ips.join(", ") || ' - '}</td>`;
if (user.can('globalban')) buf += `<td>${punishment.ips.join(", ") || ' - '}</td>`;
buf += `</tr>`;
}
buf += `</table>`;

View File

@@ -4,14 +4,14 @@ export const PLAYER_SYMBOL: GroupSymbol = '\u2606';
export const HOST_SYMBOL: GroupSymbol = '\u2605';
export const ROOM_PERMISSIONS = [
'addhtml', 'announce', 'ban', 'broadcast', 'bypassafktimer', 'declare', 'editprivacy', 'editroom', 'exportinputlog', 'game', 'gamemanagement', 'gamemoderation', 'joinbattle', 'kick', 'minigame', 'modchat', 'modchatall', 'modlog', 'mute', 'nooverride', 'receiveauthmessages', 'roombot', 'roomdriver', 'roommod', 'roomowner', 'roomvoice', 'showmedia', 'timer', 'tournaments', 'warn',
'addhtml', 'announce', 'ban', 'bypassafktimer', 'declare', 'editprivacy', 'editroom', 'exportinputlog', 'game', 'gamemanagement', 'gamemoderation', 'joinbattle', 'kick', 'minigame', 'modchat', 'modchatall', 'modlog', 'mute', 'nooverride', 'receiveauthmessages', 'roombot', 'roomdriver', 'roommod', 'roomowner', 'roomvoice', 'show', 'showmedia', 'timer', 'tournaments', 'warn',
] as const;
export const GLOBAL_PERMISSIONS = [
// administrative
'bypassall', 'console', 'disableladder', 'lockdown', 'potd', 'rawpacket',
// other
'addhtml', 'alts', 'autotimer', 'ban', 'bypassblocks', 'bypassafktimer', 'forcepromote', 'forcerename', 'forcewin', 'gdeclare', 'ignorelimits', 'importinputlog', 'ip', 'lock', 'makeroom', 'modlog', 'rangeban', 'promote',
'addhtml', 'alts', 'autotimer', 'globalban', 'bypassblocks', 'bypassafktimer', 'forcepromote', 'forcerename', 'forcewin', 'gdeclare', 'ignorelimits', 'importinputlog', 'ip', 'lock', 'makeroom', 'modlog', 'rangeban', 'promote',
] as const;
export type RoomPermission = typeof ROOM_PERMISSIONS[number];

View File

@@ -172,16 +172,16 @@ describe('Users features', function () {
});
it(`should allow 'u' permissions on lower ranked users`, function () {
const user = new User();
user.group = '&';
assert.equal(user.can('promote', user), false, 'targeting self');
user.group = '@';
assert.equal(user.can('globalban', user), false, 'targeting self');
const target = new User();
target.group = ' ';
assert.equal(user.can('promote', target), true, 'targeting lower rank');
assert.equal(user.can('globalban', target), true, 'targeting lower rank');
target.group = '@';
assert.equal(user.can('globalban', target), false, 'targeting same rank');
target.group = '&';
assert.equal(user.can('promote', target), false, 'targeting same rank');
target.group = '~';
assert.equal(user.can('promote', target), false, 'targeting higher rank');
assert.equal(user.can('globalban', target), false, 'targeting higher rank');
});
it(`should not allow users to demote themselves`, function () {
room = Rooms.createChatRoom("test");