summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--api/src/middlewares/RateLimit.ts70
-rw-r--r--api/src/routes/channels/#channel_id/messages/index.ts2
-rw-r--r--api/src/routes/guilds/#guild_id/roles.ts2
-rw-r--r--api/src/routes/guilds/#guild_id/templates.ts2
-rw-r--r--api/src/routes/guilds/index.ts88
-rw-r--r--api/src/schema/Guild.ts2
-rw-r--r--bundle/database.dbbin225280 -> 225280 bytes
-rw-r--r--gateway/src/opcodes/Identify.ts2
-rw-r--r--gateway/src/schema/Activity.ts4
-rw-r--r--util/src/entities/Application.ts2
-rw-r--r--util/src/entities/BaseClass.ts3
-rw-r--r--util/src/entities/Emoji.ts2
-rw-r--r--util/src/entities/Guild.ts24
-rw-r--r--util/src/entities/Member.ts6
-rw-r--r--util/src/entities/RateLimit.ts3
-rw-r--r--util/src/entities/Role.ts4
-rw-r--r--util/src/interfaces/Event.ts6
-rw-r--r--util/src/util/Config.ts3
-rw-r--r--util/src/util/Permissions.ts9
19 files changed, 128 insertions, 106 deletions
diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts

index e0cf103a..dffbc0d9 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts
@@ -1,11 +1,12 @@ -import { Config, listenEvent, emitEvent, RateLimit } from "@fosscord/util"; +import { Config, listenEvent } from "@fosscord/util"; import { NextFunction, Request, Response, Router } from "express"; -import { LessThan } from "typeorm"; import { getIpAdress } from "../util/ipAddress"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; // Docs: https://discord.com/developers/docs/topics/rate-limits +// TODO: use better caching (e.g. redis) as else it creates to much pressure on the database + /* ? bucket limit? Max actions/sec per bucket? @@ -18,6 +19,14 @@ TODO: different for methods (GET/POST) */ +type RateLimit = { + id: "global" | "error" | string; + executor_id: string; + hits: number; + blocked: boolean; + expires_at: Date; +}; + var Cache = new Map<string, RateLimit>(); const EventRateLimit = "RATELIMIT"; @@ -46,13 +55,22 @@ export default function rateLimit(opts: { const offender = Cache.get(executor_id + bucket_id); - if (offender && offender.blocked) { + if (offender) { const reset = offender.expires_at.getTime(); const resetAfterMs = reset - Date.now(); const resetAfterSec = resetAfterMs / 1000; - const global = bucket_id === "global"; - if (resetAfterMs > 0) { + if (resetAfterMs <= 0) { + offender.hits = 0; + offender.expires_at = new Date(Date.now() + opts.window * 1000); + offender.blocked = false; + + Cache.delete(executor_id + bucket_id); + } + + if (offender.blocked) { + const global = bucket_id === "global"; + console.log("blocked bucket: " + bucket_id, { resetAfterMs }); return ( res @@ -67,15 +85,9 @@ export default function rateLimit(opts: { // TODO: error rate limit message translation .send({ message: "You are being rate limited.", retry_after: resetAfterSec, global }) ); - } else { - offender.hits = 0; - offender.expires_at = new Date(Date.now() + opts.window * 1000); - offender.blocked = false; - // mongodb ttl didn't update yet -> manually update/delete - RateLimit.delete({ id: bucket_id, executor_id }); - Cache.delete(executor_id + bucket_id); } } + next(); const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window }; @@ -100,20 +112,20 @@ export async function initRateLimits(app: Router) { Cache.set(event.channel_id as string, event.data); event.acknowledge?.(); }); - await RateLimit.delete({ expires_at: LessThan(new Date()) }); // clean up if not already deleted - const limits = await RateLimit.find({ blocked: true }); - limits.forEach((limit) => { - Cache.set(limit.executor_id, limit); - }); + // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date + // const limits = await RateLimit.find({ blocked: true }); + // limits.forEach((limit) => { + // Cache.set(limit.executor_id, limit); + // }); setInterval(() => { Cache.forEach((x, key) => { if (new Date() > x.expires_at) { Cache.delete(key); - RateLimit.delete({ executor_id: key }); + // RateLimit.delete({ executor_id: key }); } }); - }, 1000 * 60 * 10); + }, 1000 * 60); app.use( rateLimit({ @@ -139,6 +151,25 @@ export async function initRateLimits(app: Router) { } async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { + const id = opts.executor_id + opts.bucket_id; + var limit = Cache.get(id); + if (!limit) { + limit = { + id: opts.bucket_id, + executor_id: opts.executor_id, + expires_at: new Date(Date.now() + opts.window * 1000), + hits: 0, + blocked: false + }; + Cache.set(id, limit); + } + + limit.hits++; + if (limit.hits >= opts.max_hits) { + limit.blocked = true; + } + + /* var ratelimit = await RateLimit.findOne({ id: opts.bucket_id, executor_id: opts.executor_id }); if (!ratelimit) { ratelimit = new RateLimit({ @@ -167,4 +198,5 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits } await ratelimit.save(); + */ } diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts
index 6307c022..17944548 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts
@@ -77,7 +77,7 @@ router.get("/", async (req: Request, res: Response) => { delete x.user_ids; }); // @ts-ignore - if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: 0n, avatar: null }; + if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null }; return x; }); diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts
index e9e777b9..796a8eb8 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts
@@ -40,7 +40,7 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => managed: false, position: 0, tags: null, - permissions: perms.bitfield & (body.permissions || 0n) + permissions: String(perms.bitfield & (body.permissions || 0n)) }).save(); await emitEvent({ diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts
index e1d2f5fd..a7613abf 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/api/src/routes/guilds/#guild_id/templates.ts
@@ -17,7 +17,7 @@ const TemplateGuildProjection: (keyof Guild)[] = [ "preferred_locale", "afk_timeout", "roles", - "channels", + // "channels", "afk_channel_id", "system_channel_id", "system_channel_flags", diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts
index c158c7d4..020aba6a 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts
@@ -19,63 +19,55 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = } const guild_id = Snowflake.generate(); - const guild = new Guild({ - name: body.name, - region: Config.get().regions.default, - owner_id: req.user_id, - icon: undefined, - afk_channel_id: undefined, - afk_timeout: 300, - application_id: undefined, - banner: undefined, - default_message_notifications: 0, - description: undefined, - splash: undefined, - discovery_splash: undefined, - explicit_content_filter: 0, - features: [], - id: guild_id, - large: undefined, - max_members: 250000, - max_presences: 250000, - max_video_channel_users: 25, - presence_count: 0, - member_count: 0, // will automatically be increased by addMember() - mfa_level: 0, - preferred_locale: "en-US", - premium_subscription_count: 0, - premium_tier: 0, - public_updates_channel_id: undefined, - rules_channel_id: undefined, - system_channel_flags: 0, - system_channel_id: undefined, - unavailable: false, - vanity_url_code: undefined, - verification_level: 0, - welcome_screen: { - enabled: false, - description: "No description", - welcome_channels: [] - }, - widget_channel_id: undefined, - widget_enabled: false - }); - - const [guild_doc, role] = await Promise.all([ - new Guild(guild).save(), - new Role({ + const guild = new Guild( + { + name: body.name, + region: Config.get().regions.default, + owner_id: req.user_id, + afk_timeout: 300, + default_message_notifications: 0, + explicit_content_filter: 0, + features: [], id: guild_id, + max_members: 250000, + max_presences: 250000, + max_video_channel_users: 25, + presence_count: 0, + member_count: 0, // will automatically be increased by addMember() + mfa_level: 0, + preferred_locale: "en-US", + premium_subscription_count: 0, + premium_tier: 0, + system_channel_flags: "0", + unavailable: false, + verification_level: 0, + welcome_screen: { + enabled: false, + description: "No description", + welcome_channels: [] + }, + widget_enabled: false + }, + { id: guild_id } + ); + const role = new Role( + { guild_id: guild_id, color: 0, hoist: false, managed: false, mentionable: false, name: "@everyone", - permissions: 2251804225n, + permissions: String("2251804225"), position: 0, tags: null - }).save() - ]); + }, + { + id: guild_id + } + ); + + await Promise.all([guild.save(), role.save()]); if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general" }]; diff --git a/api/src/schema/Guild.ts b/api/src/schema/Guild.ts
index 01690ae9..3e98fe76 100644 --- a/api/src/schema/Guild.ts +++ b/api/src/schema/Guild.ts
@@ -33,7 +33,7 @@ export const GuildUpdateSchema = { $icon: String, $verification_level: Number, $default_message_notifications: Number, - $system_channel_flags: Number, + $system_channel_flags: String, $system_channel_id: String, $explicit_content_filter: Number, $public_updates_channel_id: String, diff --git a/bundle/database.db b/bundle/database.db
index 9572c45e..2d4abd49 100644 --- a/bundle/database.db +++ b/bundle/database.db
Binary files differdiff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts
index 5be2acce..87008998 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts
@@ -42,7 +42,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { } } - const members = await Member.find({ where: { id: this.user_id }, relations: ["guilds"] }); + const members = await Member.find({ where: { id: this.user_id }, relations: ["guild"] }); const merged_members = members.map((x: any) => { const y = { ...x, user_id: x.id }; delete y.settings; diff --git a/gateway/src/schema/Activity.ts b/gateway/src/schema/Activity.ts
index 0fd0592f..f1665efd 100644 --- a/gateway/src/schema/Activity.ts +++ b/gateway/src/schema/Activity.ts
@@ -39,7 +39,7 @@ export const ActivitySchema = { $match: String, }, $instance: Boolean, - $flags: BigInt, + $flags: String, }, ], $since: Number, // unix time (in milliseconds) of when the client went idle, or null if the client is not idle @@ -79,7 +79,7 @@ export interface ActivitySchema { match?: string; // the secret for a specific instanced match }; instance?: boolean; - flags: bigint; // activity flags OR d together, describes what the payload includes + flags: string; // activity flags OR d together, describes what the payload includes } ]; since?: number; // unix time (in milliseconds) of when the client went idle, or null if the client is not idle diff --git a/util/src/entities/Application.ts b/util/src/entities/Application.ts
index a87b5cea..b179d171 100644 --- a/util/src/entities/Application.ts +++ b/util/src/entities/Application.ts
@@ -62,7 +62,7 @@ export class Application extends BaseClass { cover_image?: string; // the application's default rich presence invite cover image hash @Column() - flags: number; // the application's public flags + flags: string; // the application's public flags } export interface ApplicationCommand { diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts
index 31338ff6..63ce5836 100644 --- a/util/src/entities/BaseClass.ts +++ b/util/src/entities/BaseClass.ts
@@ -51,7 +51,8 @@ export class BaseClass extends BaseEntity { if (setter) { setter.call(this, props[key]); } else { - Object.defineProperty(this, key, { value: props[key] }); + // @ts-ignore + this[key] = props[key]; } } } diff --git a/util/src/entities/Emoji.ts b/util/src/entities/Emoji.ts
index 0686d476..4c0fccd3 100644 --- a/util/src/entities/Emoji.ts +++ b/util/src/entities/Emoji.ts
@@ -15,7 +15,7 @@ export class Emoji extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, (guild: Guild) => guild.id) + @ManyToOne(() => Guild, (guild: Guild) => guild.emojis) guild: Guild; @Column() diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index e6a93824..3e7e8917 100644 --- a/util/src/entities/Guild.ts +++ b/util/src/entities/Guild.ts
@@ -1,4 +1,4 @@ -import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; import { Emoji } from "./Emoji"; @@ -68,35 +68,35 @@ export class Guild extends BaseClass { member_ids: string[]; @JoinColumn({ name: "member_ids" }) - @ManyToMany(() => Member, (member: Member) => member.id) + @OneToMany(() => Member, (member: Member) => member.guild) members: Member[]; @RelationId((guild: Guild) => guild.roles) role_ids: string[]; @JoinColumn({ name: "role_ids" }) - @ManyToMany(() => Role, (role: Role) => role.id) + @OneToMany(() => Role, (role: Role) => role.guild) roles: Role[]; @RelationId((guild: Guild) => guild.channels) channel_ids: string[]; @JoinColumn({ name: "channel_ids" }) - @ManyToMany(() => Channel, (channel: Channel) => channel.id) + @OneToMany(() => Channel, (channel: Channel) => channel.guild) channels: Channel[]; @RelationId((guild: Guild) => guild.emojis) emoji_ids: string[]; @JoinColumn({ name: "emoji_ids" }) - @ManyToMany(() => Emoji, (emoji: Emoji) => emoji.id) + @OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild) emojis: Emoji[]; @RelationId((guild: Guild) => guild.voice_states) voice_state_ids: string[]; @JoinColumn({ name: "voice_state_ids" }) - @ManyToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.id) + @OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild) voice_states: VoiceState[]; @Column({ nullable: true }) @@ -109,7 +109,7 @@ export class Guild extends BaseClass { owner_id: string; @JoinColumn({ name: "owner_id" }) - @ManyToOne(() => User, (user: User) => user.id) + @OneToOne(() => User) owner: User; @Column({ nullable: true }) @@ -125,14 +125,14 @@ export class Guild extends BaseClass { public_updates_channel_id: string; @JoinColumn({ name: "public_updates_channel_id" }) - @ManyToOne(() => Channel, (channel: Channel) => channel.id) + @OneToOne(() => Channel, (channel: Channel) => channel.id) public_updates_channel?: Channel; @RelationId((guild: Guild) => guild.rules_channel) rules_channel_id?: string; @JoinColumn({ name: "rules_channel_id" }) - @ManyToOne(() => Channel, (channel: Channel) => channel.id) + @OneToOne(() => Channel, (channel: Channel) => channel.id) rules_channel?: string; @Column({ nullable: true }) @@ -145,7 +145,7 @@ export class Guild extends BaseClass { system_channel_id?: string; @JoinColumn({ name: "system_channel_id" }) - @ManyToMany(() => Channel, (channel: Channel) => channel.id) + @OneToOne(() => Channel, (channel: Channel) => channel.id) system_channel?: Channel; @Column({ nullable: true }) @@ -158,7 +158,7 @@ export class Guild extends BaseClass { vanity_url_code?: string; @JoinColumn({ name: "vanity_url_code" }) - @ManyToOne(() => Invite) + @OneToOne(() => Invite) vanity_url?: Invite; @Column({ nullable: true }) @@ -180,7 +180,7 @@ export class Guild extends BaseClass { widget_channel_id?: string; @JoinColumn({ name: "widget_channel_id" }) - @ManyToOne(() => Channel, (channel: Channel) => channel.id) + @OneToOne(() => Channel, (channel: Channel) => channel.id) widget_channel?: Channel; @Column({ nullable: true }) diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index 5b588d70..c5d289ef 100644 --- a/util/src/entities/Member.ts +++ b/util/src/entities/Member.ts
@@ -1,6 +1,6 @@ import { PublicUser, User } from "./User"; import { BaseClass } from "./BaseClass"; -import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm"; import { Guild } from "./Guild"; import { Config, emitEvent } from "../util"; import { @@ -26,7 +26,7 @@ export class Member extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, (guild: Guild) => guild.id) + @ManyToOne(() => Guild, (guild: Guild) => guild.members) guild: Guild; @Column({ nullable: true }) @@ -35,7 +35,7 @@ export class Member extends BaseClass { @RelationId((member: Member) => member.roles) role_ids: string[]; - @JoinColumn({ name: "role_ids" }) + @JoinTable() @ManyToMany(() => Role) roles: Role[]; diff --git a/util/src/entities/RateLimit.ts b/util/src/entities/RateLimit.ts
index 49af0416..fa9c32c1 100644 --- a/util/src/entities/RateLimit.ts +++ b/util/src/entities/RateLimit.ts
@@ -1,6 +1,5 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity } from "typeorm"; import { BaseClass } from "./BaseClass"; -import { User } from "./User"; @Entity("rate_limits") export class RateLimit extends BaseClass { diff --git a/util/src/entities/Role.ts b/util/src/entities/Role.ts
index ddae7e40..be8c731d 100644 --- a/util/src/entities/Role.ts +++ b/util/src/entities/Role.ts
@@ -27,8 +27,8 @@ export class Role extends BaseClass { @Column() name: string; - @Column({ type: "bigint" }) - permissions: bigint; + @Column() + permissions: string; @Column() position: number; diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index 0de55f71..814a8beb 100644 --- a/util/src/interfaces/Event.ts +++ b/util/src/interfaces/Event.ts
@@ -36,7 +36,7 @@ export interface ReadyEventData { mobile: boolean; desktop: boolean; email: string | undefined; - flags: bigint; + flags: string; mfa_enabled: boolean; nsfw_allowed: boolean; phone: string | undefined; @@ -85,7 +85,7 @@ export interface ReadyEventData { }; application?: { id: string; - flags: bigint; + flags: string; }; merged_members?: Omit<Member, "settings" | "user">[][]; // probably all users who the user is in contact with @@ -95,7 +95,7 @@ export interface ReadyEventData { id: string; username: string; bot: boolean; - public_flags: bigint; + public_flags: string; }[]; } diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts
index f16921bd..508a8901 100644 --- a/util/src/util/Config.ts +++ b/util/src/util/Config.ts
@@ -7,7 +7,8 @@ var config: ConfigEntity; export const Config = { init: async function init() { if (config) return config; - config = new ConfigEntity({}, { id: "0" }); + config = (await ConfigEntity.findOne({ id: "0" })) || new ConfigEntity({}, { id: "0" }); + return this.set((config.value || {}).merge(DefaultConfigOptions)); }, get: function get() { diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts
index 5082d8ea..ab6aa795 100644 --- a/util/src/util/Permissions.ts +++ b/util/src/util/Permissions.ts
@@ -212,18 +212,15 @@ export async function getPermission(user_id?: string, guild_id?: string, channel var guild: Guild | undefined; if (channel_id) { - channel = await Channel.findOneOrFail( - { id: channel_id }, - { select: ["permission_overwrites", "recipients", "owner", "guild"] } - ); + channel = await Channel.findOneOrFail({ id: channel_id }); if (channel.guild_id) guild_id = channel.guild_id; // derive guild_id from the channel } if (guild_id) { - guild = await Guild.findOneOrFail({ id: guild_id }, { select: ["owner"] }); + guild = await Guild.findOneOrFail({ id: guild_id }); if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR); - member = await Member.findOneOrFail({ guild_id, id: user_id }, { select: ["roles"] }); + member = await Member.findOneOrFail({ where: { guild_id, id: user_id }, relations: ["roles"] }); } var permission = Permissions.finalPermission({