U
    ÏnajjQ  ã                   @  s  d Z ddlmZ ddlZddlZddlmZm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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mZ ddlmZmZmZ eeƒ  ¡ j!d	 Z"d
Z#dZ$dZ%dZ&dZ'dZ(dZ)dZ*e +d¡Z,dddœdd„Z-dddœdd„Z.e'dœddddœdd„Z/e	dd �G d!d"„ d"ƒƒZ0G d#d$„ d$ƒZ1G d%d&„ d&ƒZ2d'ddœd(d)„Z3d*d+œdd,dd-œd.d/„Z4G d0d1„ d1ej5j6ƒZ7e(dd2œd3d"dd,d4d5œd6d7„Z8dddd8œd9d:„Z9d;d<œd=d>„Z:d;d<œd?d@„Z;e<dAk�re;ƒ  dS )Ba  Password-protected static server for the V3 Face review viewer.

The server intentionally uses only Python's standard library.  It keeps the
existing byte-range support required by audio, video, VRM, and FBX assets,
while putting the whole origin behind a server-side password check.
é    )ÚannotationsN)ÚdefaultdictÚdeque)Ú	dataclass)ÚPath)ÚDeque)Úparse_qsÚquoteÚurlsplité   z%/tools/blendshape-player-natural.htmlz__Host-face_sessioni   Zpbkdf2_sha256iÀ'	 i€p  i,  é   zbytes=(\d*)-(\d*)\ZÚbytesÚstr)ÚvalueÚreturnc                 C  s   t  | ¡ d¡ d¡S )Nó   =Úascii)Úbase64Zurlsafe_b64encodeÚrstripÚdecode©r   © r   úface_viewer_server.pyÚ
_b64encode,   s    r   c                 C  s   t  | dt| ƒ d   ¡S )Nú=é   )r   Zurlsafe_b64decodeÚlenr   r   r   r   Ú
_b64decode0   s    r   )Ú
iterationsÚint)Úpasswordr   r   c                C  sV   t | ƒdk rtdƒ‚t d¡}t d|  d¡||¡}t› d|› dt|ƒ› dt|ƒ› �S )zFReturn a salted PBKDF2-SHA256 password hash suitable for the env file.é   z,password must contain at least 12 charactersé   Úsha256úutf-8ú$)	r   Ú
ValueErrorÚsecretsZtoken_bytesÚhashlibÚpbkdf2_hmacÚencodeÚPASSWORD_HASH_SCHEMEr   )r    r   ÚsaltZdigestr   r   r   Úmake_password_hash4   s
    
r-   T)Úfrozenc                   @  sJ   e Zd ZU ded< ded< ded< edddœd	d
„ƒZdddœdd„ZdS )ÚPasswordVerifierr   r   r   r,   Úexpectedr   z'PasswordVerifier')Úencodedr   c           
   
   C  sÊ   z0|  dd¡\}}}}t|ƒ}t|ƒ}t|ƒ}W n0 ttfk
