Skip to content

Commit

Permalink
added whitelist module (skip some protections for whitelisted IPs)
Browse files Browse the repository at this point in the history
will be used to whitelist devconnect ip ranges during the event.
otherwise the whole event with thousands of devs will be limited to 1 concurrent session
  • Loading branch information
pk910 committed Oct 20, 2023
1 parent 517250b commit 0d3be60
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 2 deletions.
40 changes: 40 additions & 0 deletions src/modules/MODULE_HOOKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

ClientConfig
prio 1: captcha, ensname, github, passport, pow

SessionStart
prio 1: captcha, *maintenance_mode_check
prio 2: whitelist
prio 3: ensname
prio 5: *eth_address_check
prio 6: concurrency-limit, ethinfo, ipinfo, mainnet-wallet, passport, recurring-limits
prio 10: pow

SessionRestore
prio 10: pow

SessionInfo
prio 1: passport, pow

SessionRewardFactor
prio 5: faucet-outflow, github, passport
prio 6: faucet-balance, ipinfo, whitelist

SessionRewarded
prio 5: faucet-outflow

SessionIpChange
prio 2: whitelist
prio 6: concurrency-limit, ipinfo

SessionComplete
prio 5: github
prio 10: pow

SessionClaim
prio 1: captcha

SessionClaimed

SessionClose

2 changes: 2 additions & 0 deletions src/modules/concurrency-limit/ConcurrencyLimitModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export class ConcurrencyLimitModule extends BaseModule<IConcurrencyLimitConfig>
}

private async processSessionStart(session: FaucetSession): Promise<void> {
if(session.getSessionData<Array<string>>("skip.modules", []).indexOf(this.moduleName) !== -1)
return;
this.checkLimit(session);
}

Expand Down
3 changes: 3 additions & 0 deletions src/modules/ethinfo/EthInfoModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export class EthInfoModule extends BaseModule<IEthInfoConfig> {
}

