Skip to content

Commit

Permalink
Add API endpoints for adding and removing block rules (#1594)
Browse files Browse the repository at this point in the history
  • Loading branch information
forgetso authored Dec 23, 2024
1 parent 2dd7ee8 commit cf2cf7f
Show file tree
Hide file tree
Showing 20 changed files with 331 additions and 140 deletions.
16 changes: 16 additions & 0 deletions packages/cli/src/RateLimiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,21 @@ export const getRateLimitConfig = () => {
windowMs: process.env.PROSOPO_GET_FR_CAPTCHA_CHALLENGE_WINDOW,
limit: process.env.PROSOPO_GET_FR_CAPTCHA_CHALLENGE_LIMIT,
},
[AdminApiPaths.BlockRuleIPAdd]: {
windowMs: process.env.PROSOPO_BLOCK_RULE_IP_ADD_WINDOW,
limit: process.env.PROSOPO_BLOCK_RULE_IP_ADD_LIMIT,
},
[AdminApiPaths.BlockRuleIPRemove]: {
windowMs: process.env.PROSOPO_BLOCK_RULE_IP_REMOVE_WINDOW,
limit: process.env.PROSOPO_BLOCK_RULE_IP_REMOVE_LIMIT,
},
[AdminApiPaths.BlocKRuleUserAdd]: {
windowMs: process.env.PROSOPO_BLOCK_RULE_USER_ADD_WINDOW,
limit: process.env.PROSOPO_BLOCK_RULE_USER_ADD_LIMIT,
},
[AdminApiPaths.BlockRuleUserRemove]: {
windowMs: process.env.PROSOPO_BLOCK_RULE_USER_REMOVE_WINDOW,
limit: process.env.PROSOPO_BLOCK_RULE_USER_REMOVE_LIMIT,
},
};
};
38 changes: 23 additions & 15 deletions packages/cli/src/commands/addBlockRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ import type { KeyringPair } from "@polkadot/keyring/types";
import { LogLevel, type Logger, getLogger } from "@prosopo/common";
import { ProviderEnvironment } from "@prosopo/env";
import { Tasks } from "@prosopo/provider";
import type { CaptchaConfig, ProsopoConfigOutput } from "@prosopo/types";
import {
AddBlockRulesIPSpec,
AddBlockRulesUserSpec,
type ProsopoCaptchaCountConfigSchemaOutput,
type ProsopoConfigOutput,
} from "@prosopo/types";
import type { ArgumentsCamelCase, Argv } from "yargs";
import * as z from "zod";
import { loadJSONFile } from "../files.js";

export default (
pair: KeyringPair,
Expand Down Expand Up @@ -78,7 +81,7 @@ export default (
const env = new ProviderEnvironment(config, pair);
await env.isReady();
const tasks = new Tasks(env);
let captchaConfig: CaptchaConfig | undefined;
let captchaConfig: ProsopoCaptchaCountConfigSchemaOutput | undefined;
if (argv.solved) {
captchaConfig = {
solved: {
Expand All @@ -92,23 +95,28 @@ export default (

if (argv.ips) {
await tasks.clientTaskManager.addIPBlockRules(
argv.ips as unknown as string[],
argv.global as boolean,
argv.hardBlock as boolean,
argv.dapp as unknown as string,
captchaConfig,
AddBlockRulesIPSpec.parse({
ips: argv.ips,
global: argv.global,
hardBlock: argv.hardBlock,
dapp: argv.dapp,
captchaConfig,
}),
);
logger.info("IP Block rules added");
}
if (argv.users) {
await tasks.clientTaskManager.addUserBlockRules(
argv.users as unknown as string[],
argv.hardBlock as boolean,
argv.global as boolean,
argv.dapp as unknown as string,
captchaConfig,
AddBlockRulesUserSpec.parse({
users: argv.users,
global: argv.global,
hardBlock: argv.hardBlock,
dapp: argv.dapp,
captchaConfig,
}),
);
logger.info("User Block rules added");
}
logger.info("IP Block rules added");
} catch (err) {
logger.error(err);
}
Expand Down
37 changes: 36 additions & 1 deletion packages/database/src/databases/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1455,7 +1455,7 @@ export class ProviderDatabase
}

/**
* @description Check if a request has a blocking rule associated with it
* @description Store IP blocking rule records
*/
async storeIPBlockRuleRecords(rules: IPBlockRuleRecord[]) {
await this.tables?.ipblockrules.bulkWrite(
Expand All @@ -1469,6 +1469,21 @@ export class ProviderDatabase
);
}

/**
* @description Remove IP blocking rule records
*/
async removeIPBlockRuleRecords(ipAddresses: bigint[], dappAccount?: string) {
const filter: {
[key in keyof Pick<IPBlockRuleRecord, "ip">]: { $in: number[] };
} & {
[key in keyof Pick<IPBlockRuleRecord, "dappAccount">]?: string; // Optional `dappAccount` key
} = { ip: { $in: ipAddresses.map(Number) } };
if (dappAccount) {
filter.dappAccount = dappAccount;
}
await this.tables?.ipblockrules.deleteMany(filter);
}

/**
* @description Check if a request has a blocking rule associated with it
*/
Expand Down Expand Up @@ -1503,4 +1518,24 @@ export class ProviderDatabase
})),
);
}