r` }	 ztdƒ|	‚W 5 d }	~	X Y nX |tkrxtd|› �ƒ‚d|  krŒdks–n tdƒ‚t|ƒdk s´t|ƒt ¡ j	kr¼td	ƒ‚| |||d
�S )Nr%   é   z(invalid FACE_VIEWER_PASSWORD_HASH formatz"unsupported password hash scheme: i † i@KL z;password hash iterations must be between 100000 and 5000000r"   z+invalid password hash salt or digest length)r   r,   r0   )
Úsplitr   r   Ú	TypeErrorr&   r+   r   r(   r#   Zdigest_size)
Úclsr1   ÚschemeZraw_iterationsZraw_saltZraw_expectedr   r,   r0   Úexcr   r   r   Úfrom_encodedC   s    zPasswordVerifier.from_encodedÚbool)r    r   c                 C  s(   t  d| d¡| j| j¡}t || j¡S )Nr#   r$   )r(   r)   r*   r,   r   ÚhmacZcompare_digestr0   )Úselfr    Zactualr   r   r   ÚverifyT   s       ÿzPasswordVerifier.verifyN)Ú__name__Ú
__module__Ú__qualname__Ú__annotations__Úclassmethodr8   r<   r   r   r   r   r/   =   s   
r/   c                   @  s^   e Zd Zefdddœdd„Zddœdd	„Zd
ddœdd„Zd
ddœdd„Zdddœdd„ZdS )ÚSessionStorer   ÚNone)Úttl_secondsr   c                 C  s   || _ i | _t ¡ | _d S ©N)rD   Ú	_sessionsÚ	threadingÚLockÚ_lock)r;   rD   r   r   r   Ú__init__\   s    zSessionStore.__init__r   ©r   c              	   C  sB   t  d¡}| j�( |  t ¡ ¡ t ¡ | j | j|< W 5 Q R X |S )Né    )r'   Ztoken_urlsaferI   Ú_purge_lockedÚtimerD   rF   ©r;   Útokenr   r   r   Úcreatea   s
    
zSessionStore.createú
str | Noner9   )rP   r   c              
   C  sT   |sdS t   ¡ }| j�4 |  |¡ | j |¡}|d k	o<||kW  5 Q R £ S Q R X d S )NF)rN   rI   rM   rF   Úget)r;   rP   ÚnowÚ
expires_atr   r   r   Úvalidh   s    
zSessionStore.validc              	   C  s,   |sd S | j � | j |d ¡ W 5 Q R X d S rE   )rI   rF   ÚpoprO   r   r   r   Úrevokeq   s    zSessionStore.revokeÚfloat)rT   r   c                   s4   ‡ fdd„| j  ¡ D ƒ}|D ]}| j  |d ¡ qd S )Nc                   s   g | ]\}}|ˆ kr|‘qS r   r   )Ú.0rP   rU   ©rT   r   r   Ú
<listcomp>x   s      z.SessionStore._purge_locked.<locals>.<listcomp>)rF   ÚitemsrW   )r;   rT   ZexpiredrP   r   r[   r   rM   w   s    zSessionStore._purge_lockedN)	r=   r>   r?   ÚSESSION_TTL_SECONDSrJ   rQ   rV   rX   rM   r   r   r   r   rB   [   s
   	rB   c                   @  sh   e Zd Zeedœddddœdd„Zddd	œd
d„Zddd	œdd„Zddd	œdd„Zddddœdd„Z	dS )ÚLoginRateLimiter)Úmax_failuresÚwindow_secondsr   rC   )r`   ra   r   c                C  s$   || _ || _ttƒ| _t ¡ | _d S rE   )r`   ra   r   r   Ú	_failuresrG   rH   rI   )r;   r`   ra   r   r   r   rJ   ~   s    
zLoginRateLimiter.__init__r   r9   )Ú	client_idr   c              
   C  s<   | j �, |  |t ¡ ¡}t|ƒ| jkW  5 Q R £ S Q R X d S rE   )rI   Ú_active_failures_lockedrN   r   r`   ©r;   rc   Úfailuresr   r   r   Ú
is_limited‰   s    zLoginRateLimiter.is_limitedc              	   C  s4   | j �$ |  |t ¡ ¡}| t ¡ ¡ W 5 Q R X d S rE   )rI   rd   rN   Úappendre   r   r   r   Úrecord_failureŽ   s    zLoginRateLimiter.record_failurec              	   C  s$   | j � | j |d ¡ W 5 Q R X d S rE   )rI   rb   rW   )r;   rc   r   r   r   Úclear“   s    zLoginRateLimiter.clearrY   zDeque[float])rc   rT   r   c                 C  sf   | j | }|| j }|r.|d |kr.| ¡  q|sbt| j ƒD ]$}||kr<| j | s<| j  |d ¡ q<|S )Nr   )rb   ra   ÚpopleftÚlistrW   )r;   rc   rT   rf   ÚcutoffÚkeyr   r   r   rd   —   s    


z(LoginRateLimiter._active_failures_lockedN)
r=   r>   r?   ÚMAX_FAILURES_PER_WINDOWÚFAILURE_WINDOW_SECONDSrJ   rg   ri   rj   rd   r   r   r   r   r_   }   s   ür_   rR   c                 C  sV   | st S t| ƒ}|js4|js4|j d¡r4|j d¡r8t S |j}|jrR|d|j 7 }|S )z>Allow only same-origin absolute paths in post-login redirects.ú/z//ú?)ÚDEFAULT_TARGETr
   r6   ZnetlocÚpathÚ
startswithÚquery)r   ÚpartsÚresultr   r   r   Ú	safe_next¥   s    $ry   F©Ú
show_errorr9   )Únext_urlr{   r   c                C  s:   t jt| ƒdd�}|rdnd}d|› d|› d�}| d¡S )	NT)r	   uL   <p class="error" role="alert">ë¹„ë°€ë²ˆí˜¸ê°€ ì˜¬ë°”ë¥´ì§€ ì•ŠìŠµë‹ˆë‹¤.</p>Ú uN  <!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>V3 Face â€” Review Access</title>
  <style>
    * { box-sizing: border-box; }
    html, body { min-height: 100%; }
    body {
      margin: 0; display: grid; place-items: center; padding: 24px;
      color: #1e293b; background: #f8fafc;
      font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    }
    main {
      width: min(100%, 400px); padding: 34px; background: #fff;
      border: 1px solid #e2e8f0; border-radius: 16px;
      box-shadow: 0 20px 45px rgba(15, 23, 42, .08);
    }
    .mark { width: 34px; height: 34px; border-radius: 10px; margin-bottom: 22px;
      background: linear-gradient(135deg, #0052ff, #4d7cff); }
    h1 { margin: 0 0 8px; font-size: 22px; letter-spacing: -.02em; }
    .sub { margin: 0 0 24px; color: #64748b; font-size: 14px; line-height: 1.55; }
    label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 650; }
    input { width: 100%; height: 46px; border: 1px solid #cbd5e1; border-radius: 9px;
      padding: 0 13px; font: inherit; outline: none; }
    input:focus { border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37, 99, 235, .14); }
    button { width: 100%; height: 46px; margin-top: 14px; border: 0; border-radius: 9px;
      color: white; background: #155eef; font: inherit; font-weight: 700; cursor: pointer; }
    button:hover { background: #0b4fd6; }
    .error { margin: 0 0 14px; padding: 10px 12px; border-radius: 8px;
      color: #b42318; background: #fef3f2; font-size: 13px; }
  </style>
</head>
<body>
  <main>
    <div class="mark" aria-hidden="true"></div>
    <h1>V3 Face Review</h1>
    <p class="sub">GoodGangLabs ì‚¬ë‚´ ê²€í† ìš© íŽ˜ì�´ì§€ìž…ë‹ˆë‹¤.<br>ê³µìœ ë°›ì�€ ë¹„ë°€ë²ˆí˜¸ë¥¼ ìž…ë ¥í•´ ì£¼ì„¸ìš”.</p>
    z]
    <form method="post" action="/auth/login">
      <input type="hidden" name="next" value="u  ">
      <label for="password">ë¹„ë°€ë²ˆí˜¸</label>
      <input id="password" name="password" type="password" required autofocus
             autocomplete="current-password" maxlength="256">
      <button type="submit">ë“¤ì–´ê°€ê¸°</button>
    </form>
  </main>
</body>
</html>
r$   )ÚhtmlÚescapery   r*   )r|   r{   Zescaped_nextÚerrorZdocumentr   r   r   Ú_login_document²   s    ÿý'Ù)×3r�   c                      s~  e Zd ZU dZded< ded< ded< ded	< d
Zded< ddœ‡ fdd„Zddœ‡ fdd„Zddœdd„Zddœdd„Z	ddœdd„Z
dddœdd„Zddœdd„Zddœdd„Zd d!d
d"œd#d$dddd%œd&d'„Zd$d(d#ddd)œd*d+„Zd(d#ddd,œd-d.„Zd#dd/œd0d1„Zdddœ‡ fd2d3„Zed#d$d4d5œd6d7„ƒZd#d$d$dd8œd9d:„Zddœd;d<„Zd=dœd>d?„Zd#dœd@dA„Z‡  ZS )BÚFaceViewerHandlerzFaceViewer/2r/   Úpassword_verifierrB   Úsessionsr_   Úrate_limiterr   ÚrootTr9   Útrust_proxyrC   rK   c                   s    t ƒ j|dt| jƒi|—Ž d S )NZ	directory)ÚsuperrJ   r   r†   )r;   ÚargsÚkwargs©Ú	__class__r   r   rJ   ÷   s    zFaceViewerHandler.__init__c                   sJ   |   dd¡ |   dd¡ |   dd¡ |   dd¡ |   d	d
¡ tƒ  ¡  d S )NzCache-Controlzprivate, no-store, max-age=0ZPragmazno-cachezX-Content-Type-OptionsZnosniffzReferrer-Policyzsame-originzX-Frame-OptionsZDENY)Úsend_headerrˆ   Úend_headers©r;   r‹   r   r   rŽ   ú   s    zFaceViewerHandler.end_headersc                 C  s   | j dd� d S )NT©Ú	send_body©Ú_route_get_or_headr�   r   r   r   Údo_GET  s    zFaceViewerHandler.do_GETc                 C  s   | j dd� d S )NFr�   r’   r�   r   r   r   Údo_HEAD  s    zFaceViewerHandler.do_HEADc                 C  s>   t | jƒj}|dkr|  ¡  n|dkr0|  ¡  n
|  d¡ d S )Nz/auth/loginz/auth/logouté”  )r
   rt   Ú_handle_loginÚ_handle_logoutÚ
send_error)r;   rt   r   r   r   Údo_POST  s    

zFaceViewerHandler.do_POST)r‘   r   c                C  sÞ   t | jƒ}|j}|dkr.| jddd|d� d S |dkrxtt|jƒ dtg¡d ƒ}|  ¡ rf|  	|¡ n| j
||d� d S | d	¡r�|  d
¡ d S |  ¡ sÎ||jrªd|j nd }|  	dtt|ƒdd� ¡ d S | j|d� d S )Nz/healthzéÈ   s   ok
ztext/plain; charset=utf-8r�   ú/loginÚnextr   z/auth/i•  rr   r}   z/login?next=)Zsafe)r
   rt   Ú_send_bytesry   r   rv   rS   rs   Ú_is_authenticatedÚ	_redirectÚ_send_loginru   r™   r	   Ú_serve_static)r;   r‘   rw   rt   r|   Z	requestedr   r   r   r“     s&    


z$FaceViewerHandler._route_get_or_headc           	   	   C  sà  |   ¡ }| j |¡rF|  d¡ |  dttƒ¡ |  dd¡ |  ¡  d S | j 	dd¡}zt
|ƒ}W n" tk
r‚   |  dd¡ Y d S X |dk s”|tkr¢|  d	¡ d S | j 	d
d¡ dd¡d  ¡  ¡ }|dkrÚ|  d¡ d S zt| j |¡ d¡dd�}W n" tk
�r   |  d¡ Y d S X | 	ddg¡d d d… }t| 	dtg¡d ƒ}| j |¡�sz| j |¡ | j|ddd� d S | j |¡ | j ¡ }|  d¡ |  d|¡ |  dt› d|› d| jj › d�¡ |  dd¡ |  ¡  d S )Ni­  zRetry-AfterúContent-LengthÚ0r}   i�  zInvalid Content-Lengthr   i�  úContent-Typeú;r   z!application/x-www-form-urlencodediŸ  r$   T)Zkeep_blank_valuesr    é   r�   i‘  )Ústatusr{   é/  ÚLocationú
Set-Cookier   z; Path=/; Max-Age=z ; Secure; HttpOnly; SameSite=Lax)!Ú
_client_idr…   rg   Úsend_responser�   r   rp   rŽ   ÚheadersrS   r   r&   r™   ÚMAX_FORM_BYTESr3   ÚstripÚlowerr   ZrfileÚreadr   ÚUnicodeDecodeErrorry   rs   rƒ   r<   ri   r¡   rj   r„   rQ   ÚCOOKIE_NAMErD   )	r;   rc   Z
raw_lengthÚlengthÚcontent_typeZfieldsr    r|   rP   r   r   r   r—   *  sR    

"



þzFaceViewerHandler._handle_loginc                 C  sP   | j  |  ¡ ¡ |  d¡ |  dd¡ |  dt› d�¡ |  dd¡ |  ¡  d S )Nr©   rª   rœ   r«   z4=; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=Laxr£   r¤   )r„   rX   Ú_session_tokenr­   r�   r´   rŽ   r�   r   r   r   r˜   V  s    
þz FaceViewerHandler._handle_logoutr›   F)r¨   r{   r‘   r   r   )r|   r¨   r{   r‘   r   c                C  s6   t ||d�}|  |¡ |  dd¡ | j|d|d� d S )Nrz   zContent-Security-Policyzjdefault-src 'none'; style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'ztext/html; charset=utf-8r�   )r�   r­   r�   Ú_finish_bytes)r;   r|   r¨   r{   r‘   Úbodyr   r   r   r¡   a  s    
zFaceViewerHandler._send_loginr   )r¨   r¹   r¶   r‘   r   c                C  s   |   |¡ | j|||d� d S )Nr�   )r­   r¸   )r;   r¨   r¹   r¶   r‘   r   r   r   rž   n  s    
zFaceViewerHandler._send_bytes)r¹   r¶   r‘   r   c                C  s<   |   d|¡ |   dtt|ƒƒ¡ |  ¡  |r8| j |¡ d S )Nr¥   r£   )r�   r   r   rŽ   ÚwfileÚwrite)r;   r¹   r¶   r‘   r   r   r   r¸   r  s
    zFaceViewerHandler._finish_bytes)Úlocationr   c                 C  s.   |   d¡ |  d|¡ |  dd¡ |  ¡  d S )Nr©   rª   r£   r¤   )r­   r�   rŽ   )r;   r¼   r   r   r   r    y  s    
zFaceViewerHandler._redirectc          	   	     s–  |   | j¡}tj |¡r6|r(tƒ  ¡  n
tƒ  ¡  d S tj |¡sP|  d¡ d S tj 	|¡}| j
 d¡}|sÂ|  d¡ |  d|  |¡¡ |  dd¡ |  dt|ƒ¡ |  ¡  |r¾|  |d|¡ d S |  ||¡}|d k�r|  d	¡ |  d
d|› �¡ |  dd¡ |  ¡  d S |\}}|| d }|  d¡ |  d|  |¡¡ |  dd¡ |  d
d|› d|› d|› �¡ |  dt|ƒ¡ |  ¡  |�r’|  |||¡ d S )Nr–   ZRanger›   r¥   zAccept-Rangesr   r£   r   i   zContent-Rangezbytes */r¤   r   éÎ   zbytes ú-rq   )Ztranslate_pathrt   ÚosÚisdirrˆ   r”   r•   Úisfiler™   Úgetsizer®   rS   r­   r�   Z
guess_typer   rŽ   Ú_copy_file_rangeÚ_parse_range)	r;   r‘   rt   ÚsizeZrange_headerZ
byte_rangeÚstartÚendrµ   r‹   r   r   r¢     sJ    





zFaceViewerHandler._serve_staticztuple[int, int] | None)r   rÅ   r   c                 C  sª   t  |  ¡ ¡}|r|dkrd S | ¡ \}}|s6|s6d S |sdt|ƒ}|dkrNd S td|| ƒ|d fS t|ƒ}|rxt|ƒn|d }t||d ƒ}||ksž||kr¢d S ||fS )Nr   r   )ÚRANGE_REÚ	fullmatchr°   Úgroupsr   ÚmaxÚmin)r   rÅ   ÚmatchZ	raw_startZraw_endZsuffix_lengthrÆ   rÇ   r   r   r   rÄ   §  s"    zFaceViewerHandler._parse_range)rt   rÆ   rµ   r   c              	   C  sz   z\t |dƒ�H}| |¡ |}|rP| td|ƒ¡}|s6qP| j |¡ |t|ƒ8 }qW 5 Q R X W n ttfk
rt   Y nX d S )NÚrbi   )	ÚopenÚseekr²   rÌ   rº   r»   r   ÚBrokenPipeErrorÚConnectionResetError)r;   rt   rÆ   rµ   ÚsourceZ	remainingÚchunkr   r   r   rÃ   »  s    
z"FaceViewerHandler._copy_file_rangec                 C  s   | j  |  ¡ ¡S rE   )r„   rV   r·   r�   r   r   r   rŸ   É  s    z#FaceViewerHandler._is_authenticatedrR   c                 C  s`   | j  d¡}|sd S tj ¡ }z| |¡ W n tjjk
rF   Y d S X | t¡}|r\|jS d S )NZCookie)	r®   rS   ÚhttpZcookiesZSimpleCookieÚloadZCookieErrorr´   r   )r;   Z
raw_cookieZcookieZmorselr   r   r   r·   Ì  s    

