summary refs log tree commit diff
path: root/util/src/entities/User.ts
diff options
context:
space:
mode:
Diffstat (limited to 'util/src/entities/User.ts')
-rw-r--r--util/src/entities/User.ts437
1 files changed, 0 insertions, 437 deletions
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
deleted file mode 100644

index 35aeea52..00000000 --- a/util/src/entities/User.ts +++ /dev/null
@@ -1,437 +0,0 @@ -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, BannedWords, adjustEmail } from ".."; -import { Member, Session } from "."; - -export enum PublicUserEnum { - username, - discriminator, - id, - public_flags, - avatar, - accent_color, - banner, - bio, - bot, - premium_since, -} -export type PublicUserKeys = keyof typeof PublicUserEnum; - -export enum PrivateUserEnum { - flags, - mfa_enabled, - email, - phone, - verified, - nsfw_allowed, - premium, - premium_type, - purchased_flags, - premium_usage_flags, - disabled, - settings, - // locale -} -export type PrivateUserKeys = keyof typeof PrivateUserEnum | PublicUserKeys; - -export const PublicUserProjection = Object.values(PublicUserEnum).filter( - (x) => typeof x === "string" -) as PublicUserKeys[]; -export const PrivateUserProjection = [ - ...PublicUserProjection, - ...Object.values(PrivateUserEnum).filter((x) => typeof x === "string"), -] as PrivateUserKeys[]; - -// Private user data that should never get sent to the client -export type PublicUser = Pick<User, PublicUserKeys>; - -export interface UserPublic extends Pick<User, PublicUserKeys> { } - -export interface UserPrivate extends Pick<User, PrivateUserKeys> { - locale: string; -} - -@Entity("users") -export class User extends BaseClass { - @Column() - username: string; // username max length 32, min 2 (should be configurable) - - setUsername(val: string) { - if (BannedWords.find(val)) throw FieldErrors({ username: { message: "Bad username", code: "INVALID_USERNAME" } }); - this.username = val; - } - - @Column() - discriminator: string; // opaque string: 4 digits on discord.com - - setDiscriminator(val: string) { - const number = Number(val); - if (val.length > 4) throw new Error("invalid discriminator"); - if (isNaN(number)) throw new Error("invalid discriminator"); - if (number <= 0 || number >= 10000) throw new Error("discriminator must be between 1 and 9999"); - val = Number(val).toString(); - this.discriminator = val.toString().padStart(4, "0"); - } - - @Column({ nullable: true }) - avatar?: string; // hash of the user avatar - - @Column({ nullable: true }) - accent_color?: number; // banner color of user - - @Column({ nullable: true }) - banner?: string; // hash of the user banner - - @Column({ nullable: true, select: false }) - phone?: string; // phone number of the user - - @Column({ select: false }) - desktop: boolean; // if the user has desktop app installed - - @Column({ select: false }) - mobile: boolean; // if the user has mobile app installed - - @Column() - premium: boolean; // if user bought individual premium - - @Column() - premium_type: number; // individual premium level - - @Column() - bot: boolean; // if user is bot - - @Column() - bio: string; // short description of the user (max 190 chars -> should be configurable) - - @Column() - system: boolean; // shouldn't be used, the api sends this field type true, if the generated message comes from a system generated author - - @Column({ select: false }) - nsfw_allowed: boolean; // if the user can do age-restricted actions (NSFW channels/guilds/commands) - - @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 - - @Column({ nullable: true }) - premium_since: Date; // premium date - - @Column({ select: false }) - verified: boolean; // if the user is offically verified - - @Column() - disabled: boolean; // if the account is disabled - - @Column() - deleted: boolean; // if the user was deleted - - @Column({ nullable: true, select: false }) - email?: string; // email of the user - - setEmail(val?: string) { - val = adjustEmail(val); - if (!val) throw FieldErrors({ email: { message: "Invalid email", code: "EMAIL_INVALID" } }); - if (!val.match(/([a-z\d.-]{3,})@([a-z\d.-]+).([a-z]{2,})/g)) throw FieldErrors({ email: { message: "Invalid email", code: "EMAIL_INVALID" } }); - this.email = val; - } - - @Column() - flags: string; // UserFlags - - @Column() - public_flags: number; - - @Column() - purchased_flags: number; - - @Column() - premium_usage_flags: number; - - @Column({ type: "bigint" }) - rights: string; // Rights - - @OneToMany(() => Session, (session: Session) => session.user) - sessions: Session[]; - - @JoinColumn({ name: "relationship_ids" }) - @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, { - cascade: true, - orphanedRowAction: "delete", - }) - relationships: Relationship[]; - - @JoinColumn({ name: "connected_account_ids" }) - @OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user, { - cascade: true, - orphanedRowAction: "delete", - }) - connected_accounts: ConnectedAccount[]; - - @Column({ type: "simple-json", select: false }) - data: { - valid_tokens_since: Date; // all tokens with a previous issue date are invalid - hash?: string; // hash of the password, salt is saved in password (bcrypt) - }; - - @Column({ type: "simple-array", select: false }) - fingerprints: string[]; // array of fingerprints -> used to prevent multiple accounts - - @Column({ type: "simple-json", select: false }) - settings: UserSettings; - - // workaround to prevent fossord-unaware clients from deleting settings not used by them - @Column({ type: "simple-json", select: false }) - extended_settings: string; - - toPublicUser() { - const user: any = {}; - PublicUserProjection.forEach((x) => { - user[x] = this[x]; - }); - return user as PublicUser; - } - - static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) { - return await User.findOneOrFail( - { id: user_id }, - { - ...opts, - select: [...PublicUserProjection, ...(opts?.select || [])], - } - ); - } - - private static async generateDiscriminator(username: string): Promise<string | undefined> { - if (Config.get().register.incrementingDiscriminators) { - // discriminator will be incrementally generated - - // First we need to figure out the currently highest discrimnator for the given username and then increment it - const users = await User.find({ where: { username }, select: ["discriminator"] }); - const highestDiscriminator = Math.max(0, ...users.map((u) => Number(u.discriminator))); - - const discriminator = highestDiscriminator + 1; - if (discriminator >= 10000) { - return undefined; - } - - return discriminator.toString().padStart(4, "0"); - } else { - // discriminator will be randomly generated - - // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists - // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the database? - for (let tries = 0; tries < 5; tries++) { - const discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); - const exists = await User.findOne({ where: { discriminator, username: username }, select: ["id"] }); - if (!exists) return discriminator; - } - - return undefined; - } - } - - static async register({ - email, - username, - password, - date_of_birth, - req, - }: { - username: string; - password?: string; - email?: string; - date_of_birth?: Date; // "2000-04-03" - req?: any; - }) { - // trim special uf8 control characters -> Backspace, Newline, ... - username = trimSpecial(username); - - const discriminator = await User.generateDiscriminator(username); - if (!discriminator) { - // We've failed to generate a valid and unused discriminator - throw FieldErrors({ - username: { - code: "USERNAME_TOO_MANY_USERS", - message: req.t("auth:register.USERNAME_TOO_MANY_USERS"), - }, - }); - } - - // TODO: save date_of_birth - // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed - // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false - const language = req.language === "en" ? "en-US" : req.language || "en-US"; - - const user = new User({ - created_at: new Date(), - username: username, - discriminator, - id: Snowflake.generate(), - bot: false, - system: false, - premium_since: new Date(), - desktop: false, - mobile: false, - premium: true, - premium_type: 2, - bio: "", - mfa_enabled: false, - verified: true, - disabled: false, - deleted: false, - email: email, - rights: Config.get().security.defaultRights, - nsfw_allowed: true, // TODO: depending on age - public_flags: "0", - flags: "0", // TODO: generate - data: { - hash: password, - valid_tokens_since: new Date(), - }, - settings: { ...defaultSettings, locale: language }, - purchased_flags: 5, // TODO: idk what the values for this are - premium_usage_flags: 2, // TODO: idk what the values for this are - extended_settings: {}, - fingerprints: [], - notes: {}, - }); - - await user.save(); - - setImmediate(async () => { - if (Config.get().guild.autoJoin.enabled) { - for (const guild of Config.get().guild.autoJoin.guilds || []) { - await Member.addToGuild(user.id, guild).catch((e) => { }); - } - } - }); - - return user; - } -} - -export const defaultSettings: UserSettings = { - afk_timeout: 3600, - allow_accessibility_detection: true, - animate_emoji: true, - animate_stickers: 0, - contact_sync_enabled: false, - convert_emoticons: false, - custom_status: null, - default_guilds_restricted: false, - detect_platform_accounts: false, - developer_mode: true, - disable_games_tab: true, - enable_tts_command: false, - explicit_content_filter: 0, - friend_source_flags: { all: true }, - gateway_connected: false, - gif_auto_play: true, - guild_folders: [], - guild_positions: [], - inline_attachment_media: true, - inline_embed_media: true, - locale: "en-US", - message_display_compact: false, - native_phone_integration_enabled: true, - render_embeds: true, - render_reactions: true, - restricted_guilds: [], - show_current_game: true, - status: "online", - stream_notifications_enabled: false, - theme: "dark", - timezone_offset: 0, // TODO: timezone from request - - banner_color: null, - friend_discovery_flags: 0, - view_nsfw_guilds: true, - passwordless: false, -}; - -export interface UserSettings { - afk_timeout: number; - allow_accessibility_detection: boolean; - animate_emoji: boolean; - animate_stickers: number; - contact_sync_enabled: boolean; - convert_emoticons: boolean; - custom_status: { - emoji_id?: string; - emoji_name?: string; - expires_at?: number; - text?: string; - } | null; - default_guilds_restricted: boolean; - detect_platform_accounts: boolean; - developer_mode: boolean; - disable_games_tab: boolean; - enable_tts_command: boolean; - explicit_content_filter: number; - friend_source_flags: { all: boolean; }; - gateway_connected: boolean; - gif_auto_play: boolean; - // every top guild is displayed as a "folder" - guild_folders: { - color: number; - guild_ids: string[]; - id: number; - name: string; - }[]; - guild_positions: string[]; // guild ids ordered by position - inline_attachment_media: boolean; - inline_embed_media: boolean; - locale: string; // en_US - message_display_compact: boolean; - native_phone_integration_enabled: boolean; - render_embeds: boolean; - render_reactions: boolean; - restricted_guilds: string[]; - show_current_game: boolean; - status: "online" | "offline" | "dnd" | "idle" | "invisible"; - stream_notifications_enabled: boolean; - theme: "dark" | "white"; // dark - timezone_offset: number; // e.g -60 - banner_color: string | null; - friend_discovery_flags: number; - view_nsfw_guilds: boolean; - passwordless: boolean; -} - -export const CUSTOM_USER_FLAG_OFFSET = BigInt(1) << BigInt(32); - -export class UserFlags extends BitField { - static FLAGS = { - DISCORD_EMPLOYEE: BigInt(1) << BigInt(0), - PARTNERED_SERVER_OWNER: BigInt(1) << BigInt(1), - HYPESQUAD_EVENTS: BigInt(1) << BigInt(2), - BUGHUNTER_LEVEL_1: BigInt(1) << BigInt(3), - MFA_SMS: BigInt(1) << BigInt(4), - PREMIUM_PROMO_DISMISSED: BigInt(1) << BigInt(5), - HOUSE_BRAVERY: BigInt(1) << BigInt(6), - HOUSE_BRILLIANCE: BigInt(1) << BigInt(7), - HOUSE_BALANCE: BigInt(1) << BigInt(8), - EARLY_SUPPORTER: BigInt(1) << BigInt(9), - TEAM_USER: BigInt(1) << BigInt(10), - TRUST_AND_SAFETY: BigInt(1) << BigInt(11), - SYSTEM: BigInt(1) << BigInt(12), - HAS_UNREAD_URGENT_MESSAGES: BigInt(1) << BigInt(13), - BUGHUNTER_LEVEL_2: BigInt(1) << BigInt(14), - UNDERAGE_DELETED: BigInt(1) << BigInt(15), - VERIFIED_BOT: BigInt(1) << BigInt(16), - EARLY_VERIFIED_BOT_DEVELOPER: BigInt(1) << BigInt(17), - CERTIFIED_MODERATOR: BigInt(1) << BigInt(18), - BOT_HTTP_INTERACTIONS: BigInt(1) << BigInt(19), - }; -}