
    Gdit0                    n    d dl mZ d dlZd dlmZmZ d dlmZ d dlmZm	Z	 d dl
mZ d
dZ G d d	e      Zy)    )annotationsN)datetime	timedelta)Set)AsyncMongoClientReturnDocument)HttpMiddlewarec                z    	 t        t        j                  | j                                     S # t        $ r Y y w xY wN)str	ipaddress
ip_addressstrip	Exception)values    )./examples/blog/middlewares/rate_limit.py	_valid_ipr      s5    9''677 s   +. 	::c                  h    e Zd ZdZdZdZdZdZdZdZ		 ddd	d	d
	 	 	 	 	 	 	 	 	 	 	 ddZ
ddZd Zd Zy)MongoRateLimitMiddlewaream  
    Global MongoDB-based rate limiter (Heroku + Cloudflare safe).

    - One document per client IP
    - Fixed window counter
    - Escalating temporary blocks
    - Permanent block based on 24h violation history
    - Fully atomic (single DB op per request)
    - PyMongo Async API (no Motor)

    Requires:
    - MongoDB 4.2+ (aggregation pipeline updates)
    2   <      i     
   NT)allowed_hoststrust_proxy_headersrequire_cf_rayc                   t        |      | _        | j                  |   | _        | j                  |   | _        |xs
 t	               | _        || _        || _        y r   )r   clientdb
collectionsetr   r   r   )self	mongo_uridb_namecollection_namer   r   r   s          r   __init__z!MongoRateLimitMiddleware.__init__+   sQ     'y1++g&''/2 +3ce#6 ,    c                   t        |di       xs i }| j                  rG|j                  d      xs dj                  dd      d   j	                         }|r|| j                  vry| j
                  }|r&| j                  rt        |j                  d            }|rd	D ]"  }t        |j                  |            }|s |c S  |j                  d
      }t        |t              r"t        |j                  dd      d         }|r|S t        |j                  d            }|r|S |j                  j                  d      xs d}t        |d         xs dS )Nheadershost :   r   unknownzcf-ray)zcf-connecting-ipztrue-client-ipzx-forwarded-for,z	x-real-ipr   )r/   r   )getattrr   getsplitlowerr   r   boolr   
isinstancer   scope)	r#   requestr*   r+   	can_trusthipxffr   s	            r   
_client_ipz#MongoRateLimitMiddleware._client_ipB   sA   '9b17R KK'-244S!<Q?EEGDD$6$66  ,,	,,W[[23I; w{{1~.I ++/0C#s#syya034I 7;;{34B	 ""8,>#0y0r(   c                  K   | j                  |      }t        j                         }|t        | j                        z
  }|t        | j
                        z
  }|}| j                  j                  d|id||dddgidd|gidd	dgidd
d gidddgiddd giddg gid	iddddddd|gidiiiddddddd
d gidd
|gigigiiiddddddd|gigiiidddddd|dgigidddddd d!dd gigigid"idd#dd$didd| j                  gigiiiddd%d!d	d gid	gidd%d&d|ggidgid'idd(ddd%dd	| j                  gigi|t        | j                        z   d
giiidd)d*diiidddd%dd+| j                  gigid,dgiddd%dd+| j                  gid-dd gigi|dgid.id/g d0igd,t        j                  d d d d12       d {   }|xs i }|j                  d3      r
d4d5| d6g d7S |j                  d(      }t        |t              rA||k  r<t!        dt#        ||z
  j%                                     }	d8d9| d:d;t'        |	      fgd7S t#        |j                  d<d            | j                  kD  r
d8d=| d6g d7S y 7 w)>N)seconds)hours_idz$setz$ifNullz$countr   z$window_startz$violationsz$blocked_untilz$permanent_blockedFz$permanent_blocked_atz$violation_events)	rA   r;   countwindow_start
violationsblocked_untilpermanent_blockedpermanent_blocked_atviolation_eventsrH   z$filtertz$gtez$$t)inputascond_blocked_nowz$orz$andz$nez$gt_window_expiredz$condz$_blocked_nowz$ltz$_window_expiredr.   z$add)rC   rB   _over_limitz$notz$_over_limitz$concatArrays)rD   rH   rE   _events_24hz$sizez$_events_24hTz$eq)rF   rG   z$unset)rM   rN   rO   rP   )rB   rE   rF   )upsertreturn_document
projectionrF   i  z"Access permanently blocked for IP .)status_codebodyr*   i  zToo many requests from z. Temporarily blocked.zRetry-AfterrB   zRate limit exceeded for IP )r=   r   utcnowr   WINDOW_SECONDSPERMA_WINDOW_HOURSr!   find_one_and_updateMAX_REQUESTSBLOCK_AFTER_VIOLATIONSBLOCK_FOR_SECONDSPERMA_BLOCK_AFTERr   AFTERr2   r6   maxinttotal_secondsr   )
r#   r8   	client_ipnowwindow_start_cutoffperma_window_cutoffkeydocrE   retry_afters
             r   before_requestz'MongoRateLimitMiddleware.before_requestk   s    OOG,	oo!Id6I6I$JJ!ID4K4K$LLOO77CL "'"+h]!;)2_c4J(K'0=!2D&E*36F5M)N.7:NPU9V-W%(?'F1 .79Lb8Q,R" *%)<&))/%9L1M(N(-
 &! 4$*).1A40H(I).1A30G(H-&!"$
)" )# / %!&:M(N O&,
 # / /!(+=sO*T U&) $ / ($+(:())/(A(?.&!"
&"4 %"!' 9!&43D3D(E F%(	 # .!'-); < -&' $ .!03F2N O 3&-( '#$*(6,20=040K0K5.)*-&
!" !$i8N8N&O O 0&*, -'3F)GHI#$*(6,20>040F0F5.)*-&
!" !% 4&.$ $$*(6,20>040F0F5.)* */1H$0O(P	-&!" !$ 7&1%$&R  _wp *00!"QQOy 8 }
 }
~ iR 77&'"<YKqI  0mX.33Famc&9%H%H%J!KLK"1)<RS*C,<=>  swww"#d&7&77"5i[B  s}
s   F4I36I17B;I3c                   K   y wr    )r#   r8   rU   response_bodyextra_headerss        r   after_requestz&MongoRateLimitMiddleware.after_requestO  s	     s   )rate_limits_global)r$   r   r%   r   r&   r   r   zSet[str] | Noner   r5   r   r5   )returnr   )__name__
__module____qualname____doc__r[   rX   r\   r]   rY   r^   r'   r=   rj   ro   rl   r(   r   r   r      s     LN  4	- *.$(#-- - 	- '- "- -.#1RbHr(   r   )r   
str | Nonerq   rv   )
__future__r   r   r   r   typingr   pymongor   r   micropier	   r   r   rl   r(   r   <module>r{      s*    "  (  4 #~~ ~r(   