z FaceViewerHandler._session_tokenc                 C  sH   | j r>| j dd¡ ¡ }ztt |¡ƒW S  tk
r<   Y nX | jd S )NzCF-Connecting-IPr}   r   )	r‡   r®   rS   r°   r   Ú	ipaddressZ
ip_addressr&   Zclient_address)r;   Z	forwardedr   r   r   r¬   Ø  s    zFaceViewerHandler._client_id)r=   r>   r?   Zserver_versionr@   r‡   rJ   rŽ   r”   r•   rš   r“   r—   r˜   r¡   rž   r¸   r    r¢   ÚstaticmethodrÄ   rÃ   rŸ   r·   r¬   Ú__classcell__r   r   r‹   r   r‚   ï   s8   
	,ú(r‚   ©Úsession_ttlr‡   r   ztype[FaceViewerHandler])r†   ÚverifierrÛ   r‡   r   c                C  s<   G dd„ dt ƒ}|  ¡ |_||_t|ƒ|_tƒ |_||_|S )Nc                   @  s   e Zd ZdS )z2build_handler.<locals>.ConfiguredFaceViewerHandlerN)r=   r>   r?   r   r   r   r   ÚConfiguredFaceViewerHandleré  s   rÝ   )	r‚   Úresolver†   rƒ   rB   r„   r_   r…   r‡   )r†   rÜ   rÛ   r‡   rÝ   r   r   r   Úbuild_handlerâ  s    

