diff --git a/util/src/entities/BackupCodes.ts b/util/src/entities/BackupCodes.ts
new file mode 100644
index 00000000..d532a39a
--- /dev/null
+++ b/util/src/entities/BackupCodes.ts
@@ -0,0 +1,35 @@
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import { BaseClass } from "./BaseClass";
+import { User } from "./User";
+import crypto from "crypto";
+
+@Entity("backup_codes")
+export class BackupCode extends BaseClass {
+ @JoinColumn({ name: "user_id" })
+ @ManyToOne(() => User, { onDelete: "CASCADE" })
+ user: User;
+
+ @Column()
+ code: string;
+
+ @Column()
+ consumed: boolean;
+
+ @Column()
+ expired: boolean;
+}
+
+export function generateMfaBackupCodes(user_id: string) {
+ let backup_codes: BackupCode[] = [];
+ for (let i = 0; i < 10; i++) {
+ const code = BackupCode.create({
+ user: { id: user_id },
+ code: crypto.randomBytes(4).toString("hex"), // 8 characters
+ consumed: false,
+ expired: false,
+ });
+ backup_codes.push(code);
+ }
+
+ return backup_codes;
+}
\ No newline at end of file
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index 69c08be7..3e8cd5ef 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -3,7 +3,7 @@ import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { PublicUserProjection, User } from "./User";
import { HTTPError } from "lambert-server";
-import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial, InvisibleCharacters } from "../util";
+import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial, InvisibleCharacters, ChannelTypes } from "../util";
import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
import { Recipient } from "./Recipient";
import { Message } from "./Message";
@@ -149,7 +149,7 @@ export class Channel extends BaseClass {
orphanedRowAction: "delete",
})
webhooks?: Webhook[];
-
+
// TODO: DM channel
static async createChannel(
channel: Partial<Channel>,
@@ -175,12 +175,20 @@ export class Channel extends BaseClass {
if (channel.name.includes(character))
throw new HTTPError("Channel name cannot include invalid characters", 403);
- if (channel.name.match(/\-\-+/g))
- throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403)
+ // Categories skip these checks on discord.com
+ if (channel.type !== ChannelType.GUILD_CATEGORY) {
+ if (channel.name.includes(" "))
+ throw new HTTPError("Channel name cannot include invalid characters", 403);
+
+ if (channel.name.match(/\-\-+/g))
+ throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403);
- if (channel.name.charAt(0) === "-" ||
- channel.name.charAt(channel.name.length - 1) === "-")
- throw new HTTPError("Channel name cannot start/end with dash.", 403)
+ if (channel.name.charAt(0) === "-" ||
+ channel.name.charAt(channel.name.length - 1) === "-")
+ throw new HTTPError("Channel name cannot start/end with dash.", 403);
+ }
+ else
+ channel.name = channel.name.trim(); //category names are trimmed client side on discord.com
}
if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) {
@@ -237,6 +245,7 @@ export class Channel extends BaseClass {
static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) {
recipients = recipients.unique().filter((x) => x !== creator_user_id);
+ //@ts-ignore some typeorm typescript issue
const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) });
// TODO: check config for max number of recipients
@@ -246,7 +255,7 @@ export class Channel extends BaseClass {
}
**/
- const type = recipients.length > 1 ? ChannelType.DM : ChannelType.GROUP_DM;
+ const type = recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.DM;
let channel = null;
@@ -299,7 +308,7 @@ export class Channel extends BaseClass {
await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id });
}
- if (recipients.length === 1) return channel_dto;
+ if (recipients.length === 1) return channel_dto;
else return channel_dto.excludedRecipients([creator_user_id]);
}
diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts
index 3756d686..e545e36a 100644
--- a/util/src/entities/Config.ts
+++ b/util/src/entities/Config.ts
@@ -4,6 +4,7 @@ import crypto from "crypto";
import { Snowflake } from "../util/Snowflake";
import { SessionsReplace } from "..";
import { hostname } from "os";
+import { Rights } from "../util/Rights";
@Entity("config")
export class ConfigEntity extends BaseClassWithoutId {
@@ -49,6 +50,8 @@ export interface ConfigValue {
endpointClient: string | null;
endpointPublic: string | null;
endpointPrivate: string | null;
+ resizeHeightMax: number | null;
+ resizeWidthMax: number | null;
};
api: {
defaultVersion: string;
@@ -121,6 +124,7 @@ export interface ConfigValue {
secret: string | null;
};
ipdataApiKey: string | null;
+ defaultRights: string;
};
login: {
requireCaptcha: boolean;
@@ -169,6 +173,7 @@ export interface ConfigValue {
guilds: string[];
canLeave: boolean;
};
+ defaultFeatures: string[];
};
gif: {
enabled: boolean;
@@ -192,7 +197,7 @@ export interface ConfigValue {
releases: {
useLocalRelease: Boolean; //TODO
upstreamVersion: string;
- }
+ };
},
metrics: {
timeout: number;
@@ -202,7 +207,7 @@ export interface ConfigValue {
endpoint: string;
traceSampleRate: number;
environment: string;
- }
+ };
}
export const DefaultConfigOptions: ConfigValue = {
@@ -215,6 +220,8 @@ export const DefaultConfigOptions: ConfigValue = {
endpointClient: null,
endpointPrivate: null,
endpointPublic: null,
+ resizeHeightMax: 1000,
+ resizeWidthMax: 1000,
},
api: {
defaultVersion: "9",
@@ -312,6 +319,34 @@ export const DefaultConfigOptions: ConfigValue = {
secret: null,
},
ipdataApiKey: "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9",
+ defaultRights: (
+ Rights.FLAGS.CREATE_CHANNELS +
+ Rights.FLAGS.CREATE_DMS +
+ Rights.FLAGS.CREATE_DM_GROUPS +
+ Rights.FLAGS.CREATE_GUILDS +
+ Rights.FLAGS.CREATE_INVITES +
+ Rights.FLAGS.CREATE_ROLES +
+ Rights.FLAGS.CREATE_TEMPLATES +
+ Rights.FLAGS.CREATE_WEBHOOKS +
+ Rights.FLAGS.JOIN_GUILDS +
+ Rights.FLAGS.PIN_MESSAGES +
+ Rights.FLAGS.SELF_ADD_REACTIONS +
+ Rights.FLAGS.SELF_DELETE_MESSAGES +
+ Rights.FLAGS.SELF_EDIT_MESSAGES +
+ Rights.FLAGS.SELF_EDIT_NAME +
+ Rights.FLAGS.SEND_MESSAGES +
+ Rights.FLAGS.USE_ACTIVITIES +
+ Rights.FLAGS.USE_VIDEO +
+ Rights.FLAGS.USE_VOICE +
+ Rights.FLAGS.INVITE_USERS +
+ Rights.FLAGS.SELF_DELETE_DISABLE +
+ Rights.FLAGS.DEBTABLE +
+ Rights.FLAGS.KICK_BAN_MEMBERS +
+ Rights.FLAGS.SELF_LEAVE_GROUPS +
+ Rights.FLAGS.SELF_ADD_DISCOVERABLE +
+ Rights.FLAGS.USE_ACHIEVEMENTS +
+ Rights.FLAGS.USE_MASS_INVITES
+ ).toString()
},
login: {
requireCaptcha: false,
@@ -370,6 +405,7 @@ export const DefaultConfigOptions: ConfigValue = {
canLeave: true,
guilds: [],
},
+ defaultFeatures: [],
},
gif: {
enabled: true,
diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index 70bb41c5..a5b732e8 100644
--- a/util/src/entities/Guild.ts
+++ b/util/src/entities/Guild.ts
@@ -248,7 +248,7 @@ export class Guild extends BaseClass {
welcome_channels: {
description: string;
emoji_id?: string;
- emoji_name: string;
+ emoji_name?: string;
channel_id: string;
}[];
};
@@ -293,7 +293,7 @@ export class Guild extends BaseClass {
afk_timeout: 300,
default_message_notifications: 1, // defaults effect: setting the push default at mentions-only will save a lot
explicit_content_filter: 0,
- features: [],
+ features: Config.get().guild.defaultFeatures,
primary_category_id: null,
id: guild_id,
max_members: 250000,
diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts
index e18cf691..013e92a9 100644
--- a/util/src/entities/Message.ts
+++ b/util/src/entities/Message.ts
@@ -114,7 +114,7 @@ export class Message extends BaseClass {
@ManyToOne(() => Application)
application?: Application;
- @Column({ nullable: true })
+ @Column({ nullable: true, type: "longtext" })
content?: string;
@Column()
diff --git a/util/src/entities/Note.ts b/util/src/entities/Note.ts
new file mode 100644
index 00000000..36017c5e
--- /dev/null
+++ b/util/src/entities/Note.ts
@@ -0,0 +1,18 @@
+import { Column, Entity, JoinColumn, ManyToOne, Unique } from "typeorm";
+import { BaseClass } from "./BaseClass";
+import { User } from "./User";
+
+@Entity("notes")
+@Unique(["owner", "target"])
+export class Note extends BaseClass {
+ @JoinColumn({ name: "owner_id" })
+ @ManyToOne(() => User, { onDelete: "CASCADE" })
+ owner: User;
+
+ @JoinColumn({ name: "target_id" })
+ @ManyToOne(() => User, { onDelete: "CASCADE" })
+ target: User;
+
+ @Column()
+ content: string;
+}
\ No newline at end of file
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index 9b1c494e..a8f7f0c3 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -1,10 +1,11 @@
-import { Column, Entity, FindOneOptions, JoinColumn, ManyToMany, OneToMany, RelationId } from "typeorm";
+import { Column, Entity, FindOneOptions, JoinColumn, OneToMany } from "typeorm";
import { BaseClass } from "./BaseClass";
import { BitField } from "../util/BitField";
import { Relationship } from "./Relationship";
import { ConnectedAccount } from "./ConnectedAccount";
import { Config, FieldErrors, Snowflake, trimSpecial } from "..";
import { Member, Session } from ".";
+import { Note } from "./Note";
export enum PublicUserEnum {
username,
@@ -108,6 +109,12 @@ export class User extends BaseClass {
@Column({ select: false })
mfa_enabled: boolean; // if multi factor authentication is enabled
+ @Column({ select: false, nullable: true })
+ totp_secret?: string;
+
+ @Column({ nullable: true, select: false })
+ totp_last_ticket?: string;
+
@Column()
created_at: Date; // registration date
@@ -168,9 +175,6 @@ export class User extends BaseClass {
@Column({ type: "simple-json", select: false })
extended_settings: string;
- @Column({ type: "simple-json" })
- notes: { [key: string]: string }; //key is ID of user
-
toPublicUser() {
const user: any = {};
PublicUserProjection.forEach((x) => {
@@ -268,7 +272,7 @@ export class User extends BaseClass {
disabled: false,
deleted: false,
email: email,
- rights: "0", // TODO: grant rights correctly, as 0 actually stands for no rights at all
+ rights: Config.get().security.defaultRights,
nsfw_allowed: true, // TODO: depending on age
public_flags: "0",
flags: "0", // TODO: generate
@@ -318,7 +322,7 @@ export const defaultSettings: UserSettings = {
inline_attachment_media: true,
inline_embed_media: true,
locale: "en-US",
- message_display_compact: true,
+ message_display_compact: false,
native_phone_integration_enabled: true,
render_embeds: true,
render_reactions: true,
diff --git a/util/src/entities/index.ts b/util/src/entities/index.ts
index f023d5a6..c439a4b7 100644
--- a/util/src/entities/index.ts
+++ b/util/src/entities/index.ts
@@ -27,4 +27,6 @@ export * from "./Template";
export * from "./User";
export * from "./VoiceState";
export * from "./Webhook";
-export * from "./ClientRelease";
\ No newline at end of file
+export * from "./ClientRelease";
+export * from "./BackupCodes";
+export * from "./Note";
diff --git a/util/src/util/Constants.ts b/util/src/util/Constants.ts
index a5d3fcd2..81a7165d 100644
--- a/util/src/util/Constants.ts
+++ b/util/src/util/Constants.ts
@@ -73,9 +73,13 @@ export const VoiceOPCodes = {
HEARTBEAT: 3,
SESSION_DESCRIPTION: 4,
SPEAKING: 5,
+ HEARTBEAT_ACK: 6,
+ RESUME: 7,
HELLO: 8,
- CLIENT_CONNECT: 12,
- CLIENT_DISCONNECT: 13,
+ RESUMED: 9,
+ CLIENT_CONNECT: 12, // incorrect, op 12 is probably used for video
+ CLIENT_DISCONNECT: 13, // incorrect
+ VERSION: 16, //not documented
};
export const Events = {
diff --git a/util/src/util/Event.ts b/util/src/util/Event.ts
index bb624051..20a638a0 100644
--- a/util/src/util/Event.ts
+++ b/util/src/util/Event.ts
@@ -62,6 +62,7 @@ export async function listenEvent(event: string, callback: (event: EventOpts) =>
msg.type === "event" && msg.id === event && callback({ ...msg.event, cancel });
};
+ //@ts-ignore apparently theres no function addListener with this signature
process.addListener("message", listener);
process.setMaxListeners(process.getMaxListeners() + 1);
diff --git a/util/src/util/InvisibleCharacters.ts b/util/src/util/InvisibleCharacters.ts
index 2b014e14..a48cfab0 100644
--- a/util/src/util/InvisibleCharacters.ts
+++ b/util/src/util/InvisibleCharacters.ts
@@ -1,7 +1,7 @@
// List from https://invisible-characters.com/
export const InvisibleCharacters = [
'\u{9}', //Tab
- '\u{20}', //Space
+ //'\u{20}', //Space //categories can have spaces in them
'\u{ad}', //Soft hyphen
'\u{34f}', //Combining grapheme joiner
'\u{61c}', //Arabic letter mark
|