Experimental Backend Channel Perms Integration

This commit is contained in:
ADAMJR 2021-10-01 21:09:08 +01:00
parent f2e8987add
commit 03aea11985
7 changed files with 49 additions and 75 deletions

View File

@ -7,6 +7,7 @@ import { WS } from '../../types/ws';
import Deps from '../../utils/deps';
import { updateUser, validateUser } from '../modules/middleware';
import { WebSocket } from '../../ws/websocket';
import { WSGuard } from '../../ws/modules/ws-guard';
export const router = Router();
@ -14,6 +15,7 @@ const channels = Deps.get<Channels>(Channels);
const messages = Deps.get<Messages>(Messages);
const pings = Deps.get<Pings>(Pings);
const ws = Deps.get<WebSocket>(WebSocket);
const guard = Deps.get<WSGuard>(WSGuard);
router.get('/:channelId/messages', updateUser, validateUser, async (req, res) => {
const channelId = req.params.channelId;

View File

@ -7,14 +7,13 @@ import Roles from '../../data/roles';
import Users from '../../data/users';
import Guilds from '../../data/guilds';
import GuildMembers from '../../data/guild-members';
import { Prohibited } from '../../types/prohibited';
import { PermissionTypes } from '../../types/permission-types';
export class WSGuard {
constructor(
private channels = Deps.get<Channels>(Channels),
private guilds = Deps.get<Guilds>(Guilds),
private guildMembers = Deps.get<GuildMembers>(GuildMembers),
private members = Deps.get<GuildMembers>(GuildMembers),
private roles = Deps.get<Roles>(Roles),
private users = Deps.get<Users>(Users),
private ws = Deps.get<WebSocket>(WebSocket),
@ -30,33 +29,50 @@ export class WSGuard {
}
public async validateIsOwner(client: Socket, guildId: string) {
const isOwner = await Guild.exists({
_id: guildId,
ownerId: this.userId(client)
});
const ownerId = this.userId(client);
const isOwner = await Guild.exists({ _id: guildId, ownerId });
if (!isOwner)
throw new TypeError('Only the guild owner can do this');
}
public async canAccessChannel(client: Socket, channelId?: string, withUse = false) {
const channel = await this.channels.get(channelId);
const perms = (!withUse)
? PermissionTypes.Text.READ_MESSAGES
: PermissionTypes.Text.READ_MESSAGES | PermissionTypes.Text.SEND_MESSAGES;
await this.validateCan(client, channel.guildId, perms);
public async validateCan(client: Socket, guildId: string, permission: PermissionTypes.PermissionString) {
const can = await this.can(permission, guildId, this.userId(client));
this.validate(can, permission);
}
public async validateCan(client: Socket, guildId: string | undefined, permission: PermissionTypes.PermissionString) {
const userId = this.userId(client);
const member = await this.guildMembers.getInGuild(guildId, userId);
const guild = await this.guilds.get(guildId);
const can = await this.roles.hasPermission(guild, member, permission)
|| guild.ownerId === userId;
public async validateCanInChannel(client: Socket, channelId: string, permission: PermissionTypes.PermissionString) {
const can = await this.canInChannel(permission, channelId, this.userId(client));
this.validate(can, permission);
}
}
private async can(permission: PermissionTypes.PermissionString, guildId: string, userId: string) {
const guild = await this.guilds.get(guildId);
const member = await this.members.getInGuild(guildId, userId);
return guild.ownerId === member.userId
|| this.roles.hasPermission(guild, member, permission);
}
private async canInChannel(permission: PermissionTypes.PermissionString, channelId: string, userId: string) {
const channel = await this.channels.get(channelId);
const member = await this.members.getInGuild(channel.guildId, userId);
const overrides = channel.overrides?.filter(o => member.roleIds.includes(o.roleId)) ?? [];
const cumulativeAllowPerms = overrides.reduce((prev, curr) => prev | curr.allow, 0);
const cumulativeDenyPerms = overrides.reduce((prev, curr) => prev | curr.deny, 0);
const has = (totalPerms: number, permission: number) =>
Boolean(totalPerms & permission)
|| Boolean(totalPerms & PermissionTypes.General.ADMINISTRATOR);
const permNumber = PermissionTypes.Text[permission];
const canInheritantly = await this.can(permission, channel.guildId, userId);
const isAllowedByOverride = has(cumulativeAllowPerms, permNumber);
const isDeniedByOverride = has(cumulativeDenyPerms, permNumber);
return (canInheritantly && !isDeniedByOverride) || isAllowedByOverride;
}
// FIXME: you cannot combine string permissions with bitfields
private validate(can: boolean, permission: PermissionTypes.PermissionString) {
if (!can)

View File

@ -30,7 +30,7 @@ export class WSRooms {
for (const { id } of channels)
try {
await this.guard.canAccessChannel(client, id);
await this.guard.validateCanInChannel(client, id, 'READ_MESSAGES');
ids.push(id);
} catch {}
return ids;

View File

@ -21,7 +21,7 @@ export default class implements WSEvent<'MESSAGE_CREATE'> {
const authorId = ws.sessions.userId(client);
const [_, message, author] = await Promise.all([
this.guard.canAccessChannel(client, channelId, true),
this.guard.validateCanInChannel(client, channelId, 'SEND_MESSAGES'),
this.messages.create(authorId, channelId, { content, embed }),
this.users.getSelf(authorId),
]);

View File

@ -25,7 +25,7 @@ export default class implements WSEvent<'MESSAGE_DELETE'> {
try {
this.guard.validateIsUser(client, message.authorId);
} catch {
await this.guard.validateCan(client, channel.guildId, 'MANAGE_MESSAGES');
await this.guard.validateCanInChannel(client, channel.id, 'MANAGE_MESSAGES');
}
await message.deleteOne();

View File

@ -68,7 +68,7 @@ export class PermService {
public canMember(permission: PermissionTypes.PermissionString, guild: Entity.Guild, member: Entity.GuildMember) {
return guild.ownerId === member.userId
|| this.hasPermission(
|| this.hasPerm(
this.getTotalPerms(member, guild.id),
PermissionTypes.All[permission] as number,
);
@ -85,8 +85,8 @@ export class PermService {
const permNumber = PermissionTypes.Text[permission];
const canInheritantly = this.can(permission, guildId);
const isAllowedByOverride = this.hasPermission(cumulativeAllowPerms, permNumber);
const isDeniedByOverride = this.hasPermission(cumulativeDenyPerms, permNumber);
const isAllowedByOverride = this.hasPerm(cumulativeAllowPerms, permNumber);
const isDeniedByOverride = this.hasPerm(cumulativeDenyPerms, permNumber);
return (canInheritantly && !isDeniedByOverride) || isAllowedByOverride;
}
@ -96,17 +96,17 @@ export class PermService {
const member = this.getSelfMember(guildId);
return guild.ownerId === member.userId
|| this.hasPermission(
|| this.hasPerm(
this.getTotalPerms(member, guildId),
PermissionTypes.All[permission] as number,
);
}
public getTotalPerms(member: Entity.GuildMember, guildId: string) {
private getTotalPerms(member: Entity.GuildMember, guildId: string) {
return getGuildRoles(guildId)(this.state)
.filter(r => member?.roleIds.includes(r.id))
.reduce((acc, value) => value.permissions | acc, 0);
}
public hasPermission(totalPerms: number, permission: number) {
private hasPerm(totalPerms: number, permission: number) {
return Boolean(totalPerms & permission)
|| Boolean(totalPerms & PermissionTypes.General.ADMINISTRATOR);
}

View File

@ -1,44 +0,0 @@
// using explicit properties in websocket args makes this redundant?
/** Keys of objects that cannot be updated. */
export namespace Prohibited {
export const general: any = ['id', 'createdAt'];
export const app: (keyof Entity.App)[] = [
...general,
'owner',
'user',
];
export const channel: (keyof Entity.Channel)[] = [
...general,
'guildId',
'lastMessageId',
'type',
];
export const guild: (keyof Entity.Guild)[] = [
...general,
'nameAcronym',
];
export const guildMember: (keyof Entity.GuildMember)[] = [
...general,
'guildId',
'userId',
];
export const message: (keyof Entity.Message)[] = [
...general,
'authorId',
'channelId',
'updatedAt',
];
export const role: (keyof Entity.Role)[] = [
...general,
'guildId',
];
export const user: (keyof UserTypes.Self)[] = [
...general,
'channelIds',
'guildIds',
'badges',
'bot',
'verified',
];
}