diff --git a/src/api/Server.ts b/src/api/Server.ts
index 08a1e874..9e4bb937 100644
--- a/src/api/Server.ts
+++ b/src/api/Server.ts
@@ -62,7 +62,7 @@ export class SpacebarServer extends Server {
await ConnectionConfig.init();
await initInstance();
WebAuthn.init();
- await BcryptWorkerPool.Init(4); // TODO: make configurable
+ // await BcryptWorkerPool.Init(8); // TODO: make configurable
const logRequests = process.env["LOG_REQUESTS"] != undefined;
if (logRequests) {
diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts
index d8313135..4f2f4a7d 100644
--- a/src/api/routes/auth/register.ts
+++ b/src/api/routes/auth/register.ts
@@ -144,79 +144,81 @@ router.post(
logTrace("Basic checks");
//region IP checks
- const cacheBlockedIp = (ip: string, reason: string) => {
- recentlyBlockedIps[ip] = {
- firstHit: new Date(),
- lastHit: new Date(),
- hits: 0,
- reason,
+ if (register.enableAbuseIpDb || register.enableIpData) {
+ const cacheBlockedIp = (ip: string, reason: string) => {
+ recentlyBlockedIps[ip] = {
+ firstHit: new Date(),
+ lastHit: new Date(),
+ hits: 0,
+ reason,
+ };
+ console.log(`[Register] ${ip} blocked from registration:`, reason);
};
- console.log(`[Register] ${ip} blocked from registration:`, reason);
- };
-
- if (!regTokenUsed && recentlyBlockedIps[ip]) {
- if (new TimeSpan(recentlyBlockedIps[ip].firstHit.getTime(), new Date().getTime()).totalHours >= 24) delete recentlyBlockedIps[ip];
- else {
- recentlyBlockedIps[ip].lastHit = new Date();
- recentlyBlockedIps[ip].hits++;
- console.log(
- `[Register] ${ip} blocked from registration: blocked since ${recentlyBlockedIps[ip].firstHit} with ${recentlyBlockedIps[ip].hits} hits and reason:`,
- recentlyBlockedIps[ip].reason,
- );
- throw new HTTPError("Your IP is blocked from registration");
- }
- }
- if (!regTokenUsed && register.enableAbuseIpDb) {
- const blacklist = await AbuseIpDbClient.getBlacklist();
- if (blacklist) {
- const entry = blacklist.data.find((e) => e.ipAddress === ip);
-
- if (entry && entry.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
- cacheBlockedIp(ip, `AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`);
+ if (!regTokenUsed && recentlyBlockedIps[ip]) {
+ if (new TimeSpan(recentlyBlockedIps[ip].firstHit.getTime(), new Date().getTime()).totalHours >= 24) delete recentlyBlockedIps[ip];
+ else {
+ recentlyBlockedIps[ip].lastHit = new Date();
+ recentlyBlockedIps[ip].hits++;
+ console.log(
+ `[Register] ${ip} blocked from registration: blocked since ${recentlyBlockedIps[ip].firstHit} with ${recentlyBlockedIps[ip].hits} hits and reason:`,
+ recentlyBlockedIps[ip].reason,
+ );
throw new HTTPError("Your IP is blocked from registration");
}
}
- const checkIp = await AbuseIpDbClient.checkIpAddress(ip);
- if (checkIp?.data && checkIp.data.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
- cacheBlockedIp(ip, `AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`);
- throw new HTTPError("Your IP is blocked from registration");
- }
- }
+ if (!regTokenUsed && register.enableAbuseIpDb) {
+ const blacklist = await AbuseIpDbClient.getBlacklist();
+ if (blacklist) {
+ const entry = blacklist.data.find((e) => e.ipAddress === ip);
- if (!regTokenUsed && register.enableIpData) {
- const ipData = await IpDataClient.getIpInfo(ip);
- if (ipData) {
- if (!ipData.threat) {
- console.log("Invalid IPData.co response, missing threat field", ipData);
- }
- const categories = Object.entries(ipData.threat)
- .filter(([key, value]) => key.startsWith("is_") && value === true)
- .map(([key]) => key.replace("is_", ""));
- const blockedCategories = new Set(categories).intersection(new Set(register.blockIpDataCoThreatTypes));
- if (blockedCategories.size > 0) {
- cacheBlockedIp(ip, `IPData.co threat types ${Array.from(blockedCategories).join(", ")}`);
- throw new HTTPError("Your IP is blocked from registration");
+ if (entry && entry.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
+ cacheBlockedIp(ip, `AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
}
- if (ipData.asn.type && register.blockAsnTypes.includes(ipData.asn.type)) {
- cacheBlockedIp(ip, `IPData.co ASN type ${ipData.asn.type} is blocked`);
+ const checkIp = await AbuseIpDbClient.checkIpAddress(ip);
+ if (checkIp?.data && checkIp.data.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
+ cacheBlockedIp(ip, `AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`);
throw new HTTPError("Your IP is blocked from registration");
- } else if (!ipData.asn.type) {
- console.log("[Register] IPData.co response missing asn.type field", ipData);
}
+ }
- if (ipData.asn.asn && register.blockAsns.includes(ipData.asn.asn)) {
- cacheBlockedIp(ip, `IPData.co ASN ${ipData.asn.name} is blocked`);
- throw new HTTPError("Your IP is blocked from registration");
- } else if (!ipData.asn.asn) {
- console.log("[Register] IPData.co response missing asn.asn field", ipData);
+ if (!regTokenUsed && register.enableIpData) {
+ const ipData = await IpDataClient.getIpInfo(ip);
+ if (ipData) {
+ if (!ipData.threat) {
+ console.log("Invalid IPData.co response, missing threat field", ipData);
+ }
+ const categories = Object.entries(ipData.threat)
+ .filter(([key, value]) => key.startsWith("is_") && value === true)
+ .map(([key]) => key.replace("is_", ""));
+ const blockedCategories = new Set(categories).intersection(new Set(register.blockIpDataCoThreatTypes));
+ if (blockedCategories.size > 0) {
+ cacheBlockedIp(ip, `IPData.co threat types ${Array.from(blockedCategories).join(", ")}`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
+
+ if (ipData.asn.type && register.blockAsnTypes.includes(ipData.asn.type)) {
+ cacheBlockedIp(ip, `IPData.co ASN type ${ipData.asn.type} is blocked`);
+ throw new HTTPError("Your IP is blocked from registration");
+ } else if (!ipData.asn.type) {
+ console.log("[Register] IPData.co response missing asn.type field", ipData);
+ }
+
+ if (ipData.asn.asn && register.blockAsns.includes(ipData.asn.asn)) {
+ cacheBlockedIp(ip, `IPData.co ASN ${ipData.asn.name} is blocked`);
+ throw new HTTPError("Your IP is blocked from registration");
+ } else if (!ipData.asn.asn) {
+ console.log("[Register] IPData.co response missing asn.asn field", ipData);
+ }
}
}
+ logTrace("IP checks");
}
//endregion
- logTrace("IP checks");
// TODO: gift_code_sku_id?
// TODO: check password strength
@@ -306,8 +308,8 @@ router.post(
});
}
// the salt is saved in the password refer to bcrypt docs
- body.password = await BcryptWorkerPool.GetBcryptWorker().hashPassword(body.password, 12);
- // body.password = await bcrypt.hash(body.password, 12);
+ // body.password = await BcryptWorkerPool.GetBcryptWorker().hashPassword(body.password, 12);
+ body.password = await bcrypt.hash(body.password, 12);
} else if (register.password.required) {
throw FieldErrors({
password: {
diff --git a/src/util/entities/Member.ts b/src/util/entities/Member.ts
index 67c7e4c6..00178d26 100644
--- a/src/util/entities/Member.ts
+++ b/src/util/entities/Member.ts
@@ -304,7 +304,7 @@ export class Member extends BaseClassWithoutId {
]);
}
- static async addToGuild(user_id: string, guild_id: string) {
+ static async addToGuild(user_id: string, guild_id: string, isRegistration: boolean = false) {
const totalSw = Stopwatch.startNew();
const incSw = Stopwatch.startNew();
const logTrace = (...data: unknown[]) => {
@@ -312,19 +312,23 @@ export class Member extends BaseClassWithoutId {
console.log("[Member.addToGuild]", ...data, `[${totalSw.elapsed().toString()} (+${incSw.getElapsedAndReset().totalMilliseconds}ms)]`);
};
- const isBanned = await Ban.exists({ where: { guild_id, user_id } });
- if (isBanned) throw DiscordApiErrors.USER_BANNED;
- logTrace("Check bans");
+ if (!isRegistration) {
+ const isBanned = Ban.exists({ where: { guild_id, user_id } });
+ const isMember = Member.exists({ where: { id: user_id, guild_id } });
- if (await Member.exists({ where: { id: user_id, guild_id } })) throw new HTTPError("You are already a member of this guild", 400);
- logTrace("Check existing membership");
+ if (await isBanned) throw DiscordApiErrors.USER_BANNED;
+ logTrace("Check bans");
- const { maxGuilds } = Config.get().limits.user;
- const guild_count = await Member.count({ where: { id: user_id } });
- if (guild_count >= maxGuilds) {
- throw new HTTPError(`You are at the ${maxGuilds} guild limit.`, 403);
+ if (await isMember) throw new HTTPError("You are already a member of this guild", 400);
+ logTrace("Check existing membership");
+
+ const { maxGuilds } = Config.get().limits.user;
+ const guild_count = await Member.count({ where: { id: user_id } });
+ if (guild_count >= maxGuilds) {
+ throw new HTTPError(`You are at the ${maxGuilds} guild limit.`, 403);
+ }
+ logTrace("Enforce max guilds");
}
- logTrace("Enforce max guilds");
const guild = await Guild.findOneOrFail({
where: {
@@ -339,30 +343,6 @@ export class Member extends BaseClassWithoutId {
});
logTrace("Find guild");
- for await (const channel of guild.channels) {
- channel.position = await Channel.calculatePosition(channel.id, guild_id, channelPositionsGuild);
- }
- logTrace("Reorder channels");
-
- const memberCount = await Member.count({ where: { guild_id } });
- logTrace("Get member count");
-
- const memberPreview = (
- await Member.find({
- where: {
- guild_id,
- user: {
- sessions: {
- status: Not("invisible" as const), // lol typescript?
- },
- },
- },
- relations: { user: true, roles: true },
- take: 10,
- })
- ).map((member) => member.toPublicMember());
- logTrace("Calculate member preview");
-
const newMember = Member.create({
id: user_id,
guild_id,
@@ -392,11 +372,41 @@ export class Member extends BaseClassWithoutId {
// Member.save is needed because else the roles relations wouldn't be updated
});
+ let memberCount = 0;
+ let memberPreview: PublicMember[] = [];
+ if (!isRegistration) {
+ for await (const channel of guild.channels) {
+ channel.position = await Channel.calculatePosition(channel.id, guild_id, channelPositionsGuild);
+ }
+
+ logTrace("Reorder channels");
+
+ memberCount = isRegistration ? 0 : await Member.count({ where: { guild_id } });
+ logTrace("Get member count");
+
+ memberPreview = (
+ await Member.find({
+ where: {
+ guild_id,
+ user: {
+ id: Not(user_id),
+ sessions: {
+ status: Not("invisible" as const), // lol typescript?
+ },
+ },
+ },
+ relations: { user: true, roles: true },
+ take: 10,
+ })
+ ).map((member) => member.toPublicMember());
+ logTrace("Calculate member preview");
+ }
+
const user = await User.getPublicUser(user_id);
logTrace("Get user");
await Promise.all([
- newMember.save(),
+ newMember.save(), // TODO: can we somehow insert the roles manually? We have no entity for this... Would skip a few select's
Guild.increment({ id: guild_id }, "member_count", 1),
emitEvent({
event: "GUILD_MEMBER_ADD",
@@ -408,23 +418,25 @@ export class Member extends BaseClassWithoutId {
guild_id,
origin: "util/entities/Member.ts:377/addToGuild(user_id, guild_id)",
} satisfies GuildMemberAddEvent),
- emitEvent({
- event: "GUILD_CREATE",
- data: {
- ...new ReadyGuildDTO(guild).toJSON(),
- members: [...memberPreview, { ...newMember.toPublicMember(), user }],
- member_count: memberCount + 1,
- guild_hashes: {},
- guild_scheduled_events: [],
- joined_at: newMember.joined_at,
- presences: [],
- stage_instances: [],
- threads: [],
- embedded_activities: [],
- voice_states: guild.voice_states.map((x) => x.toPublicVoiceState()),
- },
- user_id,
- } satisfies GuildCreateEvent),
+ isRegistration
+ ? null
+ : emitEvent({
+ event: "GUILD_CREATE",
+ data: {
+ ...new ReadyGuildDTO(guild).toJSON(),
+ members: [...memberPreview, { ...newMember.toPublicMember(), user }],
+ member_count: memberCount + 1,
+ guild_hashes: {},
+ guild_scheduled_events: [],
+ joined_at: newMember.joined_at,
+ presences: [],
+ stage_instances: [],
+ threads: [],
+ embedded_activities: [],
+ voice_states: guild.voice_states.map((x) => x.toPublicVoiceState()),
+ },
+ user_id,
+ } satisfies GuildCreateEvent),
]);
logTrace("Save member info");
@@ -448,13 +460,12 @@ export class Member extends BaseClassWithoutId {
mention_everyone: false,
});
- await message.insert();
- const publicMsg = message.toJSON();
await Promise.all([
+ message.insert(),
emitEvent({
event: "MESSAGE_CREATE",
channel_id: message.channel_id,
- data: publicMsg,
+ data: message.toJSON(),
} satisfies MessageCreateEvent),
Channel.update({ id: welcomeChannelId }, { last_message_id: message.id }),
]);
diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts
index a58b6579..afde3e9b 100644
--- a/src/util/entities/User.ts
+++ b/src/util/entities/User.ts
@@ -363,7 +363,7 @@ export class User extends BaseClass {
const { autoJoin } = Config.get().guild;
if (autoJoin.enabled && autoJoin.guilds.length > 0 && !(bot && !autoJoin.bots)) {
- await Promise.all(autoJoin.guilds.map((guild) => Member.addToGuild(user.id, guild).catch((e) => console.error("[Autojoin]", e))));
+ await Promise.all(autoJoin.guilds.map((guild) => Member.addToGuild(user.id, guild, true).catch((e) => console.error("[Autojoin]", e))));
logTrace("Autojoin", autoJoin.guilds.length, "guilds");
}
diff --git a/src/util/util/workers/bcrypt/BcryptWorkerPool.ts b/src/util/util/workers/bcrypt/BcryptWorkerPool.ts
index bda5a576..1d1705ef 100644
--- a/src/util/util/workers/bcrypt/BcryptWorkerPool.ts
+++ b/src/util/util/workers/bcrypt/BcryptWorkerPool.ts
@@ -58,7 +58,7 @@ class BcryptWorker {
if (msg.type == "hash" && msg.requestId === requestId) {
res((msg as BcryptHashMessage).password);
this.worker.off("message", handler);
- console.log("[BcryptWorker] Got response to hashPassword in", sw.elapsed().toString());
+ if (sw.elapsed().totalMilliseconds > 5000) console.log("[BcryptWorker] Got slow response to hashPassword in", sw.elapsed().toString());
}
};
this.worker.on("message", handler);
@@ -86,7 +86,7 @@ interface BcryptHashVerify extends BcryptWorkerMessage {
//region Worker implementation
if (!isMainThread) {
parentPort!.on("message", async (msg: BcryptWorkerMessage) => {
- console.log("[BcryptWorker] Received", msg.type, "message");
+ // console.log("[BcryptWorker] Received", msg.type, "message");
switch (msg.type) {
case "hash":
parentPort?.postMessage({
|