How can we block an IP Address using ETERNITY Workflow Framework?
What is Eternity Workflow Framework?
ETERNITY Framework GitHub Link
Block IP Address
Let's consider a scenario, we want to block the user if they failed to authenticate multiple times in some duration.
Eventually, we end up making tables in the database, and then we have to run a cleanup operation. And sometimes we want to remove IP from the blacklist.
With help of Eternity Workflow Framework, it is very easy to create the same logic, which is durable, available for multiple servers, and does not require creating extra tables/fields. This is because the Eternity framework handles storage automatically.
To create such a workflow, we need to first understand what Eternity offers.
- You can initiate a workflow.
- It stays in execution, and it waits for an external event for a certain duration, otherwise it times out.
- Workflow is deleted automatically after the execution has finished.
So considering this, let's write small logic:-
- Before processing the request, check if workflow exists for user IP address with
Result = 'Banned'
. - If it exists, then we block it till some time after the last update.
- If the user fails to authenticate, we create a new workflow
BanUserByIP
, if it does not exist, if it exists, we raise an event as an additional offense.
Inside workflow,
- We will set
PreseveTimeout
to 5 minutes. - We will wait for an external event for additional offense, and we will increment the counter with a maximum wait time of 1 minute.
- If the counter is more than 3, we return the workflow with the string "Banned". This will be released after 5 minutes due to
PreserveTimeout
as workflow will be deleted from the storage.
public class BanUserByIP: Workflow<BanUserByIP, string, string> {
public const string Offense = nameof(Offense);
public async Task<string> RunAsync(string ip) {
PreserveTimeSpan = TimeSpan.FromMinutes(5);
for(int i = 0; i < 3; i++) {
var (name,result) = await this.WaitForExternalEvents(
TimeSpan.FromSeconds(60),
Offense);
if(name == Offense) {
continue;
}
// no offense in 60 seconds
// delete immediately...
PreserveTimeSpan = TimeSpan.Zero;
return "NotBanned";
}
return "Banned";
}
}
[HttpGet]
public async Task<IActionResult> LoginPage(
[FromService] EternityContext context
) {
var ipAddress = ....
// azure table storage requires some encoding
var convertIPAddress = Uri.EscapeDataString(ipAddress);
var r = await BanUserByIP.GetStatusAsync(context,
convertIPAddress);
if(r?.Result == "Banned") {
return Forbidden();
}
....
}
[HttpPost]
public async Task<IActionResult> LoginPage(
[FromService] EternityContext context,
[FromBody] Body model
) {
if(loginFailed) {
var ipAddress = ....
// azure table storage requires some encoding
var convertIPAddress = Uri.EscapeDataString(ipAddress);
var r = await BanUserByIP.GetStatusAsync(context,
convertIPAddress);
if(r == null) {
// create a new workflow
await BanUserByIP.CreateAsync(context,
convertIPAddress);
} else {
// register additional offense
await context.RaiseEventAsync(
convertIPAddress,
BanUserByIP.Offense);
}
}
...
}
As you can see, the logic is very well written in C#, and the changes in the timeout or modifying logic will not require adding/removing fields from the database.
Eternity framework is designed to run on multiple servers, the ban
will span across all the servers using same Eternity Storage.
Like | Comment | Save | Share |