mirror of
https://github.com/apidoorman/doorman.git
synced 2026-04-29 04:39:54 -05:00
rate limits and throttles are toggled, not required
This commit is contained in:
@@ -18,11 +18,13 @@ class CreateUserModel(BaseModel):
|
||||
|
||||
rate_limit_duration: Optional[int] = Field(None, ge=0, description='Rate limit for the user', example=100)
|
||||
rate_limit_duration_type: Optional[str] = Field(None, min_length=1, max_length=7, description='Duration for the rate limit', example='hour')
|
||||
rate_limit_enabled: Optional[bool] = Field(None, description='Whether rate limiting is enabled for this user', example=True)
|
||||
throttle_duration: Optional[int] = Field(None, ge=0, description='Throttle limit for the user', example=10)
|
||||
throttle_duration_type: Optional[str] = Field(None, min_length=1, max_length=7, description='Duration for the throttle limit', example='second')
|
||||
throttle_wait_duration: Optional[int] = Field(None, ge=0, description='Wait time for the throttle limit', example=5)
|
||||
throttle_wait_duration_type: Optional[str] = Field(None, min_length=1, max_length=7, description='Wait duration for the throttle limit', example='seconds')
|
||||
throttle_queue_limit: Optional[int] = Field(None, ge=0, description='Throttle queue limit for the user', example=10)
|
||||
throttle_enabled: Optional[bool] = Field(None, description='Whether throttling is enabled for this user', example=True)
|
||||
custom_attributes: Optional[dict] = Field(None, description='Custom attributes for the user', example={'custom_key': 'custom_value'})
|
||||
bandwidth_limit_bytes: Optional[int] = Field(None, ge=0, description='Maximum bandwidth allowed within the window (bytes)', example=1073741824)
|
||||
bandwidth_limit_window: Optional[str] = Field('day', min_length=1, max_length=10, description='Bandwidth window unit (second/minute/hour/day/month)', example='day')
|
||||
|
||||
@@ -17,11 +17,13 @@ class UpdateUserModel(BaseModel):
|
||||
groups: Optional[List[str]] = Field(None, description='List of groups the user belongs to', example=['client-1-group'])
|
||||
rate_limit_duration: Optional[int] = Field(None, ge=0, description='Rate limit for the user', example=100)
|
||||
rate_limit_duration_type: Optional[str] = Field(None, min_length=1, max_length=7, description='Duration for the rate limit', example='hour')
|
||||
rate_limit_enabled: Optional[bool] = Field(None, description='Whether rate limiting is enabled for this user', example=True)
|
||||
throttle_duration: Optional[int] = Field(None, ge=0, description='Throttle limit for the user', example=10)
|
||||
throttle_duration_type: Optional[str] = Field(None, min_length=1, max_length=7, description='Duration for the throttle limit', example='second')
|
||||
throttle_wait_duration: Optional[int] = Field(None, ge=0, description='Wait time for the throttle limit', example=5)
|
||||
throttle_wait_duration_type: Optional[str] = Field(None, min_length=1, max_length=7, description='Wait duration for the throttle limit', example='seconds')
|
||||
throttle_queue_limit: Optional[int] = Field(None, ge=0, description='Throttle queue limit for the user', example=10)
|
||||
throttle_enabled: Optional[bool] = Field(None, description='Whether throttling is enabled for this user', example=True)
|
||||
custom_attributes: Optional[dict] = Field(None, description='Custom attributes for the user', example={'custom_key': 'custom_value'})
|
||||
bandwidth_limit_bytes: Optional[int] = Field(None, ge=0, description='Maximum bandwidth allowed within the window (bytes)', example=1073741824)
|
||||
bandwidth_limit_window: Optional[str] = Field(None, min_length=1, max_length=10, description='Bandwidth window unit (second/minute/hour/day/month)', example='day')
|
||||
|
||||
@@ -17,11 +17,13 @@ class UserModelResponse(BaseModel):
|
||||
groups: Optional[List[str]] = Field(None, description='List of groups the user belongs to', example=['client-1-group'])
|
||||
rate_limit_duration: Optional[int] = Field(None, ge=0, description='Rate limit for the user', example=100)
|
||||
rate_limit_duration_type: Optional[str] = Field(None, min_length=1, max_length=7, description='Duration for the rate limit', example='hour')
|
||||
rate_limit_enabled: Optional[bool] = Field(None, description='Whether rate limiting is enabled for this user', example=True)
|
||||
throttle_duration: Optional[int] = Field(None, ge=0, description='Throttle limit for the user', example=10)
|
||||
throttle_duration_type: Optional[str] = Field(None, min_length=1, max_length=7, description='Duration for the throttle limit', example='second')
|
||||
throttle_wait_duration: Optional[int] = Field(None, ge=0, description='Wait time for the throttle limit', example=5)
|
||||
throttle_wait_duration_type: Optional[str] = Field(None, min_length=1, max_length=7, description='Wait duration for the throttle limit', example='seconds')
|
||||
throttle_queue_limit: Optional[int] = Field(None, ge=0, description='Throttle queue limit for the user', example=10)
|
||||
throttle_enabled: Optional[bool] = Field(None, description='Whether throttling is enabled for this user', example=True)
|
||||
custom_attributes: Optional[dict] = Field(None, description='Custom attributes for the user', example={'custom_key': 'custom_value'})
|
||||
bandwidth_limit_bytes: Optional[int] = Field(None, ge=0, description='Maximum bandwidth allowed within the window (bytes)', example=1073741824)
|
||||
bandwidth_limit_window: Optional[str] = Field(None, min_length=1, max_length=10, description='Bandwidth window unit (second/minute/hour/day/month)', example='day')
|
||||
|
||||
@@ -58,46 +58,50 @@ async def limit_and_throttle(request: Request):
|
||||
user = doorman_cache.get_cache('user_cache', username)
|
||||
if not user:
|
||||
user = user_collection.find_one({'username': username})
|
||||
rate = int(user.get('rate_limit_duration') or 1)
|
||||
duration = user.get('rate_limit_duration_type', 'minute')
|
||||
window = duration_to_seconds(duration)
|
||||
now_ms = int(time.time() * 1000)
|
||||
key = f'rate_limit:{username}:{now_ms // (window * 1000)}'
|
||||
try:
|
||||
client = redis_client or _fallback_counter
|
||||
count = await client.incr(key)
|
||||
if count == 1:
|
||||
await client.expire(key, window)
|
||||
except Exception:
|
||||
# Rate limiting (skip if explicitly disabled)
|
||||
if user.get('rate_limit_enabled') is not False:
|
||||
rate = int(user.get('rate_limit_duration') or 1)
|
||||
duration = user.get('rate_limit_duration_type', 'minute')
|
||||
window = duration_to_seconds(duration)
|
||||
key = f'rate_limit:{username}:{now_ms // (window * 1000)}'
|
||||
try:
|
||||
client = redis_client or _fallback_counter
|
||||
count = await client.incr(key)
|
||||
if count == 1:
|
||||
await client.expire(key, window)
|
||||
except Exception:
|
||||
count = await _fallback_counter.incr(key)
|
||||
if count == 1:
|
||||
await _fallback_counter.expire(key, window)
|
||||
if count > rate:
|
||||
raise HTTPException(status_code=429, detail='Rate limit exceeded')
|
||||
|
||||
count = await _fallback_counter.incr(key)
|
||||
if count == 1:
|
||||
await _fallback_counter.expire(key, window)
|
||||
if count > rate:
|
||||
raise HTTPException(status_code=429, detail='Rate limit exceeded')
|
||||
throttle_limit = int(user.get('throttle_duration') or 5)
|
||||
throttle_duration = user.get('throttle_duration_type', 'second')
|
||||
throttle_window = duration_to_seconds(throttle_duration)
|
||||
throttle_key = f'throttle_limit:{username}:{now_ms // (throttle_window * 1000)}'
|
||||
try:
|
||||
client = redis_client or _fallback_counter
|
||||
throttle_count = await client.incr(throttle_key)
|
||||
if throttle_count == 1:
|
||||
await client.expire(throttle_key, throttle_window)
|
||||
except Exception:
|
||||
throttle_count = await _fallback_counter.incr(throttle_key)
|
||||
if throttle_count == 1:
|
||||
await _fallback_counter.expire(throttle_key, throttle_window)
|
||||
throttle_queue_limit = int(user.get('throttle_queue_limit') or 10)
|
||||
if throttle_count > throttle_queue_limit:
|
||||
raise HTTPException(status_code=429, detail='Throttle queue limit exceeded')
|
||||
if throttle_count > throttle_limit:
|
||||
throttle_wait = float(user.get('throttle_wait_duration', 0.5) or 0.5)
|
||||
throttle_wait_duration = user.get('throttle_wait_duration_type', 'second')
|
||||
if throttle_wait_duration != 'second':
|
||||
throttle_wait *= duration_to_seconds(throttle_wait_duration)
|
||||
dynamic_wait = throttle_wait * (throttle_count - throttle_limit)
|
||||
await asyncio.sleep(dynamic_wait)
|
||||
# Throttling (skip if explicitly disabled)
|
||||
if user.get('throttle_enabled') is not False:
|
||||
throttle_limit = int(user.get('throttle_duration') or 5)
|
||||
throttle_duration = user.get('throttle_duration_type', 'second')
|
||||
throttle_window = duration_to_seconds(throttle_duration)
|
||||
throttle_key = f'throttle_limit:{username}:{now_ms // (throttle_window * 1000)}'
|
||||
try:
|
||||
client = redis_client or _fallback_counter
|
||||
throttle_count = await client.incr(throttle_key)
|
||||
if throttle_count == 1:
|
||||
await client.expire(throttle_key, throttle_window)
|
||||
except Exception:
|
||||
throttle_count = await _fallback_counter.incr(throttle_key)
|
||||
if throttle_count == 1:
|
||||
await _fallback_counter.expire(throttle_key, throttle_window)
|
||||
throttle_queue_limit = int(user.get('throttle_queue_limit') or 10)
|
||||
if throttle_count > throttle_queue_limit:
|
||||
raise HTTPException(status_code=429, detail='Throttle queue limit exceeded')
|
||||
if throttle_count > throttle_limit:
|
||||
throttle_wait = float(user.get('throttle_wait_duration', 0.5) or 0.5)
|
||||
throttle_wait_duration = user.get('throttle_wait_duration_type', 'second')
|
||||
if throttle_wait_duration != 'second':
|
||||
throttle_wait *= duration_to_seconds(throttle_wait_duration)
|
||||
dynamic_wait = throttle_wait * (throttle_count - throttle_limit)
|
||||
await asyncio.sleep(dynamic_wait)
|
||||
|
||||
def reset_counters():
|
||||
"""Reset in-memory rate/throttle counters (used by tests and cache clears).
|
||||
|
||||
@@ -17,11 +17,13 @@ interface User {
|
||||
groups: string[]
|
||||
rate_limit_duration: number
|
||||
rate_limit_duration_type: string
|
||||
rate_limit_enabled?: boolean
|
||||
throttle_duration: number
|
||||
throttle_duration_type: string
|
||||
throttle_wait_duration: number
|
||||
throttle_wait_duration_type: string
|
||||
throttle_queue_limit: number | null
|
||||
throttle_enabled?: boolean
|
||||
custom_attributes: Record<string, string>
|
||||
bandwidth_limit_bytes?: number
|
||||
bandwidth_limit_window?: string
|
||||
@@ -38,11 +40,13 @@ interface UpdateUserData {
|
||||
groups?: string[]
|
||||
rate_limit_duration?: number
|
||||
rate_limit_duration_type?: string
|
||||
rate_limit_enabled?: boolean
|
||||
throttle_duration?: number
|
||||
throttle_duration_type?: string
|
||||
throttle_wait_duration?: number
|
||||
throttle_wait_duration_type?: string
|
||||
throttle_queue_limit?: number | null
|
||||
throttle_enabled?: boolean
|
||||
custom_attributes?: Record<string, string>
|
||||
bandwidth_limit_bytes?: number
|
||||
bandwidth_limit_window?: string
|
||||
@@ -84,8 +88,13 @@ const UserDetailPage = () => {
|
||||
groups: [...parsedUser.groups],
|
||||
rate_limit_duration: parsedUser.rate_limit_duration,
|
||||
rate_limit_duration_type: parsedUser.rate_limit_duration_type,
|
||||
rate_limit_enabled: (parsedUser as any).rate_limit_enabled,
|
||||
throttle_duration: parsedUser.throttle_duration,
|
||||
throttle_duration_type: parsedUser.throttle_duration_type,
|
||||
throttle_wait_duration: (parsedUser as any).throttle_wait_duration,
|
||||
throttle_wait_duration_type: (parsedUser as any).throttle_wait_duration_type,
|
||||
throttle_queue_limit: (parsedUser as any).throttle_queue_limit,
|
||||
throttle_enabled: (parsedUser as any).throttle_enabled,
|
||||
throttle_wait_duration: parsedUser.throttle_wait_duration,
|
||||
throttle_wait_duration_type: parsedUser.throttle_wait_duration_type,
|
||||
throttle_queue_limit: parsedUser.throttle_queue_limit,
|
||||
@@ -107,6 +116,8 @@ const UserDetailPage = () => {
|
||||
bandwidth_limit_bytes: refreshed.bandwidth_limit_bytes,
|
||||
bandwidth_limit_window: refreshed.bandwidth_limit_window,
|
||||
bandwidth_limit_enabled: (refreshed as any).bandwidth_limit_enabled,
|
||||
rate_limit_enabled: (refreshed as any).rate_limit_enabled,
|
||||
throttle_enabled: (refreshed as any).throttle_enabled,
|
||||
}))
|
||||
} catch {}
|
||||
})()
|
||||
@@ -142,14 +153,17 @@ const UserDetailPage = () => {
|
||||
groups: [...user.groups],
|
||||
rate_limit_duration: user.rate_limit_duration,
|
||||
rate_limit_duration_type: user.rate_limit_duration_type,
|
||||
rate_limit_enabled: (user as any).rate_limit_enabled,
|
||||
throttle_duration: user.throttle_duration,
|
||||
throttle_duration_type: user.throttle_duration_type,
|
||||
throttle_wait_duration: user.throttle_wait_duration,
|
||||
throttle_wait_duration_type: user.throttle_wait_duration_type,
|
||||
throttle_queue_limit: user.throttle_queue_limit,
|
||||
throttle_enabled: (user as any).throttle_enabled,
|
||||
custom_attributes: { ...user.custom_attributes },
|
||||
bandwidth_limit_bytes: user.bandwidth_limit_bytes,
|
||||
bandwidth_limit_window: user.bandwidth_limit_window,
|
||||
bandwidth_limit_enabled: (user as any).bandwidth_limit_enabled,
|
||||
active: user.active,
|
||||
ui_access: user.ui_access
|
||||
})
|
||||
@@ -672,6 +686,24 @@ const UserDetailPage = () => {
|
||||
<FormHelp docHref="/docs/using-fields.html#rate-limit">Limits requests per user over a time window.</FormHelp>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Enforcement</label>
|
||||
{isEditing ? (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!editData.rate_limit_enabled}
|
||||
onChange={(e) => handleInputChange('rate_limit_enabled', e.target.checked)}
|
||||
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label className="ml-2 text-sm text-gray-700 dark:text-gray-300">Enforce rate limiting for this user</label>
|
||||
</div>
|
||||
) : (
|
||||
<span className={`badge ${(user as any).rate_limit_enabled === false ? 'badge-gray' : 'badge-success'}`}>
|
||||
{(user as any).rate_limit_enabled === false ? 'Disabled' : 'Enabled'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Rate Limit Duration
|
||||
@@ -712,6 +744,24 @@ const UserDetailPage = () => {
|
||||
<FormHelp docHref="/docs/using-fields.html#throttle">Control bursts with duration, wait, and queue size.</FormHelp>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Enforcement</label>
|
||||
{isEditing ? (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!editData.throttle_enabled}
|
||||
onChange={(e) => handleInputChange('throttle_enabled', e.target.checked)}
|
||||
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label className="ml-2 text-sm text-gray-700 dark:text-gray-300">Enforce throttling for this user</label>
|
||||
</div>
|
||||
) : (
|
||||
<span className={`badge ${(user as any).throttle_enabled === false ? 'badge-gray' : 'badge-success'}`}>
|
||||
{(user as any).throttle_enabled === false ? 'Disabled' : 'Enabled'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Throttle Duration
|
||||
|
||||
@@ -17,11 +17,13 @@ interface CreateUserData {
|
||||
groups: string[]
|
||||
rate_limit_duration?: number
|
||||
rate_limit_duration_type?: string
|
||||
rate_limit_enabled?: boolean
|
||||
throttle_duration?: number
|
||||
throttle_duration_type?: string
|
||||
throttle_wait_duration?: number
|
||||
throttle_wait_duration_type?: string
|
||||
throttle_queue_limit?: number | null
|
||||
throttle_enabled?: boolean
|
||||
custom_attributes: Record<string, string>
|
||||
bandwidth_limit_bytes?: number
|
||||
bandwidth_limit_window?: string
|
||||
@@ -39,6 +41,7 @@ const AddUserPage = () => {
|
||||
role: '',
|
||||
groups: [],
|
||||
custom_attributes: {},
|
||||
rate_limit_enabled: false,
|
||||
bandwidth_limit_bytes: undefined,
|
||||
bandwidth_limit_window: 'day',
|
||||
bandwidth_limit_enabled: false,
|
||||
@@ -331,6 +334,19 @@ const AddUserPage = () => {
|
||||
<FormHelp docHref="/docs/using-fields.html#rate-limit">Limits requests per user over a time window.</FormHelp>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Enforcement</label>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
|
||||
checked={!!formData.rate_limit_enabled}
|
||||
onChange={(e) => handleInputChange('rate_limit_enabled', e.target.checked)}
|
||||
disabled={loading}
|
||||
/>
|
||||
<span className="ml-2 text-sm text-gray-700 dark:text-gray-300">Enforce rate limiting for this user</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="rate_limit_duration" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Rate Limit Duration
|
||||
@@ -375,6 +391,19 @@ const AddUserPage = () => {
|
||||
<FormHelp docHref="/docs/using-fields.html#throttle">Control burst behavior with wait, duration, and queue size.</FormHelp>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Enforcement</label>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
|
||||
checked={!!formData.throttle_enabled}
|
||||
onChange={(e) => handleInputChange('throttle_enabled', e.target.checked)}
|
||||
disabled={loading}
|
||||
/>
|
||||
<span className="ml-2 text-sm text-gray-700 dark:text-gray-300">Enforce throttling for this user</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="throttle_duration" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Throttle Duration
|
||||
|
||||
Reference in New Issue
Block a user