mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
refactor(ban): drop ipinfo cgnat blast-radius guard (#3062)
This commit is contained in:
1 parent
ca719e7b5e
commit
af49cd6cc4
45 files changed
+10
-2153
No files matched your search
@@ -24,7 +24,6 @@ import type {JoinSourceType} from '@fluxer/constants/src/GuildConstants';
|
||||
import {UnknownGuildMemberError} from '@fluxer/errors/src/domains/guild/UnknownGuildMemberError';
|
||||
import type {GuildMemberResponse} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
|
||||
import type {GuildMemberUpdateRequest} from '@fluxer/schema/src/domains/guild/GuildRequestSchemas';
|
||||
import type {IpInfoService} from '@pkgs/geoip/src/IpInfoService';
|
||||
import type {IRateLimitService} from '@pkgs/rate_limit/src/IRateLimitService';
|
||||
|
||||
export class GuildMemberService {
|
||||
@@ -47,11 +46,10 @@ export class GuildMemberService {
|
||||
rateLimitService: IRateLimitService,
|
||||
private readonly guildAuditLogService: GuildAuditLogService,
|
||||
limitConfigService: LimitConfigService,
|
||||
ipInfoService: IpInfoService,
|
||||
) {
|
||||
this.userRepository = userRepository;
|
||||
this.authService = new GuildMemberAuthService(gatewayService, userRepository);
|
||||
this.validationService = new GuildMemberValidationService(guildRepository, userRepository, ipInfoService);
|
||||
this.validationService = new GuildMemberValidationService(guildRepository, userRepository);
|
||||
this.auditService = new GuildMemberAuditService(guildAuditLogService);
|
||||
this.eventService = new GuildMemberEventService(gatewayService, userCacheService);
|
||||
this.searchIndexService = new GuildMemberSearchIndexService();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {GuildID, UserID} from '@app/api/BrandedTypes';
|
||||
import {getIpBanBlastRadiusVerdict, isSingleIpBanCandidate} from '@app/api/ban/IpBanCgnatGuard';
|
||||
import {isIpBanExempt} from '@app/api/ban/IpBanExemptions';
|
||||
import type {GuildAuditLogService} from '@app/api/guild/GuildAuditLogService';
|
||||
import type {GuildAuditLogChange} from '@app/api/guild/GuildAuditLogTypes';
|
||||
@@ -27,7 +26,6 @@ import {UnknownGuildMemberError} from '@fluxer/errors/src/domains/guild/UnknownG
|
||||
import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';
|
||||
import {isSameIpDecisionMatch} from '@fluxer/ip_utils/src/IpAddress';
|
||||
import type {GuildBanResponse} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
|
||||
import type {IpInfoService} from '@pkgs/geoip/src/IpInfoService';
|
||||
import type {IWorkerService} from '@pkgs/worker/src/contracts/IWorkerService';
|
||||
|
||||
const SECONDS_PER_DAY = 86_400;
|
||||
@@ -42,7 +40,6 @@ export class GuildModerationService {
|
||||
private readonly userCacheService: UserCacheService,
|
||||
private readonly workerService: IWorkerService<WorkerTaskName>,
|
||||
private readonly guildAuditLogService: GuildAuditLogService,
|
||||
private readonly ipInfoService: IpInfoService,
|
||||
) {
|
||||
this.searchIndexService = new GuildMemberSearchIndexService();
|
||||
}
|
||||
@@ -218,7 +215,7 @@ export class GuildModerationService {
|
||||
const userEmail = user?.email?.toLowerCase();
|
||||
for (const ban of bans) {
|
||||
if (ban.userId === userId) throw new BannedFromGuildError();
|
||||
if (isSameIpDecisionMatch(userIp, ban.ipAddress) && (await this.shouldEnforceIpBan(userIp, ban.ipAddress))) {
|
||||
if (isSameIpDecisionMatch(userIp, ban.ipAddress) && !isIpBanExempt(userIp)) {
|
||||
throw new IpBannedFromGuildError();
|
||||
}
|
||||
}
|
||||
@@ -228,35 +225,6 @@ export class GuildModerationService {
|
||||
}
|
||||
}
|
||||
|
||||
private async shouldEnforceIpBan(
|
||||
userIp: string | null | undefined,
|
||||
bannedIp: string | null | undefined,
|
||||
): Promise<boolean> {
|
||||
if (isIpBanExempt(userIp)) {
|
||||
return false;
|
||||
}
|
||||
if (!userIp || !bannedIp || !isSingleIpBanCandidate(bannedIp)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const {cgnat, sharedAccess} = await getIpBanBlastRadiusVerdict(userIp, this.ipInfoService, {
|
||||
source: 'guild.ip_ban',
|
||||
reason: 'join_cgnat_guard',
|
||||
});
|
||||
const highRisk = cgnat || sharedAccess;
|
||||
if (highRisk) {
|
||||
Logger.warn(
|
||||
{userIp, bannedIp},
|
||||
'Skipping guild IP ban match because IPInfo indicates high shared-network blast-radius risk',
|
||||
);
|
||||
}
|
||||
return !highRisk;
|
||||
} catch (error) {
|
||||
Logger.warn({error, userIp, bannedIp}, 'IPInfo blast-radius guard failed while checking guild IP ban');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private serializeBanForAudit(ban: GuildBan): Record<string, unknown> {
|
||||
return {
|
||||
user_id: ban.userId.toString(),
|
||||
|
||||
@@ -58,7 +58,6 @@ import type {
|
||||
import type {GuildUpdateRequest} from '@fluxer/schema/src/domains/guild/GuildRequestSchemas';
|
||||
import type {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
|
||||
import type {ICacheService} from '@pkgs/cache/src/ICacheService';
|
||||
import type {IpInfoService} from '@pkgs/geoip/src/IpInfoService';
|
||||
|
||||
interface StoredAuditLogWebhookResponse extends Omit<AuditLogWebhookResponse, 'type'> {
|
||||
type: number;
|
||||
@@ -113,7 +112,6 @@ export class GuildService {
|
||||
webhookRepository: IWebhookRepository,
|
||||
guildAuditLogService: GuildAuditLogService,
|
||||
limitConfigService: LimitConfigService,
|
||||
ipInfoService: IpInfoService,
|
||||
) {
|
||||
const {
|
||||
cache: cacheService,
|
||||
@@ -153,7 +151,6 @@ export class GuildService {
|
||||
rateLimitService,
|
||||
guildAuditLogService,
|
||||
limitConfigService,
|
||||
ipInfoService,
|
||||
);
|
||||
this.roles = new GuildRoleService(
|
||||
guildRepository,
|
||||
@@ -171,7 +168,6 @@ export class GuildService {
|
||||
userCacheService,
|
||||
workerService,
|
||||
guildAuditLogService,
|
||||
ipInfoService,
|
||||
);
|
||||
this.content = new GuildContentService(
|
||||
guildRepository,
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
import type {GuildID, RoleID, UserID} from '@app/api/BrandedTypes';
|
||||
import {guildIdToRoleId} from '@app/api/BrandedTypes';
|
||||
import {getIpBanBlastRadiusVerdict, isSingleIpBanCandidate} from '@app/api/ban/IpBanCgnatGuard';
|
||||
import {isIpBanExempt} from '@app/api/ban/IpBanExemptions';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import type {GuildMember} from '@app/api/models/GuildMember';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import {Permissions} from '@fluxer/constants/src/ChannelConstants';
|
||||
@@ -17,7 +15,6 @@ import {IpBannedFromGuildError} from '@fluxer/errors/src/domains/guild/IpBannedF
|
||||
import {UnknownGuildRoleError} from '@fluxer/errors/src/domains/guild/UnknownGuildRoleError';
|
||||
import {isSameIpDecisionMatch} from '@fluxer/ip_utils/src/IpAddress';
|
||||
import type {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
|
||||
import type {IpInfoService} from '@pkgs/geoip/src/IpInfoService';
|
||||
|
||||
function ensureNotEveryoneRole(roleId: RoleID, guildId: GuildID, path: string): void {
|
||||
if (roleId === guildIdToRoleId(guildId)) {
|
||||
@@ -29,7 +26,6 @@ export class GuildMemberValidationService {
|
||||
constructor(
|
||||
private readonly guildRepository: IGuildRepositoryAggregate,
|
||||
private readonly userRepository: IUserRepository,
|
||||
private readonly ipInfoService: IpInfoService,
|
||||
) {}
|
||||
|
||||
async validateAndGetRoleIds(params: {
|
||||
@@ -102,38 +98,9 @@ export class GuildMemberValidationService {
|
||||
if (ban.userId === userId) {
|
||||
throw new BannedFromGuildError();
|
||||
}
|
||||
if (isSameIpDecisionMatch(userIp, ban.ipAddress) && (await this.shouldEnforceIpBan(userIp, ban.ipAddress))) {
|
||||
if (isSameIpDecisionMatch(userIp, ban.ipAddress) && !isIpBanExempt(userIp)) {
|
||||
throw new IpBannedFromGuildError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async shouldEnforceIpBan(
|
||||
userIp: string | null | undefined,
|
||||
bannedIp: string | null | undefined,
|
||||
): Promise<boolean> {
|
||||
if (isIpBanExempt(userIp)) {
|
||||
return false;
|
||||
}
|
||||
if (!userIp || !bannedIp || !isSingleIpBanCandidate(bannedIp)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const {cgnat, sharedAccess} = await getIpBanBlastRadiusVerdict(userIp, this.ipInfoService, {
|
||||
source: 'guild.member_ip_ban',
|
||||
reason: 'join_cgnat_guard',
|
||||
});
|
||||
const highRisk = cgnat || sharedAccess;
|
||||
if (highRisk) {
|
||||
Logger.warn(
|
||||
{userIp, bannedIp},
|
||||
'Skipping guild member IP ban match because IPInfo indicates high shared-network blast-radius risk',
|
||||
);
|
||||
}
|
||||
return !highRisk;
|
||||
} catch (error) {
|
||||
Logger.warn({error, userIp, bannedIp}, 'IPInfo CGNAT guard failed while checking guild member IP ban');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user