mirror of
https://github.com/spacebarchat/server.git
synced 2026-07-21 15:30:58 +00:00
Dont send guild_add for autojoin guilds on register, disable bcrypt workers as doing it in thread is slightly faster
This commit is contained in:
+1
-1
@@ -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) {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
+67
-56
@@ -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 }),
|
||||
]);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user