private async processSessionStart(session: FaucetSession, userInput: any): Promise<void> {
if(session.getSessionData<Array<string>>("skip.modules", []).indexOf(this.moduleName) !== -1)
return;

let targetAddr = session.getTargetAddr();
let ethWalletManager = ServiceManager.GetService(EthWalletManager);

Expand Down
2 changes: 2 additions & 0 deletions src/modules/github/GithubModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export class GithubModule extends BaseModule<IGithubConfig> {
}

private async processSessionStart(session: FaucetSession, userInput: any): Promise<void> {
if(session.getSessionData<Array<string>>("skip.modules", []).indexOf(this.moduleName) !== -1)
return;
let infoOpts: IGithubInfoOpts = {
loadOwnRepo: false,
};
Expand Down
2 changes: 2 additions & 0 deletions src/modules/ipinfo/IPInfoModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export class IPInfoModule extends BaseModule<IIPInfoConfig> {
}

private async processSessionStart(session: FaucetSession): Promise<void> {
if(session.getSessionData<Array<string>>("skip.modules", []).indexOf(this.moduleName) !== -1)
return;
let remoteIp = session.getRemoteIP();
let ipInfo: IIPInfo;
try {
Expand Down
2 changes: 2 additions & 0 deletions src/modules/mainnet-wallet/MainnetWalletModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export class MainnetWalletModule extends BaseModule<IMainnetWalletConfig> {
}

private async processSessionStart(session: FaucetSession, userInput: any): Promise<void> {
if(session.getSessionData<Array<string>>("skip.modules", []).indexOf(this.moduleName) !== -1)
return;
let targetAddr = session.getTargetAddr();

if(this.moduleConfig.minBalance > 0) {
Expand Down
2 changes: 2 additions & 0 deletions src/modules/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { MainnetWalletModule } from "./mainnet-wallet/MainnetWalletModule";
import { PassportModule } from "./passport/PassportModule";
import { PoWModule } from "./pow/PoWModule";
import { RecurringLimitsModule } from "./recurring-limits/RecurringLimitsModule";
import { WhitelistModule } from "./whitelist/WhitelistModule";

export const MODULE_CLASSES = {
"captcha": CaptchaModule,
Expand All @@ -24,4 +25,5 @@ export const MODULE_CLASSES = {
"passport": PassportModule,
"pow": PoWModule,
"recurring-limits": RecurringLimitsModule,
"whitelist": WhitelistModule,
}
2 changes: 2 additions & 0 deletions src/modules/passport/PassportModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export class PassportModule extends BaseModule<IPassportConfig> {
}

private async processSessionStart(session: FaucetSession): Promise<void> {
if(session.getSessionData<Array<string>>("skip.modules", []).indexOf(this.moduleName) !== -1)
return;
let targetAddr = session.getTargetAddr();
let passportInfo = await this.passportResolver.getPassport(targetAddr);
session.setSessionData("passport.refresh", Math.floor(new Date().getTime() / 1000));
Expand Down
3 changes: 3 additions & 0 deletions src/modules/pow/PoWModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ export class PoWModule extends BaseModule<IPoWConfig> {
}

private async processSessionStart(session: FaucetSession): Promise<void> {
if(session.getSessionData<Array<string>>("skip.modules", []).indexOf(this.moduleName) !== -1)
return;

session.addBlockingTask(this.moduleName, "mining", this.moduleConfig.powSessionTimeout); // this prevents the session from progressing to claimable before this module allows it
session.setDropAmount(0n);

Expand Down
2 changes: 2 additions & 0 deletions src/modules/recurring-limits/RecurringLimitsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export class RecurringLimitsModule extends BaseModule<IRecurringLimitsConfig> {
}

private async processSessionStart(session: FaucetSession, userInput: any): Promise<void> {
if(session.getSessionData<Array<string>>("skip.modules", []).indexOf(this.moduleName) !== -1)
return;
await Promise.all(this.moduleConfig.limits.map((limit) => this.checkLimit(session, limit)));
}

Expand Down
25 changes: 25 additions & 0 deletions src/modules/whitelist/WhitelistConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { IBaseModuleConfig } from "../BaseModule";

export interface IWhitelistConfig extends IBaseModuleConfig {
whitelistPattern: { // ip info pattern based restrictions
[pattern: string]: IWhitelistEntryConfig; // percentage of reward per share if IP info matches regex pattern
};
whitelistFile: null | { // ip info pattern based restrictions from file
yaml: string|string[]; // path to yaml file (for more actions/kill messages/etc.)
refresh: number; // refresh interval
};
}

export interface IWhitelistEntryConfig {
reward: number;
skipModules?: string[];
msgkey?: string;
message?: string;
notify?: boolean|string;
}

export const defaultConfig: IWhitelistConfig = {
enabled: false,
whitelistPattern: {},
whitelistFile: null,
}
115 changes: 115 additions & 0 deletions src/modules/whitelist/WhitelistModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import * as fs from 'fs';
import YAML from 'yaml'
import { ServiceManager } from "../../common/ServiceManager";
import { FaucetSession } from "../../session/FaucetSession";
import { BaseModule } from "../BaseModule";
import { ModuleHookAction } from "../ModuleManager";
import { FaucetError } from '../../common/FaucetError';
import { defaultConfig, IWhitelistConfig, IWhitelistEntryConfig } from "./WhitelistConfig";
import { resolveRelativePath } from "../../config/FaucetConfig";
import { ISessionRewardFactor } from '../../session/SessionRewardFactor';
import { FaucetLogLevel, FaucetProcess } from '../../common/FaucetProcess';

export class WhitelistModule extends BaseModule<IWhitelistConfig> {
protected readonly moduleDefaultConfig = defaultConfig;
private cachedWhitelistEntries: [pattern: string, restriction: IWhitelistEntryConfig][];
private cachedWhitelistRefresh: number;

protected override async startModule(): Promise<void> {
this.moduleManager.addActionHook(
this, ModuleHookAction.SessionStart, 2, "Whitelist check",
(session: FaucetSession) => this.processSessionStart(session)
);
this.moduleManager.addActionHook(
this, ModuleHookAction.SessionIpChange, 2, "Whitelist check",
(session: FaucetSession) => this.processSessionStart(session)
);
this.moduleManager.addActionHook(
this, ModuleHookAction.SessionRewardFactor, 6, "whitelist factor",
(session: FaucetSession, rewardFactors: ISessionRewardFactor[]) => this.processSessionRewardFactor(session, rewardFactors)
);
}

protected override stopModule(): Promise<void> {
return Promise.resolve();
}

protected override onConfigReload(): void {
this.cachedWhitelistRefresh = 0;
}

private async processSessionStart(session: FaucetSession): Promise<void> {
let whitelistEntry = this.getSessionWhitelistEntry(session);
if(whitelistEntry) {
session.setSessionData("whitelist", true);
if(whitelistEntry.skipModules)
session.setSessionData("skip.modules", whitelistEntry.skipModules);
if(typeof whitelistEntry.reward === "number")
session.setSessionData("whitelist.factor", whitelistEntry.reward);
}
}

private async processSessionRewardFactor(session: FaucetSession, rewardFactors: ISessionRewardFactor[]) {
let rewardPerc = session.getSessionData("whitelist.factor", 100);
if(rewardPerc !== 100) {
rewardFactors.push({
factor: rewardPerc / 100,
module: this.moduleName,
});
}
}

public refreshCachedWhitelistEntries(force?: boolean) {
let now = Math.floor((new Date()).getTime() / 1000);
let refresh = this.moduleConfig.whitelistFile ? this.moduleConfig.whitelistFile.refresh : 30;
if(this.cachedWhitelistRefresh > now - refresh && !force)
return;

this.cachedWhitelistRefresh = now;
this.cachedWhitelistEntries = [];
if(this.moduleConfig.whitelistPattern) {
Object.keys(this.moduleConfig.whitelistPattern).forEach((pattern) => {
let entry = this.moduleConfig.whitelistPattern[pattern];
this.cachedWhitelistEntries.push([pattern, entry]);
});
}

if(this.moduleConfig.whitelistFile && this.moduleConfig.whitelistFile.yaml) {
// load yaml file
if(Array.isArray(this.moduleConfig.whitelistFile.yaml))
this.moduleConfig.whitelistFile.yaml.forEach((file) => this.refreshCachedWhitelistEntriesFromYaml(resolveRelativePath(file)));
else
this.refreshCachedWhitelistEntriesFromYaml(resolveRelativePath(this.moduleConfig.whitelistFile.yaml));
}
}

private refreshCachedWhitelistEntriesFromYaml(yamlFile: string) {
if(!fs.existsSync(yamlFile))
return;

let yamlSrc = fs.readFileSync(yamlFile, "utf8");
let yamlObj = YAML.parse(yamlSrc);

if(Array.isArray(yamlObj.restrictions)) {
yamlObj.restrictions.forEach((entry) => {
let pattern = entry.pattern;
delete entry.pattern;
this.cachedWhitelistEntries.push([pattern, entry]);
})
}
}

private getSessionWhitelistEntry(session: FaucetSession): IWhitelistEntryConfig|null {
let remoteIp = session.getRemoteIP();
this.refreshCachedWhitelistEntries();
for(let i = 0; i < this.cachedWhitelistEntries.length; i++) {
let entry = this.cachedWhitelistEntries[i];
if(remoteIp.match(new RegExp(entry[0], "mi"))) {
return entry[1];
}
}
return null;
}


}
4 changes: 2 additions & 2 deletions src/session/FaucetSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,8 @@ export class FaucetSession {
this.targetAddr = addr;
}

public getSessionData(key: string): any {
return this.sessionDataDict[key];
public getSessionData<T = any>(key: string, defval?: T): T {
return key in this.sessionDataDict ? this.sessionDataDict[key] : defval;
}

public setSessionData(key: string, value: any) {
Expand Down

0 comments on commit 0d3be60

Please sign in to comment.