---
name: "CGI::Application::Plugin::RateLimit"
section: "3pm"
platform: "linux"
package: "libcgi-application-plugin-ratelimit-perl"
version: "1.0-4"
release: "Debian 13 (Trixie)"
platforms: ["linux"]
url: "https://new.carta.tech/CGI::Application::Plugin::RateLimit"
---

# CGI::Application::Plugin::RateLimit(3pm)

limits runmode call rate per user

## NAME

CGI::Application::Plugin::RateLimit - limits runmode call rate per user

## SYNOPSIS

```
  use CGI::Application::Plugin::RateLimit;
  sub setup {
    ...
    # call this in your setup routine to set
    my $rate_limit = $self->rate_limit();
    # set the database handle to use
    $rate_limit->dbh($dbh);
    # set the table name to use for storing hits, the default is
    # 'rate_limit_hits'
    $rate_limit->table('rate_limit_hits');
    # keep people from calling 'send' more often than 5 times in 10
    # minutes and 'list' more often than once every 5 seconds.
    $rate_limit->protected_modes(send => {timeframe => '10m',
                                          max_hits  => 5
                                         },
                                 list => {timeframe => '5s',
                                          max_hits  => 1
                                         });
    # you can also protect abstract actions, for example to prevent a
    # flood of failed logins
    $rate_limit->protected_actions(failed_login => {timeframe => '10s',
                                                    max_hits  => 2
                                                   });
    # call this runmode when a violation is detected
    $rate_limit->violation_mode('too_fast_buddy');
    # or, run this callback
    $rate_limit->violation_callback(sub { die(...) });
    # override the default identity function
    # ($ENV{REMOTE_USER} || $ENV{REMOTE_IP})
    $rate_limit->identity_callback(sub { ... });
  }
  # record a hit for an action (not needed for run-modes which are
  # handled automatically)
  $rate_limit->record_hit(action => 'failed_login');
  # check for a violation on an action and handle
  return $self->slow_down_buddy
    if( $rate_limit->check_violation(action => 'failed_login') );
  # revoke the most recent hit for this user, preventing it from
  # counting towards a violation
  $rate_limit->revoke_hit();
  # examine the violation in violation_mode or violation_callback:
  $mode   = $rate_limit->violated_mode;
  $action = $rate_limit->violated_action;
  $limits = $rate_limit->violated_limits;
```

## DESCRIPTION

This module provides protection against a user calling a runmode too frequently.  A typical use-case might be a contact form that sends email.  You'd like to allow your users to send you messages, but thousands of messages from a single user would be a problem.

This module works by maintaining a database of hits to protected runmodes.  It then checks this database to determine if a new hit should be allowed based on past activity by the user.  The user's identity is, by default, tied to login (via REMOTE\_USER) or IP address (via REMOTE\_IP) if login info is not available.  You may provide your own identity function via the **identity\_callback()** method.

To use this module you must create a table in your database with the following schema (using MySQL-syntax, although other DBs may work as well with minor alterations):

```
  CREATE TABLE rate_limit_hits (
     user_id   VARCHAR(255)      NOT NULL,
     action    VARCHAR(255)      NOT NULL,
     timestamp UNSIGNED INTEGER  NOT NULL,
     INDEX (user_id, action, timestamp)
  );
```

You may feel free to vary the storage-type and size of user\_id and action to match your usage.  For example, if your **identity\_callback()** always returns an integer you could make user\_id an integer column.

This table should be periodically cleared of old data.  Anything older than the maximum timeframe being used can be safely deleted.

**IMPORTANT NOTE**: The protection offered by this module is not perfect.  Identifying a user on the internet is very hard and a sophisticated attacker can work around these checks, by switching IPs or automating login creation.

## INTERFACE

The object returned from calling `"$self->rate_limit"` on your CGI::App object supports the following method calls:

### dbh

```
   $rate_limit->dbh($dbh);
```

Call this to set the database handle the object should use.  Must be set in **setup()**.

### table

```
   $rate_limit->table('some_table_name');
```

