Skip to content

Configuration

Terminal window
php artisan vendor:publish --tag="suspend-config"

This creates config/suspend.php.

Configure the primary key type for Suspend tables:

'primary_key_type' => 'id', // 'id', 'uuid', or 'ulid'
TypeDescription
idAuto-incrementing integer (default)
uuidUUID v4 strings
ulidULID strings (sortable)

Note: Set this before running migrations.

Configure how polymorphic relationships store model types:

// For context (suspended entities)
'context_morph_type' => 'string', // 'string', 'numeric', 'uuid', 'ulid'
// For actors (suspended_by, revoked_by)
'actor_morph_type' => 'string',

Using numeric reduces storage but requires a morph map:

// In AppServiceProvider
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
1 => App\Models\User::class,
2 => App\Models\Organization::class,
]);

Choose how client IP addresses are extracted:

use Cline\Suspend\Resolvers\Ip\StandardIpResolver;
use Cline\Suspend\Resolvers\Ip\CloudflareIpResolver;
use Cline\Suspend\Resolvers\Ip\AwsApiGatewayIpResolver;
use Cline\Suspend\Resolvers\Ip\FastlyIpResolver;
use Cline\Suspend\Resolvers\Ip\AkamaiIpResolver;
use Cline\Suspend\Resolvers\Ip\TrustedProxyIpResolver;
'ip_resolver' => StandardIpResolver::class,
ResolverUse When
StandardIpResolverDirect connections, standard proxies
CloudflareIpResolverBehind Cloudflare CDN
AwsApiGatewayIpResolverBehind AWS API Gateway/ALB
FastlyIpResolverBehind Fastly CDN
AkamaiIpResolverBehind Akamai CDN
TrustedProxyIpResolverCustom trusted proxy setup

Each CDN resolver reads the appropriate header:

  • Cloudflare: CF-Connecting-IP
  • AWS: X-Forwarded-For (first IP)
  • Fastly: Fastly-Client-IP
  • Akamai: True-Client-IP

Configure IP geolocation for country-based suspensions:

Free, fast, no API limits - use headers from your CDN:

use Cline\Suspend\Resolvers\Geo\Cdn\CloudflareGeoResolver;
use Cline\Suspend\Resolvers\Geo\Cdn\AwsCloudFrontGeoResolver;
use Cline\Suspend\Resolvers\Geo\Cdn\FastlyGeoResolver;
use Cline\Suspend\Resolvers\Geo\Cdn\AkamaiGeoResolver;
use Cline\Suspend\Resolvers\Geo\Cdn\VercelGeoResolver;
'geo_resolver' => CloudflareGeoResolver::class,

Use MaxMind GeoLite2 or GeoIP2 database:

use Cline\Suspend\Resolvers\Geo\Local\MaxMindLocalGeoResolver;
'geo_resolver' => MaxMindLocalGeoResolver::class,
'maxmind_database' => storage_path('geoip/GeoLite2-City.mmdb'),

Download the database from MaxMind.

Requires: composer require geoip2/geoip2

Use third-party geolocation APIs:

use Cline\Suspend\Resolvers\Geo\Api\IpApiGeoResolver;
use Cline\Suspend\Resolvers\Geo\Api\IpStackGeoResolver;
use Cline\Suspend\Resolvers\Geo\Api\IpInfoGeoResolver;
use Cline\Suspend\Resolvers\Geo\Api\IpDataGeoResolver;
use Cline\Suspend\Resolvers\Geo\Api\Ip2LocationApiGeoResolver;
use Cline\Suspend\Resolvers\Geo\Api\DbIpApiGeoResolver;
use Cline\Suspend\Resolvers\Geo\Api\IpGeolocationIoGeoResolver;
'geo_resolver' => IpApiGeoResolver::class,

Use multiple resolvers with fallback:

use Cline\Suspend\Resolvers\ChainResolver;
'geo_resolver' => [
CloudflareGeoResolver::class, // Try CDN first (free)
MaxMindLocalGeoResolver::class, // Fallback to local DB
IpApiGeoResolver::class, // Fallback to API
],

