diff --git a/assets/openapi.json b/assets/openapi.json
index 5d73449d2..39ca2eec6 100644
Binary files a/assets/openapi.json and b/assets/openapi.json differ
diff --git a/assets/schemas.json b/assets/schemas.json
index 3e597f094..4fe2ea401 100644
Binary files a/assets/schemas.json and b/assets/schemas.json differ
diff --git a/src/api/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts
index 81ccbc515..1a41ad4f6 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 @@ export default function rateLimit(opts: {
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 @@ export default function rateLimit(opts: {
}
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 @@ export default function rateLimit(opts: {
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 @@ export default function rateLimit(opts: {
// 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 @@ export async function initRateLimits(app: Router) {
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 12bc9cb07..6e613820f 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 @@ router.post(
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 @@ router.post(
});
}
- 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 @@ router.post(
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 a08c98abe..92f47909d 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 @@ router.get(
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 06e3fe43e..c8ef62731 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 @@ router.post(
});
}
- 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 @@ router.post(
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 @@ router.post(
});
// 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 @@ router.post(
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 @@ router.post(
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 @@ router.post(
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 418f0a529..d4b061be2 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 @@ router.post(
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 @@ router.post(
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 @@ router.post(
},
})) >= 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 b85120d84..d8e343cd2 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 @@ router.post(
});
}
- 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/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts
index e253bef9f..973f212dd 100644
--- a/src/api/routes/guilds/#guild_id/bans.ts
+++ b/src/api/routes/guilds/#guild_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";
@@ -215,7 +215,7 @@ router.put(
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/#guild_id/bulk-ban.ts b/src/api/routes/guilds/#guild_id/bulk-ban.ts
index 3d3ed1c39..a564e2d48 100644
--- a/src/api/routes/guilds/#guild_id/bulk-ban.ts
+++ b/src/api/routes/guilds/#guild_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 @@ router.post(
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/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts
index a7fad818d..a3c5a81b6 100644
--- a/src/api/routes/guilds/#guild_id/regions.ts
+++ b/src/api/routes/guilds/#guild_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 @@ router.get(
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 c77d36eaa..87715f35e 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 @@ router.post(
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 9fdd6fdef..ba2612316 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 @@ router.get(
},
}),
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 914999a71..19408253d 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
}