Call this to determine the table to be used to store and lookup hits. The default is 'rate\_limit\_hits' if not set.  See the DESCRIPTION section for the required table schema.

### protected\_modes

```
    $rate_limit->protected_modes(send => {timeframe => '10m',
                                          max_hits  => 5
                                         },
                                 list => {timeframe => '5s',
                                          max_hits  => 1
                                         });
```

Takes a list of key-value pairs describing the modes to protect.  Keys are names of run-modes.  Values are hashes with the following keys:

```
  timeframe - the timeframe to be considered for violations.  Values
  must be numbers followed by either 's' for seconds, 'm' for minutes
  or 'h' for hours.
  max_hits - how many hits to allow in the specified timeframe before
  triggering a violation.
```

### protected\_actions

```
    $rate_limit->protected_actions(failed_login => {timeframe => '10s',
                                                    max_hits  => 2
                                                   });
```

Specifies non-run-mode actions to protect.  These are arbitrary keys you can use with **record\_hit()** and **check\_violation()**.  Takes the same data-structure as **protected\_modes()**.

### violation\_mode

```
  $rate_limit->violation_mode('too_fast_buddy');
```

Call to set a run-mode to call when a violation is triggered.  Either this or violation\_callback must be set.

### violation\_callback

```
    $rate_limit->violation_callback(sub { ... });
```

Callback to call when a violation is detected.  Should either throw an exception or return the run-mode to run.  Called with the CGI::App object as its sole parameter.

### identity\_callback

```
    $rate_limit->identity_callback(sub { ... });
```

Call this to provide a customized mechanism for determining the identity of the user.  The default is:

```
  sub { $ENV{REMOTE_USER} || $ENV{REMOTE_IP} }
```

You might consider adding in session-ID or a hook to your authentication system if it doesn't use REMOTE\_USER.  Whatever you write should return a single scalar which is expected to be unique to each user.

### record\_hit

```
  $rate_limit->record_hit(action => 'failed_login');
```

Record a hit for an arbitrary action.  This is not needed for run-mode protection.  Takes the action name as an argument, which must match an action registered with **protected\_actions()**.

### check\_violation

```
  return $self->slow_down_buddy
    if( $rate_limit->check_violation(action => 'failed_login') );
```

Checks for a violation of a protected action.  This is not needed for run-mode protection.  Takes the action name as an argument, which must match an action registered with **protected\_actions()**.

Returns 1 if a violation took place, 0 otherwise.

### revoke\_hit

```
  $rate_limit->revoke_hit();
```

Revokes the last hit for this user.  You might use this to prevent validation errors from counting against a user, for example.

### violated\_mode

```
  $mode = $rate_limit->violated_mode;
```

Returns the mode for the last violation, or undef if an action caused the violation.

### violated\_action

```
  $mode = $rate_limit->violated_action;
```

Returns the action for the last violation, or undef if an action caused the violation.

### violated\_limits

```
  $limits = $rate_limit->violated_limits;
```

Returns the hash-ref passed to **protected\_actions()** or **protected\_modes()** for the violated mode/action.

## DATABASE SUPPORT

I've tested this module with MySQL and SQLite.  I think it's likely to work with many other databases - please let me know if you try one.

## SUPPORT

Please send questions and suggestions about this module to the CGI::Application mailing-list.  To join the mailing list, simply send a blank message to:

```
  cgiapp-subscribe@lists.erlbaum.net
```

## VERSION CONTROL

This module is in a public Subversion repository at SourceForge here:

```
   https://svn.sourceforge.net/svnroot/html-template/trunk/CGI-Application-Plugin-RateLimit
```

## BUGS

I know of no bugs.  If you find one, let me know by filing a report on http://rt.cpan.org.  Failing that, you can email me at sam@tregar.com. Please include the version of the module you're using and small test case demonstrating the problem.

## AUTHOR

Sam Tregar, sam@plusthree.com

## COPYRIGHT AND LICENSE

Copyright (C) 2006 by Sam Tregar

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.6 or, at your option, any later version of Perl 5 you may have available.