Disable geolocation entirely:

use Cline\Suspend\Resolvers\Geo\NullGeoResolver;
'geo_resolver' => NullGeoResolver::class, // Default

Configure API keys for geo services:

'api_keys' => [
'ip_api' => env('SUSPEND_IP_API_KEY'),
'ipstack' => env('SUSPEND_IPSTACK_KEY'),
'ipinfo' => env('SUSPEND_IPINFO_TOKEN'),
'ipgeolocation_io' => env('SUSPEND_IPGEOLOCATION_IO_KEY'),
'ip2location' => env('SUSPEND_IP2LOCATION_KEY'),
'abstractapi' => env('SUSPEND_ABSTRACTAPI_KEY'),
'dbip' => env('SUSPEND_DBIP_KEY'),
'ipdata' => env('SUSPEND_IPDATA_KEY'),
],

Add to your .env:

SUSPEND_IP_API_KEY=your-key-here
SUSPEND_IPSTACK_KEY=your-key-here

Cache geolocation results to reduce API calls:

'geo_cache_ttl' => 3600, // 1 hour, set to 0 to disable

Override default table names:

'tables' => [
'suspensions' => 'my_suspensions',
],

Note: Set this before running migrations.

Configure the built-in suspension middleware:

'middleware' => [
// Automatically check IP against suspensions
'check_ip' => true,
// Automatically check country against suspensions
'check_country' => false,
// HTTP response code when suspended
'response_code' => 403,
// Response message when suspended
'response_message' => 'Access denied. Your access has been suspended.',
// Routes to exclude from checks
'except' => [
'login',
'logout',
'contact',
'suspended',
],
],

Recommended environment variables:

# Primary key type (set before migrations)
SUSPEND_PRIMARY_KEY_TYPE=id
# IP Resolution
SUSPEND_IP_RESOLVER=standard
# Geo Resolution
SUSPEND_GEO_RESOLVER=cloudflare
# MaxMind Database Path
SUSPEND_MAXMIND_DATABASE=/path/to/GeoLite2-City.mmdb
# API Keys (only needed for API-based resolvers)
SUSPEND_IP_API_KEY=
SUSPEND_IPSTACK_KEY=
SUSPEND_IPINFO_TOKEN=
# Cache
SUSPEND_GEO_CACHE_TTL=3600

Then in config:

'ip_resolver' => match(env('SUSPEND_IP_RESOLVER', 'standard')) {
'cloudflare' => CloudflareIpResolver::class,
'aws' => AwsApiGatewayIpResolver::class,
'fastly' => FastlyIpResolver::class,
default => StandardIpResolver::class,
},
'geo_resolver' => match(env('SUSPEND_GEO_RESOLVER', 'null')) {
'cloudflare' => CloudflareGeoResolver::class,
'maxmind' => MaxMindLocalGeoResolver::class,
'ip_api' => IpApiGeoResolver::class,
default => NullGeoResolver::class,
},
return [
'primary_key_type' => 'id',
'ip_resolver' => StandardIpResolver::class,
'geo_resolver' => NullGeoResolver::class,
];
return [
'primary_key_type' => 'ulid',
'ip_resolver' => CloudflareIpResolver::class,
'geo_resolver' => CloudflareGeoResolver::class,
];
return [
'primary_key_type' => 'uuid',
'context_morph_type' => 'uuid',
'actor_morph_type' => 'uuid',
'ip_resolver' => CloudflareIpResolver::class,
'geo_resolver' => [
CloudflareGeoResolver::class,
MaxMindLocalGeoResolver::class,
],
'maxmind_database' => storage_path('geoip/GeoIP2-City.mmdb'),
'geo_cache_ttl' => 86400, // 24 hours
'middleware' => [
'check_ip' => true,
'check_country' => true,
'response_code' => 403,
'except' => ['login', 'logout', 'suspended', 'api/health'],
],
];