rß   )Ú	raw_valueÚnamer   c              
   C  sZ   zt | ƒ}W n2 tk
r> } zt|› d�ƒ|‚W 5 d }~X Y nX |dkrVt|› d�ƒ‚|S )Nz must be an integerr   z must be positive)r   r&   )rà   rá   r   r7   r   r   r   Ú_positive_intô  s    "râ   rC   rK   c            	   
   C  s  t j dd¡} | stdƒ‚z:t | ¡}tt j dd¡dƒ}tt j dttƒ¡dƒ}W n0 t	k
r„ } ztt|ƒƒ|‚W 5 d }~X Y nX t
t j dttƒ¡ƒ ¡ }| ¡ s¶td|› �ƒ‚t j d	d
¡dk}t||||d�}tj d|f|¡}d|_td|› d|› �dd� | ¡  d S )NZFACE_VIEWER_PASSWORD_HASHr}   zAFACE_VIEWER_PASSWORD_HASH is required; refusing to start unlockedZFACE_VIEWER_PORTZ8095ZFACE_VIEWER_SESSION_TTLZFACE_VIEWER_ROOTz%FACE_VIEWER_ROOT is not a directory: ZFACE_VIEWER_TRUST_PROXYÚ1>   Úfalser¤   ÚFalserÚ   z	127.0.0.1Tzserving password-protected z on 127.0.0.1:)Úflush)r¿   ÚenvironrS   Ú
SystemExitr/   r8   râ   r   r^   r&   r   ÚDEFAULT_ROOTrÞ   Úis_dirrß   rÕ   ÚserverZThreadingHTTPServerZdaemon_threadsÚprintZserve_forever)	Zencoded_hashrÜ   ZportrÛ   r7   r†   r‡   Zhandlerrë   r   r   r   Úserveþ  s*    
þ rí   c               
   C  sž   t jtd�} | jdddd� |  ¡ }|jr”t d¡}t d¡}||krNtdƒ‚ztt	|ƒƒ W n0 t