/**
* @description Remove user blocking rule records
*/
async removeUserBlockRuleRecords(
userAccounts: string[],
dappAccount?: string,
) {
const filter: {
[key in keyof Pick<UserAccountBlockRule, "userAccount">]: {
$in: string[];
};
} & {
[key in keyof Pick<UserAccountBlockRule, "dappAccount">]?: string; // Optional `dappAccount` key
} = { userAccount: { $in: userAccounts } };
if (dappAccount) {
filter.dappAccount = dappAccount;
}
await this.tables?.userblockrules.deleteMany(filter);
}
}
65 changes: 65 additions & 0 deletions packages/provider/src/api/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ import { Logger, logError } from "@prosopo/common";
// See the License for the specific language governing permissions and
// limitations under the License.
import {
AddBlockRulesIPSpec,
AddBlockRulesUserSpec,
AdminApiPaths,
type ApiResponse,
BlockRuleIPAddBody,
RegisterSitekeyBody,
RemoveBlockRulesIPSpec,
RemoveBlockRulesUserSpec,
} from "@prosopo/types";
import type { ProviderEnvironment } from "@prosopo/types-env";
import { Router } from "express";
Expand All @@ -42,5 +47,65 @@ export function prosopoAdminRouter(env: ProviderEnvironment): Router {
}
});

router.post(AdminApiPaths.BlockRuleIPAdd, async (req, res, next) => {
try {
tasks.logger.info("Adding block rules");
const parsed = AddBlockRulesIPSpec.parse(req.body);
await tasks.clientTaskManager.addIPBlockRules(parsed);
const response: ApiResponse = {
status: "success",
};
res.json(response);
} catch (err) {
logError(err, tasks.logger);
res.status(400).send("An internal server error occurred.");
}
});

router.post(AdminApiPaths.BlockRuleIPRemove, async (req, res, next) => {
try {
tasks.logger.info("Removing block rules");
const parsed = RemoveBlockRulesIPSpec.parse(req.body);
await tasks.clientTaskManager.removeIPBlockRules(parsed);
const response: ApiResponse = {
status: "success",
};
res.json(response);
} catch (err) {
logError(err, tasks.logger);
res.status(400).send("An internal server error occurred.");
}
});

router.post(AdminApiPaths.BlocKRuleUserAdd, async (req, res, next) => {
try {
tasks.logger.info("Adding block rules");
const parsed = AddBlockRulesUserSpec.parse(req.body);
await tasks.clientTaskManager.addUserBlockRules(parsed);
const response: ApiResponse = {
status: "success",
};
res.json(response);
} catch (err) {
logError(err, tasks.logger);
res.status(400).send("An internal server error occurred.");
}
});

router.post(AdminApiPaths.BlockRuleUserRemove, async (req, res, next) => {
try {
tasks.logger.info("Removing block rules");
const parsed = RemoveBlockRulesUserSpec.parse(req.body);
await tasks.clientTaskManager.removeUserBlockRules(parsed);
const response: ApiResponse = {
status: "success",
};
res.json(response);
} catch (err) {
logError(err, tasks.logger);
res.status(400).send("An internal server error occurred.");
}
});

return router;
}
7 changes: 4 additions & 3 deletions packages/provider/src/rules/ip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type { BlockRule, IProviderDatabase } from "@prosopo/types-database";
import type { Address4, Address6 } from "ip-address";

import type { BlockRule, IPAddress } from "@prosopo/types";
import type { IProviderDatabase } from "@prosopo/types-database";

