Update server owner dashboard permissions automatically

This commit is contained in:
Dragory 2021-09-05 14:34:06 +03:00
parent 971ec0de6c
commit 48c4b3578d
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
9 changed files with 144 additions and 21 deletions

View file

@ -2,6 +2,8 @@ import { getRepository, Repository } from "typeorm";
import { ApiPermissionTypes } from "./ApiPermissionAssignments";
import { BaseRepository } from "./BaseRepository";
import { AllowedGuild } from "./entities/AllowedGuild";
import moment from "moment-timezone";
import { DBDateFormat } from "../utils";
export class AllowedGuilds extends BaseRepository {
private allowedGuilds: Repository<AllowedGuild>;
@ -37,7 +39,10 @@ export class AllowedGuilds extends BaseRepository {
}
updateInfo(id, name, icon, ownerId) {
return this.allowedGuilds.update({ id }, { name, icon, owner_id: ownerId });
return this.allowedGuilds.update(
{ id },
{ name, icon, owner_id: ownerId, updated_at: moment.utc().format(DBDateFormat) },
);
}
add(id, data: Partial<Omit<AllowedGuild, "id">> = {}) {

View file

@ -2,6 +2,9 @@ import { ApiPermissions } from "@shared/apiPermissions";
import { getRepository, Repository } from "typeorm";
import { BaseRepository } from "./BaseRepository";
import { ApiPermissionAssignment } from "./entities/ApiPermissionAssignment";
import { Permissions } from "discord.js";
import { ApiAuditLog } from "./ApiAuditLog";
import { AuditLogEventTypes } from "./apiAuditLogTypes";
export enum ApiPermissionTypes {
User = "USER",
@ -10,10 +13,12 @@ export enum ApiPermissionTypes {
export class ApiPermissionAssignments extends BaseRepository {
private apiPermissions: Repository<ApiPermissionAssignment>;
private auditLogs: ApiAuditLog;
constructor() {
super();
this.apiPermissions = getRepository(ApiPermissionAssignment);
this.auditLogs = new ApiAuditLog();
}
getByGuildId(guildId) {
@ -63,4 +68,66 @@ export class ApiPermissionAssignments extends BaseRepository {
.andWhere("expires_at <= NOW()")
.delete();
}
async applyOwnerChange(guildId: string, newOwnerId: string) {
const existingPermissions = await this.getByGuildId(guildId);
let updatedOwner = false;
for (const perm of existingPermissions) {
let hasChanges = false;
// Remove owner permission from anyone who currently has it
if (perm.permissions.includes(ApiPermissions.Owner)) {
perm.permissions.splice(perm.permissions.indexOf(ApiPermissions.Owner), 1);
hasChanges = true;
}
// Add owner permission if we encounter the new owner
if (perm.type === ApiPermissionTypes.User && perm.target_id === newOwnerId) {
perm.permissions.push(ApiPermissions.Owner);
updatedOwner = true;
hasChanges = true;
}
if (hasChanges) {
const criteria = {
guild_id: perm.guild_id,
type: perm.type,
target_id: perm.target_id,
};
if (perm.permissions.length === 0) {
// No remaining permissions -> remove entry
this.auditLogs.addEntry(guildId, "0", AuditLogEventTypes.REMOVE_API_PERMISSION, {
type: perm.type,
target_id: perm.target_id,
});
await this.apiPermissions.delete(criteria);
} else {
this.auditLogs.addEntry(guildId, "0", AuditLogEventTypes.EDIT_API_PERMISSION, {
type: perm.type,
target_id: perm.target_id,
permissions: perm.permissions,
expires_at: perm.expires_at,
});
await this.apiPermissions.update(criteria, {
permissions: perm.permissions,
});
}
}
}
if (!updatedOwner) {
this.auditLogs.addEntry(guildId, "0", AuditLogEventTypes.ADD_API_PERMISSION, {
type: ApiPermissionTypes.User,
target_id: newOwnerId,
permissions: [ApiPermissions.Owner],
expires_at: null,
});
await this.apiPermissions.insert({
guild_id: guildId,
type: ApiPermissionTypes.User,
target_id: newOwnerId,
permissions: [ApiPermissions.Owner],
});
}
}
}

View file

@ -1,7 +1,10 @@
import { ApiPermissionTypes } from "./ApiPermissionAssignments";
export const AuditLogEventTypes = {
ADD_API_PERMISSION: "ADD_API_PERMISSION",
REMOVE_API_PERMISSION: "REMOVE_API_PERMISSION",
EDIT_CONFIG: "EDIT_CONFIG",
ADD_API_PERMISSION: "ADD_API_PERMISSION" as const,
EDIT_API_PERMISSION: "EDIT_API_PERMISSION" as const,
REMOVE_API_PERMISSION: "REMOVE_API_PERMISSION" as const,
EDIT_CONFIG: "EDIT_CONFIG" as const,
};
export type AuditLogEventType = keyof typeof AuditLogEventTypes;
@ -20,12 +23,21 @@ export type EditConfigEventData = {};
export interface AuditLogEventData extends Record<AuditLogEventType, unknown> {
ADD_API_PERMISSION: {
type: ApiPermissionTypes;
target_id: string;
permissions: string[];
expires_at: string | null;
};
EDIT_API_PERMISSION: {
type: ApiPermissionTypes;
target_id: string;
permissions: string[];
expires_at: string | null;
};
REMOVE_API_PERMISSION: {
type: ApiPermissionTypes;
target_id: string;
};

View file

@ -14,4 +14,10 @@ export class AllowedGuild {
@Column()
owner_id: string;
@Column()
created_at: string;
@Column()
updated_at: string;
}

View file

@ -14,7 +14,7 @@ export class ApiAuditLogEntry<TEventType extends AuditLogEventType> {
@Column()
author_id: string;
@Column()
@Column({ type: String })
event_type: TEventType;
@Column("simple-json")

View file

@ -1,5 +1,6 @@
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm";
import { ApiUserInfo } from "./ApiUserInfo";
import { ApiPermissionTypes } from "../ApiPermissionAssignments";
@Entity("api_permissions")
export class ApiPermissionAssignment {
@ -7,9 +8,9 @@ export class ApiPermissionAssignment {
@PrimaryColumn()
guild_id: string;
@Column()
@Column({ type: "string" })
@PrimaryColumn()
type: string;
type: ApiPermissionTypes;
@Column()
@PrimaryColumn()

View file

@ -0,0 +1,24 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
export class AddTimestampsToAllowedGuilds1630840428694 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumns("allowed_guilds", [
new TableColumn({
name: "created_at",
type: "datetime",
default: "(NOW())",
}),
new TableColumn({
name: "updated_at",
type: "datetime",
default: "(NOW())",
onUpdate: "CURRENT_TIMESTAMP",
}),
]);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn("allowed_guilds", "updated_at");
await queryRunner.dropColumn("allowed_guilds", "created_at");
}
}

View file

@ -1,9 +1,11 @@
import * as t from "io-ts";
import { GuildPluginData } from "knub";
import { GuildPluginData, typedGuildEventListener } from "knub";
import { AllowedGuilds } from "../../data/AllowedGuilds";
import { MINUTES } from "../../utils";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { GuildInfoSaverPluginType } from "./types";
import { Guild } from "discord.js";
import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments";
export const GuildInfoSaverPlugin = zeppelinGuildPlugin<GuildInfoSaverPluginType>()({
name: "guild_info_saver",
@ -11,13 +13,18 @@ export const GuildInfoSaverPlugin = zeppelinGuildPlugin<GuildInfoSaverPluginType
configSchema: t.type({}),
beforeLoad(pluginData) {
pluginData.state.allowedGuilds = new AllowedGuilds();
},
events: [
typedGuildEventListener({
event: "guildUpdate",
listener({ args }) {
void updateGuildInfo(args.newGuild);
},
}),
],
afterLoad(pluginData) {
updateGuildInfo(pluginData);
pluginData.state.updateInterval = setInterval(() => updateGuildInfo(pluginData), 60 * MINUTES);
void updateGuildInfo(pluginData.guild);
pluginData.state.updateInterval = setInterval(() => updateGuildInfo(pluginData.guild), 60 * MINUTES);
},
beforeUnload(pluginData) {
@ -25,11 +32,13 @@ export const GuildInfoSaverPlugin = zeppelinGuildPlugin<GuildInfoSaverPluginType
},
});
function updateGuildInfo(pluginData: GuildPluginData<GuildInfoSaverPluginType>) {
pluginData.state.allowedGuilds.updateInfo(
pluginData.guild.id,
pluginData.guild.name,
pluginData.guild.iconURL(),
pluginData.guild.ownerId,
);
async function updateGuildInfo(guild: Guild) {
const allowedGuilds = new AllowedGuilds();
const existingData = (await allowedGuilds.find(guild.id))!;
allowedGuilds.updateInfo(guild.id, guild.name, guild.iconURL(), guild.ownerId);
if (existingData.owner_id !== guild.ownerId || existingData.created_at === existingData.updated_at) {
const apiPermissions = new ApiPermissionAssignments();
apiPermissions.applyOwnerChange(guild.id, guild.ownerId);
}
}

View file

@ -3,7 +3,6 @@ import { AllowedGuilds } from "../../data/AllowedGuilds";
export interface GuildInfoSaverPluginType extends BasePluginType {
state: {
allowedGuilds: AllowedGuilds;
updateInterval: NodeJS.Timeout;
};
}