§
    ˆv»jB  ã                  ó  — d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
mZmZmZmZmZ ddlmZ  G d„ dej        ¦  «        Z edd¬	¦  «         G d
„ d¦  «        ¦   «         Z G d„ d¦  «        Z G d„ d¦  «        ZdS )z§
Anti-Bot Captcha Engine & Rate Limiter for FreeExile.
Provides stateless HMAC-signed visual/math puzzles, Proof-of-Work (Hashcash), and sliding-window rate limiting.
é    )ÚannotationsN)ÚDictÚListÚOptionalÚSetÚTuple)Ú	dataclassc                  ó   — e Zd ZdZdZdS )ÚCaptchaTypeÚmathÚpowN)Ú__name__Ú
__module__Ú__qualname__ÚMATHÚPOW© ó    ú,C:\Projects\FreeExile\server\auth\captcha.pyr   r      s   € € € € € Ø€DØ
€C€C€Cr   r   T)ÚslotsÚfrozenc                  óZ   — e Zd ZU dZded<   ded<   ded<   ded<   ded	<   ded
<   ded<   dS )ÚCaptchaChallengez1Cryptographic Captcha or Proof-of-Work challenge.ÚstrÚchallenge_idr   Úcaptcha_typeÚquestionÚseedÚintÚ
difficultyÚexpires_at_msÚ	signatureN)r   r   r   Ú__doc__Ú__annotations__r   r   r   r   r      s^   € € € € € € à;Ð;ØÐÐÑØÐÐÑØ€M€M�MØ€I€I�IØ€O€O�OØÐÐÑØ€N€N�N€N€Nr   r   c                  óN   — e Zd ZdZdd„Zdd„Zdd d„Zd!d„Zd"d#d„Zd$d„Z	d%d„Z
dS )&ÚCaptchaEnginezFStateless HMAC challenge generation and replay-resistant verification.Úhmac_secretr   ÚreturnÚNonec                ó`   — |                      d¦  «        | _        t          ¦   «         | _        d S ©Núutf-8)Úencoder'   ÚsetÚ_used_challenges)Úselfr'   s     r   Ú__init__zCaptchaEngine.__init__&   s*   € Ø&×-Ò-¨gÑ6Ô6ˆÔÝ*-©%¬%ˆÔÐÐr   Údatac                ó–   — t          j        | j        |                     d¦  «        t          j        ¦  «                             ¦   «         S r+   )ÚhmacÚnewr'   r-   ÚhashlibÚsha256Ú	hexdigest)r0   r2   s     r   Ú_signzCaptchaEngine._sign*   s3   € ÝŒx˜Ô(¨$¯+ª+°gÑ*>Ô*>ÅÄÑOÔO×YÒYÑ[Ô[Ð[r   é´   Úttl_secondsr   r   c           	     óÂ  — dt          j        ¦   «         j        dd…         › �}t          j        dd¦  «        }t          j        dd¦  «        }t          j        dd	g¦  «        }|dk    r||z   n||z
  }t          t          j        ¦   «         |z   d
z  ¦  «        }|› d|› d|› �}|                      |¦  «        }	t          |t          j        |› d|› d|› �dd||	¬¦  «        S )zCGenerate a simple arithmetic question with cryptographic signature.Úcap_Né   é
   é2   é   é   ú+ú-éè  ú:ú Ú r   ©r   r   r   r   r    r!   r"   )ÚuuidÚuuid4ÚhexÚrandomÚrandintÚchoicer   Útimer9   r   r   r   )
r0   r;   r   ÚaÚbÚopÚanswerr!   Úsig_payloadr"   s
             r   Úgenerate_math_challengez%CaptchaEngine.generate_math_challenge-   sù   € à5�dœj™lœlÔ.¨s°¨sÔ3Ð5Ð5ˆÝŒN˜2˜rÑ"Ô"ˆÝŒN˜1˜bÑ!Ô!ˆÝŒ]˜C ˜:Ñ&Ô&ˆà š)˜)��Q‘�¨¨Q©ˆÝ�TœY™[œ[¨;Ñ6¸$Ñ>Ñ?Ô?ˆð &Ð@Ð@¨Ð@Ð@°Ð@Ð@ˆØ—J’J˜{Ñ+Ô+ˆ	åØ%Ý$Ô)Ø�_�_˜B�_�_ �_�_ØØØ'Øð
ñ 
ô 
ð 	
r   r   Úuser_answerr"   Ú
expires_atúTuple[bool, str]c                ó6  — t          t          j        ¦   «         dz  ¦  «        }||k    rdS || j        v rdS |                     ¦   «         }|                      |› d|› d|› �¦  «        }t          j        ||¦  «        sdS | j                             |¦  «         dS )z3Verify user solution with replay attack prevention.rE   )FzCaptcha challenge expired)Fz/Captcha challenge already used (Replay blocked)rF   )Fz/Incorrect captcha solution or invalid signature©TÚOK)r   rP   r/   Ústripr9   ÚsecretsÚcompare_digestÚadd)r0   r   rW   r"   rX   Únow_msÚcleaned_answerÚexpected_sigs           r   Úverify_math_challengez#CaptchaEngine.verify_math_challengeE   s³   € õ •T”Y‘[”[ 4Ñ'Ñ(Ô(ˆØ�JÒÐØ5Ð5à˜4Ô0Ð0Ð0ØKÐKà$×*Ò*Ñ,Ô,ˆØ—z’z \Ð"QÐ"Q°NÐ"QÐ"QÀZÐ"QÐ"QÑRÔRˆåÔ% l°IÑ>Ô>ð 	LØKÐKð 	Ô×!Ò! ,Ñ/Ô/Ð/Øˆzr   é   r    c           	     óD  — dt          j        ¦   «         j        dd…         › �}t          j        d¦  «        }t          t          j        ¦   «         |z   dz  ¦  «        }|                      |› d|› d|› �¦  «        }t          |t          j
        d|› d|› d�||||¬	¦  «        S )
z.Generate a Proof-of-Work (Hashcash) challenge.Úpow_Nr>   rE   rF   zCompute SHA256(z + nonce) with z leading zerosrI   )rJ   rK   rL   r^   Ú	token_hexr   rP   r9   r   r   r   )r0   r    r;   r   r   r!   r"   s          r   Úgenerate_pow_challengez$CaptchaEngine.generate_pow_challenge^   s¶   € à5�dœj™lœlÔ.¨s°¨sÔ3Ð5Ð5ˆÝÔ  Ñ$Ô$ˆÝ�TœY™[œ[¨;Ñ6¸$Ñ>Ñ?Ô?ˆà—J’J $ÐEÐE¨ÐEÐE°mÐEÐEÑFÔFˆ	åØ%Ý$œØV tÐVÐV¸JÐVÐVÐVØØ!Ø'Øð
ñ 
ô 
ð 	
r   r   úOptional[str]c                ó   — d|z  }t          d¦  «        D ]h}t          |¦  «        }t          j        |› d|› �                     d¦  «        ¦  «                             ¦   «         }|                     |¦  «        r|c S ŒidS )zEUtility method to solve PoW (used by genuine clients or test suites).Ú0i@B rF   r,   N)Úranger   r6   r7   r-   r8   Ú
startswith)r0   r   r    Útarget_prefixÚnonceÚ	nonce_strÚdigests          r   Ú	solve_powzCaptchaEngine.solve_powp   s‘   € à˜jÑ(ˆÝ˜9Ñ%Ô%ð 	!ð 	!ˆEÝ˜E™
œ
ˆIÝ”^ tÐ$9Ð$9¨iÐ$9Ð$9×$@Ò$@ÀÑ$IÔ$IÑJÔJ×TÒTÑVÔVˆFØ× Ò  Ñ/Ô/ð !Ø Ð Ð Ð ð!àˆtr   rp   c                óØ  — t          t          j        ¦   «         dz  ¦  «        }||k    rdS |                      |› d|› d|› �¦  «        }t          j        ||¦  «        sdS d|› �}|| j        v rdS t          j        |› d|› �                     d¦  «        ¦  «         	                    ¦   «         }	d|z  }
|	 
                    |
¦  «        sd	d
|› d�fS | j                             |¦  «         dS )z@Verify Proof-of-Work solution with single-use replay protection.rE   )FzProof-of-Work challenge expiredrF   )Fz)Invalid Proof-of-Work challenge signatureÚ	pow_used_)Fz$Proof-of-Work challenge already usedr,   rl   FzProof-of-Work difficulty z unsatisfiedr[   )r   rP   r9   r^   r_   r/   r6   r7   r-   r8   rn   r`   )r0   r   rp   r    r"   rX   ra   rc   Ú	cache_keyrr   ro   s              r   Úverify_pow_challengez"CaptchaEngine.verify_pow_challengez   s  € õ •T”Y‘[”[ 4Ñ'Ñ(Ô(ˆØ�JÒÐØ;Ð;à—z’z TÐ"EÐ"E¨JÐ"EÐ"E¸Ð"EÐ"EÑFÔFˆÝÔ% l°IÑ>Ô>ð 	FØEÐEà& Ð&Ð&ˆ	Ø˜Ô-Ð-Ð-Ø@Ð@õ ” 4Ð 1Ð 1¨%Ð 1Ð 1× 8Ò 8¸Ñ AÔ AÑBÔB×LÒLÑNÔNˆØ˜jÑ(ˆØ× Ò  Ñ/Ô/ð 	OØÐN°jÐNÐNÐNÐNÐNàÔ×!Ò! )Ñ,Ô,Ð,Øˆzr   N)r'   r   r(   r)   )r2   r   r(   r   )r:   )r;   r   r(   r   )
r   r   rW   r   r"   r   rX   r   r(   rY   )re   r:   )r    r   r;   r   r(   r   )r   r   r    r   r(   rj   )r   r   rp   r   r    r   r"   r   rX   r   r(   rY   )r   r   r   r#   r1   r9   rV   rd   ri   rs   rw   r   r   r   r&   r&   #   s´   € € € € € ØPÐPð0ð 0ð 0ð 0ð\ð \ð \ð \ð
ð 
ð 
ð 
ð 
ð0ð ð ð ð2
ð 
ð 
ð 
ð 
ð$ð ð ð ðð ð ð ð ð r   r&   c                  ó,   — e Zd ZdZddd
„Zdd„Zdd„ZdS )ÚRateLimiterz1Sliding-window rate limiter per client IP or key.é   ç      N@Úmax_requestsr   Úwindow_secondsÚfloatr(   r)   c                ó0   — || _         || _        i | _        d S )N)r|   r}   Ú_history)r0   r|   r}   s      r   r1   zRateLimiter.__init__œ   s   € Ø(ˆÔØ,ˆÔØ02ˆŒˆˆr   Úkeyr   Úboolc                ó.  ‡— t          j         ¦   «         }| j                             |g ¦  «        }|| j        z
  Šˆfd„|D ¦   «         | j        |<   t	          | j        |         ¦  «        | j        k     r"| j        |                              |¦  «         dS dS )Nc                ó    •— g | ]
}|‰k    ¯|‘ŒS r   r   ©Ú.0ÚtÚcutoffs     €r   ú
<listcomp>z*RateLimiter.is_allowed.<locals>.<listcomp>§   s   ø€ ÐBÐBÐB A°q¸6²z°z˜a°z°z°zr   TF)rP   r€   Ú
setdefaultr}   Úlenr|   Úappend)r0   r�   ÚnowÚ
timestampsrˆ   s       @r   Ú
is_allowedzRateLimiter.is_allowed¡   s–   ø€ ÝŒi‰kŒkˆØ”]×-Ò-¨c°2Ñ6Ô6ˆ
ð �tÔ*Ñ*ˆØBÐBÐBÐB¨ÐBÑBÔBˆŒ�cÑåˆtŒ}˜SÔ!Ñ"Ô" TÔ%6Ò6Ð6ØŒM˜#Ô×%Ò% cÑ*Ô*Ð*Ø�4Øˆur   c                óÖ   ‡— t          j         ¦   «         }|| j        z
  Šˆfd„| j                             |g ¦  «        D ¦   «         }t	          d| j        t          |¦  «        z
  ¦  «        S )Nc                ó    •— g | ]
}|‰k    ¯|‘ŒS r   r   r…   s     €r   r‰   z-RateLimiter.get_remaining.<locals>.<listcomp>±   s   ø€ ÐEÐEÐE�q¸!¸fº*¸*�¸*¸*¸*r   r   )rP   r}   r€   ÚgetÚmaxr|   r‹   )r0   r�   r�   Úvalidrˆ   s       @r   Úget_remainingzRateLimiter.get_remaining®   sd   ø€ ÝŒi‰kŒkˆØ�tÔ*Ñ*ˆØEÐEÐEÐE˜DœM×-Ò-¨c°2Ñ6Ô6ÐEÑEÔEˆÝ�1�dÔ'­#¨e©*¬*Ñ4Ñ5Ô5Ð5r   N)rz   r{   )r|   r   r}   r~   r(   r)   )r�   r   r(   r‚   )r�   r   r(   r   )r   r   r   r#   r1   r�   r•   r   r   r   ry   ry   ™   s[   € € € € € Ø;Ð;ð3ð 3ð 3ð 3ð 3ð
ð ð ð ð6ð 6ð 6ð 6ð 6ð 6r   ry   )r#   Ú
__future__r   Úenumr6   r4   rM   r^   rP   rJ   Útypingr   r   r   r   r   Údataclassesr	   ÚEnumr   r   r&   ry   r   r   r   ú<module>r›      sq  ððð ð
 #Ð "Ð "Ð "Ð "Ð "Ø €€€Ø €€€Ø €€€Ø €€€Ø €€€Ø €€€Ø €€€Ø 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ø !Ð !Ð !Ð !Ð !Ð !ðð ð ð ð �$”)ñ ô ð ð
 €�˜dÐ#Ñ#Ô#ðð ð ð ð ñ ô ñ $Ô#ððsð sð sð sð sñ sô sð sðl6ð 6ð 6ð 6ð 6ñ 6ô 6ð 6ð 6ð 6r   