export const checkIpRules = async (
db: IProviderDatabase,
ipAddress: Address4 | Address6,
ipAddress: IPAddress,
dapp: string,
): Promise<BlockRule | undefined> => {
const rule = await db.getIPBlockRuleRecord(ipAddress.bigInt());
Expand Down
4 changes: 3 additions & 1 deletion packages/provider/src/rules/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type { BlockRule, IProviderDatabase } from "@prosopo/types-database";

import type { BlockRule } from "@prosopo/types";
import type { IProviderDatabase } from "@prosopo/types-database";

export const checkUserRules = async (
db: IProviderDatabase,
Expand Down
94 changes: 60 additions & 34 deletions packages/provider/src/tasks/client/clientTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,23 @@ import { validateAddress } from "@polkadot/util-crypto/address";
import { type Logger, ProsopoApiError } from "@prosopo/common";
import { CaptchaDatabase, ClientDatabase } from "@prosopo/database";
import {
type CaptchaConfig,
type AddBlockRulesIP,
type AddBlockRulesUser,
BlockRuleType,
type IUserSettings,
type ProsopoConfigOutput,
type RemoveBlockRulesIP,
type RemoveBlockRulesUser,
ScheduledTaskNames,
ScheduledTaskStatus,
} from "@prosopo/types";
import {
BlockRuleType,
type ClientRecord,
type IPAddressBlockRule,
type IProviderDatabase,
type PoWCaptchaStored,
type UserAccountBlockRule,
type UserCommitment,
import type {
ClientRecord,
IPAddressBlockRule,
IProviderDatabase,
PoWCaptchaStored,
UserAccountBlockRule,
UserCommitment,
} from "@prosopo/types-database";
import { parseUrl } from "@prosopo/util";
import { getIPAddress } from "../../util.js";
Expand Down Expand Up @@ -218,48 +221,71 @@ export class ClientTaskManager {
]);
}

async addIPBlockRules(
ips: string[],
global: boolean,
hardBlock: boolean,
dappAccount?: string,
captchaConfig?: CaptchaConfig,
): Promise<void> {
const rules: IPAddressBlockRule[] = ips.map((ip) => {
/**
* @description Add IP block rules to the database. Allows specifying mutiple IPs for a single configuration
* @param {AddBlockRulesIP} opts
*/
async addIPBlockRules(opts: AddBlockRulesIP): Promise<void> {
const rules: IPAddressBlockRule[] = opts.ips.map((ip) => {
return {
ip: Number(getIPAddress(ip).bigInt()),
global,
global: opts.global,
type: BlockRuleType.ipAddress,
dappAccount,
hardBlock,
...(captchaConfig && { captchaConfig }),
dappAccount: opts.dappAccount,
hardBlock: opts.hardBlock,
...(opts.captchaConfig && { captchaConfig: opts.captchaConfig }),
};
});
await this.providerDB.storeIPBlockRuleRecords(rules);
}

async addUserBlockRules(
userAccounts: string[],
hardBlock: boolean,
global: boolean,
dappAccount?: string,
captchaConfig?: CaptchaConfig,
): Promise<void> {
validateAddress(dappAccount, false, 42);
const rules: UserAccountBlockRule[] = userAccounts.map((userAccount) => {
/**
* @description Remove IP block rules from the database by IP address and optionally dapp account
* @param {RemoveBlockRulesIP} opts
*/
async removeIPBlockRules(opts: RemoveBlockRulesIP): Promise<void> {
await this.providerDB.removeIPBlockRuleRecords(
opts.ips.map((ip) => getIPAddress(ip).bigInt()),
opts.dappAccount,
);
}

/**
* @description Add user block rules to the database. Allows specifying multiple users for a single configuration
* @param {AddBlockRulesUser} opts
*/
async addUserBlockRules(opts: AddBlockRulesUser): Promise<void> {
validateAddress(opts.dappAccount, false, 42);
const rules: UserAccountBlockRule[] = opts.users.map((userAccount) => {
validateAddress(userAccount, false, 42);
return {
dappAccount,
dappAccount: opts.dappAccount,
userAccount,
type: BlockRuleType.userAccount,
global,
hardBlock,
...(captchaConfig && { captchaConfig }),
global: opts.global,
hardBlock: opts.hardBlock,
...(opts.captchaConfig && { captchaConfig: opts.captchaConfig }),
};
});
await this.providerDB.storeUserBlockRuleRecords(rules);
}

/**
* @description Remove user block rules from the database by user account and optionally dapp account
* @param {RemoveBlockRulesUser} opts
*/
async removeUserBlockRules(opts: RemoveBlockRulesUser): Promise<void> {
if (opts.dappAccount) {
validateAddress(opts.dappAccount, false, 42);
await this.providerDB.removeUserBlockRuleRecords(
opts.users,
opts.dappAccount,
);
} else {
await this.providerDB.removeUserBlockRuleRecords(opts.users);
}
}

isSubdomainOrExactMatch(referrer: string, clientDomain: string): boolean {
if (!referrer || !clientDomain) return false;
if (clientDomain === "*") return true;
Expand Down
Loading

0 comments on commit cf2cf7f

Please sign in to comment.