diff --git a/assets/openapi.json b/assets/openapi.json index e502af6..07175b0 100644 --- a/assets/openapi.json +++ b/assets/openapi.json @@ -30,17 +30,6 @@ } }, "schemas": { - "CreateFingerprintResponse": { - "type": "object", - "properties": { - "fingerprint": { - "type": "string" - } - }, - "required": [ - "fingerprint" - ] - }, "ApplicationCommandSchema": { "type": "object", "properties": { @@ -3109,9 +3098,6 @@ "APIPrivateUser": { "type": "object", "properties": { - "email": { - "type": "string" - }, "id": { "description": "A Twitter-like snowflake, except the epoch is 2015-01-01T00:00:00.000Z\n```\nIf we have a snowflake '266241948824764416' we can represent it as binary:\n\n64 22 17 12 0\n 000000111011000111100001101001000101000000 00001 00000 000000000000\n number of ms since Discord epoch worker pid increment\n```", "type": "string" @@ -3122,6 +3108,9 @@ "disabled": { "type": "boolean" }, + "email": { + "type": "string" + }, "verified": { "type": "boolean" }, @@ -3236,9 +3225,6 @@ "newToken": { "type": "string" }, - "email": { - "type": "string" - }, "id": { "description": "A Twitter-like snowflake, except the epoch is 2015-01-01T00:00:00.000Z\n```\nIf we have a snowflake '266241948824764416' we can represent it as binary:\n\n64 22 17 12 0\n 000000111011000111100001101001000101000000 00001 00000 000000000000\n number of ms since Discord epoch worker pid increment\n```", "type": "string" @@ -3249,6 +3235,9 @@ "disabled": { "type": "boolean" }, + "email": { + "type": "string" + }, "verified": { "type": "boolean" }, @@ -6946,6 +6935,17 @@ "$ref": "#/components/schemas/ApplicationCommandCreateSchema" } }, + "CreateFingerprintResponse": { + "type": "object", + "properties": { + "fingerprint": { + "type": "string" + } + }, + "required": [ + "fingerprint" + ] + }, "ApplicationCommandOption": { "type": "object", "properties": { @@ -12137,7 +12137,7 @@ "length": { "type": "integer" }, - "__@unscopables@698": { + "__@unscopables@703": { "type": "object", "additionalProperties": false, "patternProperties": { @@ -12266,17 +12266,17 @@ "remove": { "type": "boolean" }, - "__@iterator@696": { + "__@iterator@670": { "type": "boolean" }, - "__@unscopables@698": { + "__@unscopables@703": { "type": "boolean" } } } }, "required": [ - "__@unscopables@698", + "__@unscopables@703", "length" ] }, diff --git a/assets/schemas.json b/assets/schemas.json index d5b4835..24eebc3 100644 --- a/assets/schemas.json +++ b/assets/schemas.json @@ -1,17 +1,4 @@ { - "CreateFingerprintResponse": { - "type": "object", - "properties": { - "fingerprint": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "fingerprint" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, "ApplicationCommandSchema": { "type": "object", "properties": { @@ -3260,9 +3247,6 @@ "APIPrivateUser": { "type": "object", "properties": { - "email": { - "type": "string" - }, "id": { "description": "A Twitter-like snowflake, except the epoch is 2015-01-01T00:00:00.000Z\n```\nIf we have a snowflake '266241948824764416' we can represent it as binary:\n\n64 22 17 12 0\n 000000111011000111100001101001000101000000 00001 00000 000000000000\n number of ms since Discord epoch worker pid increment\n```", "type": "string" @@ -3273,6 +3257,9 @@ "disabled": { "type": "boolean" }, + "email": { + "type": "string" + }, "verified": { "type": "boolean" }, @@ -3392,9 +3379,6 @@ "newToken": { "type": "string" }, - "email": { - "type": "string" - }, "id": { "description": "A Twitter-like snowflake, except the epoch is 2015-01-01T00:00:00.000Z\n```\nIf we have a snowflake '266241948824764416' we can represent it as binary:\n\n64 22 17 12 0\n 000000111011000111100001101001000101000000 00001 00000 000000000000\n number of ms since Discord epoch worker pid increment\n```", "type": "string" @@ -3405,6 +3389,9 @@ "disabled": { "type": "boolean" }, + "email": { + "type": "string" + }, "verified": { "type": "boolean" }, @@ -7391,6 +7378,19 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, + "CreateFingerprintResponse": { + "type": "object", + "properties": { + "fingerprint": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "fingerprint" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, "ApplicationCommandOption": { "type": "object", "properties": { @@ -12895,7 +12895,7 @@ "length": { "type": "integer" }, - "__@unscopables@698": { + "__@unscopables@703": { "type": "object", "additionalProperties": false, "patternProperties": { @@ -13024,17 +13024,17 @@ "remove": { "type": "boolean" }, - "__@iterator@696": { + "__@iterator@670": { "type": "boolean" }, - "__@unscopables@698": { + "__@unscopables@703": { "type": "boolean" } } } }, "required": [ - "__@unscopables@698", + "__@unscopables@703", "length" ] }, diff --git a/src/api/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts index 81ccbc5..1a41ad4 100644 --- a/src/api/middlewares/RateLimit.ts +++ b/src/api/middlewares/RateLimit.ts @@ -16,7 +16,6 @@ along with this program. If not, see . */ -import { getIpAdress } from "@spacebar/api"; import { Config, getRights, listenEvent } from "@spacebar/util"; import { NextFunction, Request, Response, Router } from "express"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; @@ -65,21 +64,14 @@ if (rights.has("BYPASS_RATE_LIMITS")) return next(); } - const bucket_id = - opts.bucket || - req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); - let executor_id = getIpAdress(req); + const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); + let executor_id = req.ip || "127.0.0.1"; if (!opts.onlyIp && req.user_id) executor_id = req.user_id; let max_hits = opts.count; if (opts.bot && req.user_bot) max_hits = opts.bot; - if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) - max_hits = opts.GET; - else if ( - opts.MODIFY && - ["POST", "DELETE", "PATCH", "PUT"].includes(req.method) - ) - max_hits = opts.MODIFY; + if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET; + else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY; const offender = Cache.get(executor_id + bucket_id); @@ -104,18 +96,13 @@ } res.set("X-RateLimit-Reset", `${reset}`); - res.set( - "X-RateLimit-Reset-After", - `${Math.max(0, Math.ceil(resetAfterSec))}`, - ); + res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`); if (offender.blocked) { const global = bucket_id === "global"; // each block violation pushes the expiry one full window further reset += opts.window * 1000; - offender.expires_at = new Date( - offender.expires_at.getTime() + opts.window * 1000, - ); + offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000); resetAfterMs = reset - Date.now(); resetAfterSec = Math.ceil(resetAfterMs / 1000); @@ -129,10 +116,7 @@ res .status(429) .set("X-RateLimit-Remaining", "0") - .set( - "Retry-After", - `${Math.max(0, Math.ceil(resetAfterSec))}`, - ) + .set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`) // TODO: error rate limit message translation .send({ message: "You are being rate limited.", @@ -156,11 +140,7 @@ // check if error and increment error rate limit if (res.statusCode >= 400 && opts.error) { return hitRoute(hitRouteOpts); - } else if ( - res.statusCode >= 200 && - res.statusCode < 300 && - opts.success - ) { + } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) { return hitRoute(hitRouteOpts); } }); @@ -213,18 +193,10 @@ app.use("/webhooks/:webhook_id", rateLimit(routes.webhook)); app.use("/channels/:channel_id", rateLimit(routes.channel)); app.use("/auth/login", rateLimit(routes.auth.login)); - app.use( - "/auth/register", - rateLimit({ onlyIp: true, success: true, ...routes.auth.register }), - ); + app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register })); } -async function hitRoute(opts: { - executor_id: string; - bucket_id: string; - max_hits: number; - window: number; -}) { +async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { const id = opts.executor_id + opts.bucket_id; let limit = Cache.get(id); if (!limit) { diff --git a/src/api/routes/auth/forgot.ts b/src/api/routes/auth/forgot.ts index 12bc9cb..6e61382 100644 --- a/src/api/routes/auth/forgot.ts +++ b/src/api/routes/auth/forgot.ts @@ -16,10 +16,10 @@ along with this program. If not, see . */ -import { getIpAdress, route, verifyCaptcha } from "@spacebar/api"; +import { route, verifyCaptcha } from "@spacebar/api"; import { Config, Email, User } from "@spacebar/util"; import { Request, Response, Router } from "express"; -import { ForgotPasswordSchema } from "@spacebar/schemas" +import { ForgotPasswordSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.post( @@ -38,10 +38,7 @@ const config = Config.get(); - if ( - config.passwordReset.requireCaptcha && - config.security.captcha.enabled - ) { + if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) { const { sitekey, service } = config.security.captcha; if (!captcha_key) { return res.status(400).json({ @@ -51,7 +48,7 @@ }); } - const ip = getIpAdress(req); + const ip = req.ip; const verify = await verifyCaptcha(captcha_key, ip); if (!verify.success) { return res.status(400).json({ @@ -71,9 +68,7 @@ if (user && user.email) { Email.sendResetPassword(user, user.email).catch((e) => { - console.error( - `Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`, - ); + console.error(`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`); }); } }, diff --git a/src/api/routes/auth/location-metadata.ts b/src/api/routes/auth/location-metadata.ts index a08c98a..92f4790 100644 --- a/src/api/routes/auth/location-metadata.ts +++ b/src/api/routes/auth/location-metadata.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, route } from "@spacebar/api"; +import { route } from "@spacebar/api"; import { IpDataClient } from "@spacebar/util"; import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); @@ -33,7 +33,7 @@ async (req: Request, res: Response) => { //TODO //Note: It's most likely related to legal. At the moment Discord hasn't finished this too - const country_code = (await IpDataClient.getIpInfo(getIpAdress(req)))?.country_code; + const country_code = (await IpDataClient.getIpInfo(req.ip!))?.country_code; res.json({ consent_required: false, country_code: country_code, diff --git a/src/api/routes/auth/login.ts b/src/api/routes/auth/login.ts index 06e3fe4..c8ef627 100644 --- a/src/api/routes/auth/login.ts +++ b/src/api/routes/auth/login.ts @@ -16,19 +16,12 @@ along with this program. If not, see . */ -import { getIpAdress, route, verifyCaptcha } from "@spacebar/api"; -import { - Config, - FieldErrors, - User, - WebAuthn, - generateToken, - generateWebAuthnTicket, -} from "@spacebar/util"; +import { route, verifyCaptcha } from "@spacebar/api"; +import { Config, FieldErrors, User, WebAuthn, generateToken, generateWebAuthnTicket } from "@spacebar/util"; import bcrypt from "bcrypt"; import crypto from "crypto"; import { Request, Response, Router } from "express"; -import { LoginSchema } from "@spacebar/schemas" +import { LoginSchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); export default router; @@ -61,7 +54,7 @@ }); } - const ip = getIpAdress(req); + const ip = req.ip; const verify = await verifyCaptcha(captcha_key, ip); if (!verify.success) { return res.status(400).json({ @@ -74,17 +67,7 @@ const user = await User.findOneOrFail({ where: [{ phone: login }, { email: login }], - select: [ - "data", - "id", - "disabled", - "deleted", - "totp_secret", - "mfa_enabled", - "webauthn_enabled", - "security_keys", - "verified", - ], + select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "webauthn_enabled", "security_keys", "verified"], relations: ["security_keys", "settings"], }).catch(() => { throw FieldErrors({ @@ -100,10 +83,7 @@ }); // the salt is saved in the password refer to bcrypt docs - const same_password = await bcrypt.compare( - password, - user.data.hash || "", - ); + const same_password = await bcrypt.compare(password, user.data.hash || ""); if (!same_password) { throw FieldErrors({ login: { @@ -122,8 +102,7 @@ throw FieldErrors({ login: { code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL", - message: - "Email verification is required, please check your email.", + message: "Email verification is required, please check your email.", }, }); } @@ -152,9 +131,7 @@ const challenge = JSON.stringify({ publicKey: { ...options, - challenge: Buffer.from(options.challenge).toString( - "base64", - ), + challenge: Buffer.from(options.challenge).toString("base64"), allowCredentials: user.security_keys.map((x) => ({ id: x.key_id, type: "public-key", @@ -178,10 +155,8 @@ if (undelete) { // undelete refers to un'disable' here - if (user.disabled) - await User.update({ id: user.id }, { disabled: false }); - if (user.deleted) - await User.update({ id: user.id }, { deleted: false }); + if (user.disabled) await User.update({ id: user.id }, { disabled: false }); + if (user.deleted) await User.update({ id: user.id }, { deleted: false }); } else { if (user.deleted) return res.status(400).json({ diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts index 418f0a5..d4b061b 100644 --- a/src/api/routes/auth/register.ts +++ b/src/api/routes/auth/register.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, route, verifyCaptcha } from "@spacebar/api"; +import { route, verifyCaptcha } from "@spacebar/api"; import { Config, FieldErrors, Invite, User, ValidRegistrationToken, generateToken, IpDataClient, AbuseIpDbClient } from "@spacebar/util"; import bcrypt from "bcrypt"; import { Request, Response, Router } from "express"; @@ -38,7 +38,7 @@ async (req: Request, res: Response) => { const body = req.body as RegisterSchema; const { register, security, limits } = Config.get(); - const ip = getIpAdress(req); + const ip = req.ip!; // Reg tokens // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc ) @@ -143,7 +143,7 @@ const ipData = await IpDataClient.getIpInfo(ip); if (ipData) { - if(!ipData.threat) { + if (!ipData.threat) { console.log("Invalid IPData.co response, missing threat field", ipData); } const categories = Object.entries(ipData.threat) @@ -287,7 +287,7 @@ }, })) >= limits.absoluteRate.register.limit ) { - console.log(`Global register ratelimit exceeded for ${getIpAdress(req)}, ${req.body.username}, ${req.body.invite || "No invite given"}`); + console.log(`Global register ratelimit exceeded for ${req.ip}, ${req.body.username}, ${req.body.invite || "No invite given"}`); throw FieldErrors({ email: { code: "TOO_MANY_REGISTRATIONS", diff --git a/src/api/routes/auth/verify/index.ts b/src/api/routes/auth/verify/index.ts index b85120d..d8e343c 100644 --- a/src/api/routes/auth/verify/index.ts +++ b/src/api/routes/auth/verify/index.ts @@ -16,14 +16,8 @@ along with this program. If not, see . */ -import { getIpAdress, route, verifyCaptcha } from "@spacebar/api"; -import { - checkToken, - Config, - FieldErrors, - generateToken, - User, -} from "@spacebar/util"; +import { route, verifyCaptcha } from "@spacebar/api"; +import { checkToken, Config, FieldErrors, generateToken, User } from "@spacebar/util"; import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); @@ -67,7 +61,7 @@ }); } - const ip = getIpAdress(req); + const ip = req.ip; const verify = await verifyCaptcha(captcha_key, ip); if (!verify.success) { return res.status(400).json({ diff --git "a/src/api/routes/guilds/\043guild_id/bans.ts" "b/src/api/routes/guilds/\043guild_id/bans.ts" index 1274c19..30ef8df 100644 --- "a/src/api/routes/guilds/\043guild_id/bans.ts" +++ "b/src/api/routes/guilds/\043guild_id/bans.ts" @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, route } from "@spacebar/api"; +import { route } from "@spacebar/api"; import { Ban, DiscordApiErrors, GuildBanAddEvent, GuildBanRemoveEvent, Member, User, emitEvent } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -279,4 +279,4 @@ }, ); -export default router; +export default router; \ No newline at end of file diff --git "a/src/api/routes/guilds/\043guild_id/bulk-ban.ts" "b/src/api/routes/guilds/\043guild_id/bulk-ban.ts" index 3d3ed1c..a564e2d 100644 --- "a/src/api/routes/guilds/\043guild_id/bulk-ban.ts" +++ "b/src/api/routes/guilds/\043guild_id/bulk-ban.ts" @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, route } from "@spacebar/api"; +import { route } from "@spacebar/api"; import { Ban, DiscordApiErrors, GuildBanAddEvent, Member, User, emitEvent } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -81,7 +81,7 @@ const ban = Ban.create({ user_id: banned_user_id, guild_id: guild_id, - ip: getIpAdress(req), + ip: req.ip, executor_id: req.user_id, reason: req.body.reason, // || otherwise empty }); diff --git "a/src/api/routes/guilds/\043guild_id/regions.ts" "b/src/api/routes/guilds/\043guild_id/regions.ts" index a7fad81..a3c5a81 100644 --- "a/src/api/routes/guilds/\043guild_id/regions.ts" +++ "b/src/api/routes/guilds/\043guild_id/regions.ts" @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, getVoiceRegions, route } from "@spacebar/api"; +import { getVoiceRegions, route } from "@spacebar/api"; import { Guild } from "@spacebar/util"; import { Request, Response, Router } from "express"; @@ -38,12 +38,7 @@ const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); //TODO we should use an enum for guild's features and not hardcoded strings - return res.json( - await getVoiceRegions( - getIpAdress(req), - guild.features.includes("VIP_REGIONS"), - ), - ); + return res.json(await getVoiceRegions(req.ip!, guild.features.includes("VIP_REGIONS"))); }, ); diff --git a/src/api/routes/invites/index.ts b/src/api/routes/invites/index.ts index c77d36e..87715f3 100644 --- a/src/api/routes/invites/index.ts +++ b/src/api/routes/invites/index.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, route } from "@spacebar/api"; +import { route } from "@spacebar/api"; import { Ban, DiscordApiErrors, emitEvent, getPermission, Guild, Invite, InviteDeleteEvent, PublicInviteRelation, User } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -83,7 +83,7 @@ const ban = await Ban.findOne({ where: [ { guild_id: guild_id, user_id: req.user_id }, - { guild_id: guild_id, ip: getIpAdress(req) }, + { guild_id: guild_id, ip: req.ip }, ], }); diff --git a/src/api/routes/voice/regions.ts b/src/api/routes/voice/regions.ts index 9fdd6fd..ba26123 100644 --- a/src/api/routes/voice/regions.ts +++ b/src/api/routes/voice/regions.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, getVoiceRegions, route } from "@spacebar/api"; +import { getVoiceRegions, route } from "@spacebar/api"; import { Request, Response, Router } from "express"; const router: Router = Router({ mergeParams: true }); @@ -31,7 +31,7 @@ }, }), async (req: Request, res: Response) => { - res.json(await getVoiceRegions(getIpAdress(req), true)); //vip true? + res.json(await getVoiceRegions(req.ip!, true)); //vip true? }, ); diff --git a/src/api/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts index 914999a..1940825 100644 --- a/src/api/util/utility/ipAddress.ts +++ b/src/api/util/utility/ipAddress.ts @@ -16,41 +16,16 @@ along with this program. If not, see . */ -import { Config } from "@spacebar/util"; -import { Request } from "express"; - -export function getIpAdress(req: Request): string { - // TODO: express can do this (trustProxies: true)? - - return req.ip!; -} - type Location = { latitude: number; longitude: number }; -export function distanceBetweenLocations( - loc1: Location, - loc2: Location, -): number { - return distanceBetweenCoords( - loc1.latitude, - loc1.longitude, - loc2.latitude, - loc2.longitude, - ); +export function distanceBetweenLocations(loc1: Location, loc2: Location): number { + return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude); } //Haversine function -function distanceBetweenCoords( - lat1: number, - lon1: number, - lat2: number, - lon2: number, -) { +function distanceBetweenCoords(lat1: number, lon1: number, lat2: number, lon2: number) { const p = 0.017453292519943295; // Math.PI / 180 const c = Math.cos; - const a = - 0.5 - - c((lat2 - lat1) * p) / 2 + - (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2; + const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2; return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km }