diff --git a/packages/user-access-policy/src/find.ts b/packages/user-access-policy/src/find.ts new file mode 100644 index 000000000..ae8d244b4 --- /dev/null +++ b/packages/user-access-policy/src/find.ts @@ -0,0 +1,68 @@ +import * as fs from "node:fs"; +import { getLoggerDefault } from "@prosopo/common"; +import { getIPAddress } from "@prosopo/provider"; +import mongoose from "mongoose"; +import { RulesMongooseStorage } from "./rules/mongoose/rulesMongooseStorage.js"; +import { getRuleMongooseSchema } from "./rules/mongoose/schemas/getRuleMongooseSchema.js"; + +function randomIntFromInterval(min: number, max: number) { + // min and max included + return Math.floor(Math.random() * (max - min + 1) + min); +} + +const main = async (ips: string[]) => { + const mongoConnection = await mongoose.connect( + "mongodb://root:root@localhost:27017/prosopo", + { + authSource: "admin", + }, + ); + + const model = mongoConnection.model( + "UserAccessRules", + getRuleMongooseSchema(), + undefined, + { overwriteModels: true }, + ); + + const logger = getLoggerDefault(); + + await model.syncIndexes(); + + const rulesStorage = new RulesMongooseStorage(logger, model); + + while (true) { + const random = randomIntFromInterval(0, ips.length); + const ipStr = ips[random]; + if (ipStr) { + const ip = getIPAddress(ipStr); + + const rule = await rulesStorage.find({ + clientId: "5E1bGh2NgRb8dD4NC5bQKRbgiCz8oBc2EhAuYJzy99q3iJ3s", + userIpAddress: ip, + }); + + console.log("rule", rule); + } + } +}; + +const ips = fs + .readFileSync( + "/home/chris/Documents/prosopo/shared/Data/2captcha/blockedIps2025-02-12.csv", + "utf8", + ) + .split("\n") + .slice(1); + +console.log("ips", ips); + +main(ips) + .then(() => { + console.log("done"); + process.exit(); + }) + .catch((error) => { + console.error("error", error); + process.exit(1); + }); diff --git a/packages/user-access-policy/src/rules/mongoose/rulesMongooseStorage.ts b/packages/user-access-policy/src/rules/mongoose/rulesMongooseStorage.ts index c5c994acd..984755ac9 100644 --- a/packages/user-access-policy/src/rules/mongoose/rulesMongooseStorage.ts +++ b/packages/user-access-policy/src/rules/mongoose/rulesMongooseStorage.ts @@ -87,13 +87,66 @@ class RulesMongooseStorage implements RulesStorage { if (!this.readingModel) { throw this.modelNotSetProsopoError(); } + let mongooseRecords: RuleMongooseRecord[] = []; + let ruleRecords: RuleRecord[] = []; + + // If clientId is included, first check if there are any records with the clientId. + if (filters.clientId) { + const clientIdQuery = this.createSearchQuery( + filters, + false, + filterSettings, + ); + const clientIdRecords = await this.readingModel + .find(clientIdQuery) + .lean() + .exec(); + if (clientIdRecords.length > 0) { + // Query ranges first to avoid unnecessary queries over individual IPs. + const rangeQuery = this.createSearchQuery( + filters, + true, + filterSettings, + ); + + mongooseRecords = await this.readingModel + .find(rangeQuery) + .lean() + .exec(); + + if (mongooseRecords.length === 0) { + const ipQuery = this.createSearchQuery( + filters, + false, + filterSettings, + ); + mongooseRecords = await this.readingModel.find(ipQuery).lean().exec(); + } + } else { + // Query ranges first to avoid unnecessary queries over individual IPs. + const rangeQuery = this.createSearchQuery( + filters, + true, + filterSettings, + ); + + mongooseRecords = await this.readingModel + .find(rangeQuery) + .lean() + .exec(); + + if (mongooseRecords.length === 0) { + const ipQuery = this.createSearchQuery( + filters, + false, + filterSettings, + ); + mongooseRecords = await this.readingModel.find(ipQuery).lean().exec(); + } + } - const query = this.createSearchQuery(filters, filterSettings); - - const mongooseRecords = await this.readingModel.find(query).lean().exec(); - - const ruleRecords = - this.convertMongooseRecordsToRuleRecords(mongooseRecords); + ruleRecords = this.convertMongooseRecordsToRuleRecords(mongooseRecords); + } return ruleRecords; } @@ -124,6 +177,7 @@ class RulesMongooseStorage implements RulesStorage { protected createSearchQuery( filters: SearchRuleFilters, + rangeQuery: boolean, filterSettings?: SearchRuleFilterSettings, ): object { const includeRecordsWithoutClientId = @@ -138,6 +192,7 @@ class RulesMongooseStorage implements RulesStorage { const queryFilters = this.getSearchQueryFilters( filters, includeRecordsWithPartialFilterMatches, + rangeQuery, ); return { @@ -148,6 +203,7 @@ class RulesMongooseStorage implements RulesStorage { protected getSearchQueryFilters( filters: SearchRuleFilters, includeRecordsWithPartialFilterMatches: boolean, + rangeQuery: boolean, ): object[] { const queryFilters = []; @@ -156,12 +212,17 @@ class RulesMongooseStorage implements RulesStorage { } if (undefined !== filters.userIpAddress) { - queryFilters.push(this.getFilterByUserIp(filters.userIpAddress)); + queryFilters.push( + this.getFilterByUserIp(filters.userIpAddress, rangeQuery), + ); } - return includeRecordsWithPartialFilterMatches && queryFilters.length > 1 - ? [{ $or: queryFilters }] - : queryFilters; + const finalQuery = + includeRecordsWithPartialFilterMatches && queryFilters.length > 1 + ? [{ $or: queryFilters }] + : queryFilters; + + return finalQuery; } protected getFilterByClientId( @@ -182,24 +243,38 @@ class RulesMongooseStorage implements RulesStorage { : clientIdFilter; } - protected getFilterByUserIp(userIpAddress: IPAddress | null): object { - return null !== userIpAddress - ? this.getFilterByUserIpAddress(userIpAddress) - : { userIp: null }; + protected getFilterByUserIp( + userIpAddress: IPAddress | null, + rangeQuery: boolean, + ): object { + if (null !== userIpAddress) { + const userIpAsNumeric = this.getUserIpAsNumeric(userIpAddress); + const isIpV4 = userIpAddress instanceof Address4; + if (rangeQuery) { + return this.getFilterByUserIpRange(userIpAsNumeric, isIpV4); + } + return this.getFilterByUserIpAddress(userIpAsNumeric, isIpV4); + } + return { userIp: null }; } - protected getFilterByUserIpAddress(userIpAddress: IPAddress): object { + protected getUserIpAsNumeric(userIpAddress: IPAddress): string | bigint { const isIpV4 = userIpAddress instanceof Address4; - const userIpVersion = isIpV4 ? RuleIpVersion.v4 : RuleIpVersion.v6; - - const userIpAsNumeric = isIpV4 + return isIpV4 ? userIpAddress.bigInt() : // we must have the exact same string length to guarantee the right comparison. userIpAddress .bigInt() .toString() .padStart(RULE_IPV6_NUMERIC_MAX_LENGTH, "0"); + } + + protected getFilterByUserIpAddress( + userIpAsNumeric: string | bigint, + isIpV4: boolean, + ): object { + const userIpVersion = isIpV4 ? RuleIpVersion.v4 : RuleIpVersion.v6; const userIpKey = userIpVersion === RuleIpVersion.v4 @@ -215,17 +290,32 @@ class RulesMongooseStorage implements RulesStorage { : "userIp.v6.mask.rangeMaxAsNumericString"; return { - $or: [ - { [userIpKey]: userIpAsNumeric }, - { - [rangeMinKey]: { - $lte: userIpAsNumeric, - }, - [rangeMaxKey]: { - $gte: userIpAsNumeric, - }, - }, - ], + [userIpKey]: userIpAsNumeric, + }; + } + + protected getFilterByUserIpRange( + userIpAsNumeric: string | bigint, + isIpV4: boolean, + ): object { + const userIpVersion = isIpV4 ? RuleIpVersion.v4 : RuleIpVersion.v6; + + const rangeMinKey = + userIpVersion === RuleIpVersion.v4 + ? "userIp.v4.mask.rangeMinAsNumeric" + : "userIp.v6.mask.rangeMinAsNumericString"; + const rangeMaxKey = + userIpVersion === RuleIpVersion.v4 + ? "userIp.v4.mask.rangeMaxAsNumeric" + : "userIp.v6.mask.rangeMaxAsNumericString"; + + return { + [rangeMinKey]: { + $lte: userIpAsNumeric, + }, + [rangeMaxKey]: { + $gte: userIpAsNumeric, + }, }; }