diff options
Diffstat (limited to 'src/api/routes/users')
-rw-r--r-- | src/api/routes/users/#id/profile.ts | 150 | ||||
-rw-r--r-- | src/api/routes/users/#id/relationships.ts | 63 | ||||
-rw-r--r-- | src/api/routes/users/@me/channels.ts | 33 | ||||
-rw-r--r-- | src/api/routes/users/@me/delete.ts | 10 | ||||
-rw-r--r-- | src/api/routes/users/@me/disable.ts | 10 | ||||
-rw-r--r-- | src/api/routes/users/@me/email-settings.ts | 4 | ||||
-rw-r--r-- | src/api/routes/users/@me/guilds.ts | 39 | ||||
-rw-r--r-- | src/api/routes/users/@me/guilds/#guild_id/settings.ts | 42 | ||||
-rw-r--r-- | src/api/routes/users/@me/index.ts | 187 | ||||
-rw-r--r-- | src/api/routes/users/@me/mfa/codes-verification.ts | 72 | ||||
-rw-r--r-- | src/api/routes/users/@me/mfa/codes.ts | 83 | ||||
-rw-r--r-- | src/api/routes/users/@me/mfa/totp/disable.ts | 81 | ||||
-rw-r--r-- | src/api/routes/users/@me/mfa/totp/enable.ts | 90 | ||||
-rw-r--r-- | src/api/routes/users/@me/notes.ts | 34 | ||||
-rw-r--r-- | src/api/routes/users/@me/relationships.ts | 175 | ||||
-rw-r--r-- | src/api/routes/users/@me/settings.ts | 26 |
16 files changed, 697 insertions, 402 deletions
diff --git a/src/api/routes/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts index de96422a..ebea805b 100644 --- a/src/api/routes/users/#id/profile.ts +++ b/src/api/routes/users/#id/profile.ts @@ -1,5 +1,12 @@ import { Router, Request, Response } from "express"; -import { PublicConnectedAccount, PublicUser, User, UserPublic, Member, Guild } from "@fosscord/util"; +import { + PublicConnectedAccount, + PublicUser, + User, + UserPublic, + Member, + Guild, +} from "@fosscord/util"; import { route } from "@fosscord/api"; const router: Router = Router(); @@ -11,81 +18,102 @@ export interface UserProfileResponse { premium_since?: Date; } -router.get("/", route({ test: { response: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => { - if (req.params.id === "@me") req.params.id = req.user_id; +router.get( + "/", + route({ test: { response: { body: "UserProfileResponse" } } }), + async (req: Request, res: Response) => { + if (req.params.id === "@me") req.params.id = req.user_id; - const { guild_id, with_mutual_guilds } = req.query; + const { guild_id, with_mutual_guilds } = req.query; - const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] }); + const user = await User.getPublicUser(req.params.id, { + relations: ["connected_accounts"], + }); - var mutual_guilds: object[] = []; - var premium_guild_since; + var mutual_guilds: object[] = []; + var premium_guild_since; - if (with_mutual_guilds == "true") { - const requested_member = await Member.find({ where: { id: req.params.id } }); - const self_member = await Member.find({ where: { id: req.user_id } }); + if (with_mutual_guilds == "true") { + const requested_member = await Member.find({ + where: { id: req.params.id }, + }); + const self_member = await Member.find({ + where: { id: req.user_id }, + }); - for (const rmem of requested_member) { - if (rmem.premium_since) { - if (premium_guild_since) { - if (premium_guild_since > rmem.premium_since) { + for (const rmem of requested_member) { + if (rmem.premium_since) { + if (premium_guild_since) { + if (premium_guild_since > rmem.premium_since) { + premium_guild_since = rmem.premium_since; + } + } else { premium_guild_since = rmem.premium_since; } - } else { - premium_guild_since = rmem.premium_since; } - } - for (const smem of self_member) { - if (smem.guild_id === rmem.guild_id) { - mutual_guilds.push({ id: rmem.guild_id, nick: rmem.nick }); + for (const smem of self_member) { + if (smem.guild_id === rmem.guild_id) { + mutual_guilds.push({ + id: rmem.guild_id, + nick: rmem.nick, + }); + } } } } - } - const guild_member = guild_id && typeof guild_id == "string" - ? await Member.findOneOrFail({ where: { id: req.params.id, guild_id: guild_id }, relations: ["roles"] }) - : undefined; + const guild_member = + guild_id && typeof guild_id == "string" + ? await Member.findOneOrFail({ + where: { id: req.params.id, guild_id: guild_id }, + relations: ["roles"], + }) + : undefined; - // TODO: make proper DTO's in util? + // TODO: make proper DTO's in util? - const userDto = { - username: user.username, - discriminator: user.discriminator, - id: user.id, - public_flags: user.public_flags, - avatar: user.avatar, - accent_color: user.accent_color, - banner: user.banner, - bio: req.user_bot ? null : user.bio, - bot: user.bot - }; + const userDto = { + username: user.username, + discriminator: user.discriminator, + id: user.id, + public_flags: user.public_flags, + avatar: user.avatar, + accent_color: user.accent_color, + banner: user.banner, + bio: req.user_bot ? null : user.bio, + bot: user.bot, + }; - const guildMemberDto = guild_member ? { - avatar: user.avatar, // TODO - banner: user.banner, // TODO - bio: req.user_bot ? null : user.bio, // TODO - communication_disabled_until: null, // TODO - deaf: guild_member.deaf, - flags: user.flags, - is_pending: guild_member.pending, - pending: guild_member.pending, // why is this here twice, discord? - joined_at: guild_member.joined_at, - mute: guild_member.mute, - nick: guild_member.nick, - premium_since: guild_member.premium_since, - roles: guild_member.roles.map(x => x.id).filter(id => id != guild_id), - user: userDto - } : undefined; + const guildMemberDto = guild_member + ? { + avatar: user.avatar, // TODO + banner: user.banner, // TODO + bio: req.user_bot ? null : user.bio, // TODO + communication_disabled_until: null, // TODO + deaf: guild_member.deaf, + flags: user.flags, + is_pending: guild_member.pending, + pending: guild_member.pending, // why is this here twice, discord? + joined_at: guild_member.joined_at, + mute: guild_member.mute, + nick: guild_member.nick, + premium_since: guild_member.premium_since, + roles: guild_member.roles + .map((x) => x.id) + .filter((id) => id != guild_id), + user: userDto, + } + : undefined; - res.json({ - connected_accounts: user.connected_accounts, - premium_guild_since: premium_guild_since, // TODO - premium_since: user.premium_since, // TODO - mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true - user: userDto, - guild_member: guildMemberDto, - }); -}); + res.json({ + connected_accounts: user.connected_accounts, + premium_guild_since: premium_guild_since, // TODO + premium_since: user.premium_since, // TODO + mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true + user: userDto, + guild_member: guildMemberDto, + }); + }, +); export default router; diff --git a/src/api/routes/users/#id/relationships.ts b/src/api/routes/users/#id/relationships.ts index de7cb9d3..c6480567 100644 --- a/src/api/routes/users/#id/relationships.ts +++ b/src/api/routes/users/#id/relationships.ts @@ -6,36 +6,49 @@ const router: Router = Router(); export interface UserRelationsResponse { object: { - id?: string, - username?: string, - avatar?: string, - discriminator?: string, - public_flags?: number - } + id?: string; + username?: string; + avatar?: string; + discriminator?: string; + public_flags?: number; + }; } +router.get( + "/", + route({ test: { response: { body: "UserRelationsResponse" } } }), + async (req: Request, res: Response) => { + var mutual_relations: object[] = []; + const requested_relations = await User.findOneOrFail({ + where: { id: req.params.id }, + relations: ["relationships"], + }); + const self_relations = await User.findOneOrFail({ + where: { id: req.user_id }, + relations: ["relationships"], + }); -router.get("/", route({ test: { response: { body: "UserRelationsResponse" } } }), async (req: Request, res: Response) => { - var mutual_relations: object[] = []; - const requested_relations = await User.findOneOrFail({ - where: { id: req.params.id }, - relations: ["relationships"] - }); - const self_relations = await User.findOneOrFail({ - where: { id: req.user_id }, - relations: ["relationships"] - }); - - for(const rmem of requested_relations.relationships) { - for(const smem of self_relations.relationships) - if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) { - var relation_user = await User.getPublicUser(rmem.to_id) + for (const rmem of requested_relations.relationships) { + for (const smem of self_relations.relationships) + if ( + rmem.to_id === smem.to_id && + rmem.type === 1 && + rmem.to_id !== req.user_id + ) { + var relation_user = await User.getPublicUser(rmem.to_id); - mutual_relations.push({id: relation_user.id, username: relation_user.username, avatar: relation_user.avatar, discriminator: relation_user.discriminator, public_flags: relation_user.public_flags}) + mutual_relations.push({ + id: relation_user.id, + username: relation_user.username, + avatar: relation_user.avatar, + discriminator: relation_user.discriminator, + public_flags: relation_user.public_flags, + }); + } } - } - res.json(mutual_relations) -}); + res.json(mutual_relations); + }, +); export default router; diff --git a/src/api/routes/users/@me/channels.ts b/src/api/routes/users/@me/channels.ts index ad483529..237be102 100644 --- a/src/api/routes/users/@me/channels.ts +++ b/src/api/routes/users/@me/channels.ts @@ -1,5 +1,10 @@ import { Request, Response, Router } from "express"; -import { Recipient, DmChannelDTO, Channel, DmChannelCreateSchema } from "@fosscord/util"; +import { + Recipient, + DmChannelDTO, + Channel, + DmChannelCreateSchema, +} from "@fosscord/util"; import { route } from "@fosscord/api"; const router: Router = Router(); @@ -7,14 +12,28 @@ const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const recipients = await Recipient.find({ where: { user_id: req.user_id, closed: false }, - relations: ["channel", "channel.recipients"] + relations: ["channel", "channel.recipients"], }); - res.json(await Promise.all(recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id])))); + res.json( + await Promise.all( + recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id])), + ), + ); }); -router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => { - const body = req.body as DmChannelCreateSchema; - res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name)); -}); +router.post( + "/", + route({ body: "DmChannelCreateSchema" }), + async (req: Request, res: Response) => { + const body = req.body as DmChannelCreateSchema; + res.json( + await Channel.createDMChannel( + body.recipients, + req.user_id, + body.name, + ), + ); + }, +); export default router; diff --git a/src/api/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts index c24c3f1e..a9f8167c 100644 --- a/src/api/routes/users/@me/delete.ts +++ b/src/api/routes/users/@me/delete.ts @@ -7,7 +7,10 @@ import { HTTPError } from "lambert-server"; const router = Router(); router.post("/", route({}), async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] }); //User object + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); //User object let correctpass = true; if (user.data.hash) { @@ -21,7 +24,10 @@ router.post("/", route({}), async (req: Request, res: Response) => { // TODO: decrement guild member count if (correctpass) { - await Promise.all([User.delete({ id: req.user_id }), Member.delete({ id: req.user_id })]); + await Promise.all([ + User.delete({ id: req.user_id }), + Member.delete({ id: req.user_id }), + ]); res.sendStatus(204); } else { diff --git a/src/api/routes/users/@me/disable.ts b/src/api/routes/users/@me/disable.ts index 4aff3774..313a888f 100644 --- a/src/api/routes/users/@me/disable.ts +++ b/src/api/routes/users/@me/disable.ts @@ -6,7 +6,10 @@ import bcrypt from "bcrypt"; const router = Router(); router.post("/", route({}), async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] }); //User object + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); //User object let correctpass = true; if (user.data.hash) { @@ -19,7 +22,10 @@ router.post("/", route({}), async (req: Request, res: Response) => { res.sendStatus(204); } else { - res.status(400).json({ message: "Password does not match", code: 50018 }); + res.status(400).json({ + message: "Password does not match", + code: 50018, + }); } }); diff --git a/src/api/routes/users/@me/email-settings.ts b/src/api/routes/users/@me/email-settings.ts index 3114984e..a2834b89 100644 --- a/src/api/routes/users/@me/email-settings.ts +++ b/src/api/routes/users/@me/email-settings.ts @@ -11,9 +11,9 @@ router.get("/", route({}), (req: Request, res: Response) => { communication: true, tips: false, updates_and_announcements: false, - recommendations_and_events: false + recommendations_and_events: false, }, - initialized: false + initialized: false, }).status(200); }); diff --git a/src/api/routes/users/@me/guilds.ts b/src/api/routes/users/@me/guilds.ts index 754a240e..e12bf258 100644 --- a/src/api/routes/users/@me/guilds.ts +++ b/src/api/routes/users/@me/guilds.ts @@ -1,12 +1,23 @@ import { Router, Request, Response } from "express"; -import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, emitEvent, Config } from "@fosscord/util"; +import { + Guild, + Member, + User, + GuildDeleteEvent, + GuildMemberRemoveEvent, + emitEvent, + Config, +} from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - const members = await Member.find({ relations: ["guild"], where: { id: req.user_id } }); + const members = await Member.find({ + relations: ["guild"], + where: { id: req.user_id }, + }); let guild = members.map((x) => x.guild); @@ -21,11 +32,19 @@ router.get("/", route({}), async (req: Request, res: Response) => { router.delete("/:guild_id", route({}), async (req: Request, res: Response) => { const { autoJoin } = Config.get().guild; const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] }); + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: ["owner_id"], + }); if (!guild) throw new HTTPError("Guild doesn't exist", 404); - if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400); - if (autoJoin.enabled && autoJoin.guilds.includes(guild_id) && !autoJoin.canLeave) { + if (guild.owner_id === req.user_id) + throw new HTTPError("You can't leave your own guild", 400); + if ( + autoJoin.enabled && + autoJoin.guilds.includes(guild_id) && + !autoJoin.canLeave + ) { throw new HTTPError("You can't leave instance auto join guilds", 400); } @@ -34,10 +53,10 @@ router.delete("/:guild_id", route({}), async (req: Request, res: Response) => { emitEvent({ event: "GUILD_DELETE", data: { - id: guild_id + id: guild_id, }, - user_id: req.user_id - } as GuildDeleteEvent) + user_id: req.user_id, + } as GuildDeleteEvent), ]); const user = await User.getPublicUser(req.user_id); @@ -46,9 +65,9 @@ router.delete("/:guild_id", route({}), async (req: Request, res: Response) => { event: "GUILD_MEMBER_REMOVE", data: { guild_id: guild_id, - user: user + user: user, }, - guild_id: guild_id + guild_id: guild_id, } as GuildMemberRemoveEvent); return res.sendStatus(204); diff --git a/src/api/routes/users/@me/guilds/#guild_id/settings.ts b/src/api/routes/users/@me/guilds/#guild_id/settings.ts index f09be25b..4b806cfb 100644 --- a/src/api/routes/users/@me/guilds/#guild_id/settings.ts +++ b/src/api/routes/users/@me/guilds/#guild_id/settings.ts @@ -1,39 +1,51 @@ import { Router, Response, Request } from "express"; -import { Channel, ChannelOverride, Member, UserGuildSettings } from "@fosscord/util"; +import { + Channel, + ChannelOverride, + Member, + UserGuildSettings, +} from "@fosscord/util"; import { route } from "@fosscord/api"; const router = Router(); // This sucks. I would use a DeepPartial, my own or typeorms, but they both generate inncorect schema -export interface UserGuildSettingsSchema extends Partial<Omit<UserGuildSettings, 'channel_overrides'>> { +export interface UserGuildSettingsSchema + extends Partial<Omit<UserGuildSettings, "channel_overrides">> { channel_overrides: { [channel_id: string]: Partial<ChannelOverride>; - }, + }; } // GET doesn't exist on discord.com router.get("/", route({}), async (req: Request, res: Response) => { const user = await Member.findOneOrFail({ where: { id: req.user_id, guild_id: req.params.guild_id }, - select: ["settings"] + select: ["settings"], }); return res.json(user.settings); }); -router.patch("/", route({ body: "UserGuildSettingsSchema" }), async (req: Request, res: Response) => { - const body = req.body as UserGuildSettings; +router.patch( + "/", + route({ body: "UserGuildSettingsSchema" }), + async (req: Request, res: Response) => { + const body = req.body as UserGuildSettings; - if (body.channel_overrides) { - for (var channel in body.channel_overrides) { - Channel.findOneOrFail({ where: { id: channel } }); + if (body.channel_overrides) { + for (var channel in body.channel_overrides) { + Channel.findOneOrFail({ where: { id: channel } }); + } } - } - const user = await Member.findOneOrFail({ where: { id: req.user_id, guild_id: req.params.guild_id } }); - user.settings = { ...user.settings, ...body }; - await user.save(); + const user = await Member.findOneOrFail({ + where: { id: req.user_id, guild_id: req.params.guild_id }, + }); + user.settings = { ...user.settings, ...body }; + await user.save(); - res.json(user.settings); -}); + res.json(user.settings); + }, +); export default router; diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts index e849b72a..5eba4665 100644 --- a/src/api/routes/users/@me/index.ts +++ b/src/api/routes/users/@me/index.ts @@ -1,5 +1,15 @@ import { Router, Request, Response } from "express"; -import { User, PrivateUserProjection, emitEvent, UserUpdateEvent, handleFile, FieldErrors, adjustEmail, Config, UserModifySchema } from "@fosscord/util"; +import { + User, + PrivateUserProjection, + emitEvent, + UserUpdateEvent, + handleFile, + FieldErrors, + adjustEmail, + Config, + UserModifySchema, +} from "@fosscord/util"; import { route } from "@fosscord/api"; import bcrypt from "bcrypt"; import { HTTPError } from "lambert-server"; @@ -7,79 +17,134 @@ import { HTTPError } from "lambert-server"; const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } })); + res.json( + await User.findOne({ + select: PrivateUserProjection, + where: { id: req.user_id }, + }), + ); }); -router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: Response) => { - const body = req.body as UserModifySchema; - - const user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] }); - - if (user.email == "demo@maddy.k.vu") throw new HTTPError("Demo user, sorry", 400); +router.patch( + "/", + route({ body: "UserModifySchema" }), + async (req: Request, res: Response) => { + const body = req.body as UserModifySchema; + + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: [...PrivateUserProjection, "data"], + }); + + if (user.email == "demo@maddy.k.vu") + throw new HTTPError("Demo user, sorry", 400); + + if (body.avatar) + body.avatar = await handleFile( + `/avatars/${req.user_id}`, + body.avatar as string, + ); + if (body.banner) + body.banner = await handleFile( + `/banners/${req.user_id}`, + body.banner as string, + ); + + if (body.password) { + if (user.data?.hash) { + const same_password = await bcrypt.compare( + body.password, + user.data.hash || "", + ); + if (!same_password) { + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } + } else { + user.data.hash = await bcrypt.hash(body.password, 12); + } + } - if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); - if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); + if (body.email) { + body.email = adjustEmail(body.email); + if (!body.email && Config.get().register.email.required) + throw FieldErrors({ + email: { + message: req.t("auth:register.EMAIL_INVALID"), + code: "EMAIL_INVALID", + }, + }); + if (!body.password) + throw FieldErrors({ + password: { + message: req.t("auth:register.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } - if (body.password) { - if (user.data?.hash) { - const same_password = await bcrypt.compare(body.password, user.data.hash || ""); - if (!same_password) { - throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); + if (body.new_password) { + if (!body.password && !user.email) { + throw FieldErrors({ + password: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); } - } else { - user.data.hash = await bcrypt.hash(body.password, 12); + user.data.hash = await bcrypt.hash(body.new_password, 12); } - } - - if (body.email) { - body.email = adjustEmail(body.email); - if (!body.email && Config.get().register.email.required) - throw FieldErrors({ email: { message: req.t("auth:register.EMAIL_INVALID"), code: "EMAIL_INVALID" } }); - if (!body.password) - throw FieldErrors({ password: { message: req.t("auth:register.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); - } - - if (body.new_password) { - if (!body.password && !user.email) { - throw FieldErrors({ - password: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } - }); - } - user.data.hash = await bcrypt.hash(body.new_password, 12); - } - - if (body.username) { - var check_username = body?.username?.replace(/\s/g, ''); - if (!check_username) { - throw FieldErrors({ - username: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } - }); + + if (body.username) { + var check_username = body?.username?.replace(/\s/g, ""); + if (!check_username) { + throw FieldErrors({ + username: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } } - } - if (body.discriminator) { - if (await User.findOne({ where: { discriminator: body.discriminator, username: body.username || user.username } })) { - throw FieldErrors({ - discriminator: { code: "INVALID_DISCRIMINATOR", message: "This discriminator is already in use." } - }); + if (body.discriminator) { + if ( + await User.findOne({ + where: { + discriminator: body.discriminator, + username: body.username || user.username, + }, + }) + ) { + throw FieldErrors({ + discriminator: { + code: "INVALID_DISCRIMINATOR", + message: "This discriminator is already in use.", + }, + }); + } } - } - user.assign(body); - await user.save(); + user.assign(body); + await user.save(); - // @ts-ignore - delete user.data; + // @ts-ignore + delete user.data; - // TODO: send update member list event in gateway - await emitEvent({ - event: "USER_UPDATE", - user_id: req.user_id, - data: user - } as UserUpdateEvent); + // TODO: send update member list event in gateway + await emitEvent({ + event: "USER_UPDATE", + user_id: req.user_id, + data: user, + } as UserUpdateEvent); - res.json(user); -}); + res.json(user); + }, +); export default router; // {"message": "Invalid two-factor code", "code": 60008} diff --git a/src/api/routes/users/@me/mfa/codes-verification.ts b/src/api/routes/users/@me/mfa/codes-verification.ts index 071c71fa..3411605b 100644 --- a/src/api/routes/users/@me/mfa/codes-verification.ts +++ b/src/api/routes/users/@me/mfa/codes-verification.ts @@ -1,41 +1,49 @@ import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; -import { BackupCode, generateMfaBackupCodes, User, CodesVerificationSchema } from "@fosscord/util"; +import { + BackupCode, + generateMfaBackupCodes, + User, + CodesVerificationSchema, +} from "@fosscord/util"; const router = Router(); -router.post("/", route({ body: "CodesVerificationSchema" }), async (req: Request, res: Response) => { - const { key, nonce, regenerate } = req.body as CodesVerificationSchema; - - // TODO: We don't have email/etc etc, so can't send a verification code. - // Once that's done, this route can verify `key` - - const user = await User.findOneOrFail({ where: { id: req.user_id } }); - - var codes: BackupCode[]; - if (regenerate) { - await BackupCode.update( - { user: { id: req.user_id } }, - { expired: true } - ); - - codes = generateMfaBackupCodes(req.user_id); - await Promise.all(codes.map(x => x.save())); - } - else { - codes = await BackupCode.find({ - where: { - user: { - id: req.user_id, +router.post( + "/", + route({ body: "CodesVerificationSchema" }), + async (req: Request, res: Response) => { + const { key, nonce, regenerate } = req.body as CodesVerificationSchema; + + // TODO: We don't have email/etc etc, so can't send a verification code. + // Once that's done, this route can verify `key` + + const user = await User.findOneOrFail({ where: { id: req.user_id } }); + + var codes: BackupCode[]; + if (regenerate) { + await BackupCode.update( + { user: { id: req.user_id } }, + { expired: true }, + ); + + codes = generateMfaBackupCodes(req.user_id); + await Promise.all(codes.map((x) => x.save())); + } else { + codes = await BackupCode.find({ + where: { + user: { + id: req.user_id, + }, + expired: false, }, - expired: false, - } - }); - } + }); + } - return res.json({ - backup_codes: codes.map(x => ({ ...x, expired: undefined })), - }); -}); + return res.json({ + backup_codes: codes.map((x) => ({ ...x, expired: undefined })), + }); + }, +); export default router; diff --git a/src/api/routes/users/@me/mfa/codes.ts b/src/api/routes/users/@me/mfa/codes.ts index 58466b9c..33053028 100644 --- a/src/api/routes/users/@me/mfa/codes.ts +++ b/src/api/routes/users/@me/mfa/codes.ts @@ -1,45 +1,62 @@ import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; -import { BackupCode, FieldErrors, generateMfaBackupCodes, User, MfaCodesSchema } from "@fosscord/util"; +import { + BackupCode, + FieldErrors, + generateMfaBackupCodes, + User, + MfaCodesSchema, +} from "@fosscord/util"; import bcrypt from "bcrypt"; const router = Router(); // TODO: This route is replaced with users/@me/mfa/codes-verification in newer clients -router.post("/", route({ body: "MfaCodesSchema" }), async (req: Request, res: Response) => { - const { password, regenerate } = req.body as MfaCodesSchema; - - const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] }); - - if (!await bcrypt.compare(password, user.data.hash || "")) { - throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); - } - - var codes: BackupCode[]; - if (regenerate) { - await BackupCode.update( - { user: { id: req.user_id } }, - { expired: true } - ); - - codes = generateMfaBackupCodes(req.user_id); - await Promise.all(codes.map(x => x.save())); - } - else { - codes = await BackupCode.find({ - where: { - user: { - id: req.user_id, - }, - expired: false, - } +router.post( + "/", + route({ body: "MfaCodesSchema" }), + async (req: Request, res: Response) => { + const { password, regenerate } = req.body as MfaCodesSchema; + + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], }); - } - return res.json({ - backup_codes: codes.map(x => ({ ...x, expired: undefined })), - }); -}); + if (!(await bcrypt.compare(password, user.data.hash || ""))) { + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } + + var codes: BackupCode[]; + if (regenerate) { + await BackupCode.update( + { user: { id: req.user_id } }, + { expired: true }, + ); + + codes = generateMfaBackupCodes(req.user_id); + await Promise.all(codes.map((x) => x.save())); + } else { + codes = await BackupCode.find({ + where: { + user: { + id: req.user_id, + }, + expired: false, + }, + }); + } + + return res.json({ + backup_codes: codes.map((x) => ({ ...x, expired: undefined })), + }); + }, +); export default router; diff --git a/src/api/routes/users/@me/mfa/totp/disable.ts b/src/api/routes/users/@me/mfa/totp/disable.ts index 2fe9355c..7916e598 100644 --- a/src/api/routes/users/@me/mfa/totp/disable.ts +++ b/src/api/routes/users/@me/mfa/totp/disable.ts @@ -1,41 +1,56 @@ import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; -import { verifyToken } from 'node-2fa'; +import { verifyToken } from "node-2fa"; import { HTTPError } from "lambert-server"; -import { User, generateToken, BackupCode, TotpDisableSchema } from "@fosscord/util"; +import { + User, + generateToken, + BackupCode, + TotpDisableSchema, +} from "@fosscord/util"; const router = Router(); -router.post("/", route({ body: "TotpDisableSchema" }), async (req: Request, res: Response) => { - const body = req.body as TotpDisableSchema; - - const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["totp_secret"] }); - - const backup = await BackupCode.findOne({ where: { code: body.code } }); - if (!backup) { - const ret = verifyToken(user.totp_secret!, body.code); - if (!ret || ret.delta != 0) - throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - } - - await User.update( - { id: req.user_id }, - { - mfa_enabled: false, - totp_secret: "", - }, - ); - - await BackupCode.update( - { user: { id: req.user_id } }, - { - expired: true, +router.post( + "/", + route({ body: "TotpDisableSchema" }), + async (req: Request, res: Response) => { + const body = req.body as TotpDisableSchema; + + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["totp_secret"], + }); + + const backup = await BackupCode.findOne({ where: { code: body.code } }); + if (!backup) { + const ret = verifyToken(user.totp_secret!, body.code); + if (!ret || ret.delta != 0) + throw new HTTPError( + req.t("auth:login.INVALID_TOTP_CODE"), + 60008, + ); } - ); - return res.json({ - token: await generateToken(user.id), - }); -}); - -export default router; \ No newline at end of file + await User.update( + { id: req.user_id }, + { + mfa_enabled: false, + totp_secret: "", + }, + ); + + await BackupCode.update( + { user: { id: req.user_id } }, + { + expired: true, + }, + ); + + return res.json({ + token: await generateToken(user.id), + }); + }, +); + +export default router; diff --git a/src/api/routes/users/@me/mfa/totp/enable.ts b/src/api/routes/users/@me/mfa/totp/enable.ts index adafe180..75c64425 100644 --- a/src/api/routes/users/@me/mfa/totp/enable.ts +++ b/src/api/routes/users/@me/mfa/totp/enable.ts @@ -1,46 +1,62 @@ import { Router, Request, Response } from "express"; -import { User, generateToken, generateMfaBackupCodes, TotpEnableSchema } from "@fosscord/util"; +import { + User, + generateToken, + generateMfaBackupCodes, + TotpEnableSchema, +} from "@fosscord/util"; import { route } from "@fosscord/api"; import bcrypt from "bcrypt"; import { HTTPError } from "lambert-server"; -import { verifyToken } from 'node-2fa'; +import { verifyToken } from "node-2fa"; const router = Router(); -router.post("/", route({ body: "TotpEnableSchema" }), async (req: Request, res: Response) => { - const body = req.body as TotpEnableSchema; - - const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data", "email"] }); - - if (user.email == "demo@maddy.k.vu") throw new HTTPError("Demo user, sorry", 400); - - // TODO: Are guests allowed to enable 2fa? - if (user.data.hash) { - if (!await bcrypt.compare(body.password, user.data.hash)) { - throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); +router.post( + "/", + route({ body: "TotpEnableSchema" }), + async (req: Request, res: Response) => { + const body = req.body as TotpEnableSchema; + + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data", "email"], + }); + + if (user.email == "demo@maddy.k.vu") + throw new HTTPError("Demo user, sorry", 400); + + // TODO: Are guests allowed to enable 2fa? + if (user.data.hash) { + if (!(await bcrypt.compare(body.password, user.data.hash))) { + throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); + } } - } - - if (!body.secret) - throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005); - - if (!body.code) - throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - - if (verifyToken(body.secret, body.code)?.delta != 0) - throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - - let backup_codes = generateMfaBackupCodes(req.user_id); - await Promise.all(backup_codes.map(x => x.save())); - await User.update( - { id: req.user_id }, - { mfa_enabled: true, totp_secret: body.secret } - ); - - res.send({ - token: await generateToken(user.id), - backup_codes: backup_codes.map(x => ({ ...x, expired: undefined })), - }); -}); -export default router; \ No newline at end of file + if (!body.secret) + throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005); + + if (!body.code) + throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + + if (verifyToken(body.secret, body.code)?.delta != 0) + throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + + let backup_codes = generateMfaBackupCodes(req.user_id); + await Promise.all(backup_codes.map((x) => x.save())); + await User.update( + { id: req.user_id }, + { mfa_enabled: true, totp_secret: body.secret }, + ); + + res.send({ + token: await generateToken(user.id), + backup_codes: backup_codes.map((x) => ({ + ...x, + expired: undefined, + })), + }); + }, +); + +export default router; diff --git a/src/api/routes/users/@me/notes.ts b/src/api/routes/users/@me/notes.ts index f938f088..e54eb897 100644 --- a/src/api/routes/users/@me/notes.ts +++ b/src/api/routes/users/@me/notes.ts @@ -11,7 +11,7 @@ router.get("/:id", route({}), async (req: Request, res: Response) => { where: { owner: { id: req.user_id }, target: { id: id }, - } + }, }); return res.json({ @@ -24,32 +24,40 @@ router.get("/:id", route({}), async (req: Request, res: Response) => { router.put("/:id", route({}), async (req: Request, res: Response) => { const { id } = req.params; const owner = await User.findOneOrFail({ where: { id: req.user_id } }); - const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw + const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw const { note } = req.body; if (note && note.length) { // upsert a note - if (await Note.findOne({ where: { owner: { id: owner.id }, target: { id: target.id } } })) { + if ( + await Note.findOne({ + where: { owner: { id: owner.id }, target: { id: target.id } }, + }) + ) { Note.update( { owner: { id: owner.id }, target: { id: target.id } }, - { owner, target, content: note } + { owner, target, content: note }, ); + } else { + Note.insert({ + id: Snowflake.generate(), + owner, + target, + content: note, + }); } - else { - Note.insert( - { id: Snowflake.generate(), owner, target, content: note } - ); - } - } - else { - await Note.delete({ owner: { id: owner.id }, target: { id: target.id } }); + } else { + await Note.delete({ + owner: { id: owner.id }, + target: { id: target.id }, + }); } await emitEvent({ event: "USER_NOTE_UPDATE", data: { note: note, - id: target.id + id: target.id, }, user_id: owner.id, }); diff --git a/src/api/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts index cd33704d..3eec704b 100644 --- a/src/api/routes/users/@me/relationships.ts +++ b/src/api/routes/users/@me/relationships.ts @@ -6,7 +6,7 @@ import { RelationshipRemoveEvent, emitEvent, Relationship, - Config + Config, } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; @@ -15,13 +15,16 @@ import { route } from "@fosscord/api"; const router = Router(); -const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection]; +const userProjection: (keyof User)[] = [ + "relationships", + ...PublicUserProjection, +]; router.get("/", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships", "relationships.to"], - select: ["id", "relationships"] + select: ["id", "relationships"], }); //TODO DTO @@ -30,49 +33,76 @@ router.get("/", route({}), async (req: Request, res: Response) => { id: r.to.id, type: r.type, nickname: null, - user: r.to.toPublicUser() + user: r.to.toPublicUser(), }; }); return res.json(related_users); }); -router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request, res: Response) => { - return await updateRelationship( - req, - res, - await User.findOneOrFail({ where: { id: req.params.id }, relations: ["relationships", "relationships.to"], select: userProjection }), - req.body.type ?? RelationshipType.friends - ); -}); +router.put( + "/:id", + route({ body: "RelationshipPutSchema" }), + async (req: Request, res: Response) => { + return await updateRelationship( + req, + res, + await User.findOneOrFail({ + where: { id: req.params.id }, + relations: ["relationships", "relationships.to"], + select: userProjection, + }), + req.body.type ?? RelationshipType.friends, + ); + }, +); -router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, res: Response) => { - return await updateRelationship( - req, - res, - await User.findOneOrFail({ - relations: ["relationships", "relationships.to"], - select: userProjection, - where: { - discriminator: String(req.body.discriminator).padStart(4, "0"), //Discord send the discriminator as integer, we need to add leading zeroes - username: req.body.username - } - }), - req.body.type - ); -}); +router.post( + "/", + route({ body: "RelationshipPostSchema" }), + async (req: Request, res: Response) => { + return await updateRelationship( + req, + res, + await User.findOneOrFail({ + relations: ["relationships", "relationships.to"], + select: userProjection, + where: { + discriminator: String(req.body.discriminator).padStart( + 4, + "0", + ), //Discord send the discriminator as integer, we need to add leading zeroes + username: req.body.username, + }, + }), + req.body.type, + ); + }, +); router.delete("/:id", route({}), async (req: Request, res: Response) => { const { id } = req.params; - if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend"); + if (id === req.user_id) + throw new HTTPError("You can't remove yourself as a friend"); - const user = await User.findOneOrFail({ where: { id: req.user_id }, select: userProjection, relations: ["relationships"] }); - const friend = await User.findOneOrFail({ where: { id: id }, select: userProjection, relations: ["relationships"] }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: userProjection, + relations: ["relationships"], + }); + const friend = await User.findOneOrFail({ + where: { id: id }, + select: userProjection, + relations: ["relationships"], + }); const relationship = user.relationships.find((x) => x.to_id === id); - const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); + const friendRequest = friend.relationships.find( + (x) => x.to_id === req.user_id, + ); - if (!relationship) throw new HTTPError("You are not friends with the user", 404); + if (!relationship) + throw new HTTPError("You are not friends with the user", 404); if (relationship?.type === RelationshipType.blocked) { // unblock user @@ -81,8 +111,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => { emitEvent({ event: "RELATIONSHIP_REMOVE", user_id: req.user_id, - data: relationship.toPublicRelationship() - } as RelationshipRemoveEvent) + data: relationship.toPublicRelationship(), + } as RelationshipRemoveEvent), ]); return res.sendStatus(204); } @@ -92,8 +122,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => { await emitEvent({ event: "RELATIONSHIP_REMOVE", data: friendRequest.toPublicRelationship(), - user_id: id - } as RelationshipRemoveEvent) + user_id: id, + } as RelationshipRemoveEvent), ]); } @@ -102,8 +132,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => { emitEvent({ event: "RELATIONSHIP_REMOVE", data: relationship.toPublicRelationship(), - user_id: req.user_id - } as RelationshipRemoveEvent) + user_id: req.user_id, + } as RelationshipRemoveEvent), ]); return res.sendStatus(204); @@ -111,26 +141,40 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => { export default router; -async function updateRelationship(req: Request, res: Response, friend: User, type: RelationshipType) { +async function updateRelationship( + req: Request, + res: Response, + friend: User, + type: RelationshipType, +) { const id = friend.id; - if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); + if (id === req.user_id) + throw new HTTPError("You can't add yourself as a friend"); const user = await User.findOneOrFail({ where: { id: req.user_id }, - relations: ["relationships", "relationships.to"], select: userProjection + relations: ["relationships", "relationships.to"], + select: userProjection, }); var relationship = user.relationships.find((x) => x.to_id === id); - const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); + const friendRequest = friend.relationships.find( + (x) => x.to_id === req.user_id, + ); // TODO: you can add infinitely many blocked users (should this be prevented?) if (type === RelationshipType.blocked) { if (relationship) { - if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user"); + if (relationship.type === RelationshipType.blocked) + throw new HTTPError("You already blocked the user"); relationship.type = RelationshipType.blocked; await relationship.save(); } else { - relationship = await Relationship.create({ to_id: id, type: RelationshipType.blocked, from_id: req.user_id }).save(); + relationship = await Relationship.create({ + to_id: id, + type: RelationshipType.blocked, + from_id: req.user_id, + }).save(); } if (friendRequest && friendRequest.type !== RelationshipType.blocked) { @@ -139,43 +183,56 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ emitEvent({ event: "RELATIONSHIP_REMOVE", data: friendRequest.toPublicRelationship(), - user_id: id - } as RelationshipRemoveEvent) + user_id: id, + } as RelationshipRemoveEvent), ]); } await emitEvent({ event: "RELATIONSHIP_ADD", data: relationship.toPublicRelationship(), - user_id: req.user_id + user_id: req.user_id, } as RelationshipAddEvent); return res.sendStatus(204); } const { maxFriends } = Config.get().limits.user; - if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends); + if (user.relationships.length >= maxFriends) + throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends); - var incoming_relationship = Relationship.create({ nickname: undefined, type: RelationshipType.incoming, to: user, from: friend }); + var incoming_relationship = Relationship.create({ + nickname: undefined, + type: RelationshipType.incoming, + to: user, + from: friend, + }); var outgoing_relationship = Relationship.create({ nickname: undefined, type: RelationshipType.outgoing, to: friend, - from: user + from: user, }); if (friendRequest) { - if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); - if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); + if (friendRequest.type === RelationshipType.blocked) + throw new HTTPError("The user blocked you"); + if (friendRequest.type === RelationshipType.friends) + throw new HTTPError("You are already friends with the user"); // accept friend request incoming_relationship = friendRequest; incoming_relationship.type = RelationshipType.friends; } if (relationship) { - if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request"); - if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request"); - if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); + if (relationship.type === RelationshipType.outgoing) + throw new HTTPError("You already sent a friend request"); + if (relationship.type === RelationshipType.blocked) + throw new HTTPError( + "Unblock the user before sending a friend request", + ); + if (relationship.type === RelationshipType.friends) + throw new HTTPError("You are already friends with the user"); outgoing_relationship = relationship; outgoing_relationship.type = RelationshipType.friends; } @@ -186,16 +243,16 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ emitEvent({ event: "RELATIONSHIP_ADD", data: outgoing_relationship.toPublicRelationship(), - user_id: req.user_id + user_id: req.user_id, } as RelationshipAddEvent), emitEvent({ event: "RELATIONSHIP_ADD", data: { ...incoming_relationship.toPublicRelationship(), - should_notify: true + should_notify: true, }, - user_id: id - } as RelationshipAddEvent) + user_id: id, + } as RelationshipAddEvent), ]); return res.sendStatus(204); diff --git a/src/api/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts index 9060baf7..30e5969c 100644 --- a/src/api/routes/users/@me/settings.ts +++ b/src/api/routes/users/@me/settings.ts @@ -4,25 +4,31 @@ import { route } from "@fosscord/api"; const router = Router(); -export interface UserSettingsSchema extends Partial<UserSettings> { } +export interface UserSettingsSchema extends Partial<UserSettings> {} router.get("/", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ where: { id: req.user_id }, - select: ["settings"] + select: ["settings"], }); return res.json(user.settings); }); -router.patch("/", route({ body: "UserSettingsSchema" }), async (req: Request, res: Response) => { - const body = req.body as UserSettings; - if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale +router.patch( + "/", + route({ body: "UserSettingsSchema" }), + async (req: Request, res: Response) => { + const body = req.body as UserSettings; + if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale - const user = await User.findOneOrFail({ where: { id: req.user_id, bot: false } }); - user.settings = { ...user.settings, ...body }; - await user.save(); + const user = await User.findOneOrFail({ + where: { id: req.user_id, bot: false }, + }); + user.settings = { ...user.settings, ...body }; + await user.save(); - res.json(user.settings); -}); + res.json(user.settings); + }, +); export default router; |