k
rŽ } ztt|ƒƒ|‚W 5 d }~X Y nX d S tƒ  d S )	N)Zdescriptionz--hash-passwordÚ
store_truez8prompt for a password and print a salted hash, then exit)ÚactionÚhelpz
Password: zConfirm password: zpasswords do not match)ÚargparseÚArgumentParserÚ__doc__Úadd_argumentÚ
parse_argsZhash_passwordÚgetpassrè   rì   r-   r&   r   rí   )Úparserr‰   r    Zconfirmationr7   r   r   r   Úmain  s$    ý

 rø   Ú__main__)=ró   Z
__future__r   rñ   r   Úcollectionsr   r   Zdataclassesr   rö   r(   r:   r~   Zhttp.cookiesrÕ   Zhttp.serverr×   r¿   Úpathlibr   Úrer'   rG   rN   Útypingr   Zurllib.parser   r	   r
   Ú__file__rÞ   Úparentsré   rs   r´   r¯   r+   ZPASSWORD_HASH_ITERATIONSr^   rp   ro   ÚcompilerÈ   r   r   r-   r/   rB   r_   ry   r�   rë   ZSimpleHTTPRequestHandlerr‚   rß   râ   rí   rø   r=   r   r   r   r   Ú<module>   sb   
	"(= xû

