Ë
    ¶¨js  ã                  óž  — U d Z ddlm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
 ddlmZmZmZ ddlmZ ddlmZmZmZ dd	lmZmZmZmZ dd
lmZ ddlmZmZmZ ddlm Z  dZ!dZ"h d£Z#dZ$ e«       Z%d$d„Z&d%d„Z'd&d„Z( ee)«      Z*de+d<   dZ,dZ-d'd„Z.d'd„Z/d'd„Z0d(d„Z1d)d„Z2e	 G d„ d«      «       Z3d*d„Z4 ee«      fd+d „Z5d,d-d!„Z6d.d"„Z7d/d#„Z8y)0aG  Invite-only accounts: argon2id passwords, server-side sessions in an
HttpOnly cookie, and a CSRF header on unsafe requests.

Every permission check goes through `current_user` + `require_center` /
`require_admin`, so an external identity provider (e.g. NAC's Stytch) can
replace this module later without touching the routers.
é    )ÚannotationsN)Údefaultdict)Ú	dataclassÚfield)ÚUTCÚdatetimeÚ	timedelta)ÚPasswordHasher)ÚInvalidHashErrorÚVerificationErrorÚVerifyMismatchError)ÚDependsÚHTTPExceptionÚRequestÚResponse)Ú
Connectioné   )ÚfetchallÚfetchoneÚget_conn)Úget_settingsÚavyg_sessionzx-csrf-token>   ÚGETÚHEADÚOPTIONSé
   c                óx   — t        | «      t        k  rt        ddt        › d�«      ‚t        j	                  | «      S )Ni¦  zPassword must be at least z characters.)ÚlenÚMIN_PASSWORDr   Ú_phÚhash)Úpasswords    ú%/var/www/avy-guidance/api/app/auth.pyÚhash_passwordr$   "   s4   € Ü
ˆ8ƒ}”|Ò#Ü˜CÐ#=¼l¸^È<Ð!XÓYÐYÜ�8‰8�HÓÐó    c                ód   — 	 t         j                  | |«      S # t        t        t        f$ r Y yw xY w)NF)r    Úverifyr   r   r   )Úpassword_hashr"   s     r#   Úverify_passwordr)   (   s3   € ðÜ�z‰z˜-¨Ó2Ð2øÜÔ!2Ô4DÐEò Ùðús   ‚ ˜/®/c                ód   — t        j                  | j                  «       «      j                  «       S ©N)ÚhashlibÚsha256ÚencodeÚ	hexdigest)Útokens    r#   Ú
token_hashr1   /   s    € Ü�>‰>˜%Ÿ,™,›.Ó)×3Ñ3Ó5Ð5r%   zdict[str, list[float]]Ú	_ATTEMPTSi„  c                óÖ   — t        j                  «       }t        |    D �cg c]  }||z
  t        k  sŒ|‘Œ c}t        | <   t	        t        |    «      t
        k\  rt        dd«      ‚y c c}w )Ni­  z1Too many login attempts. Try again in 15 minutes.)ÚtimeÚ	monotonicr2   ÚWINDOW_Sr   ÚMAX_ATTEMPTSr   )ÚkeyÚnowÚts      r#   Úcheck_throttler;   :   s[   € Ü
�.‰.Ó
€CÜ!*¨3¡ÖF˜A°3¸±7¼XÓ3E’aÒF„Iˆc�NÜ
Œ9�S‰>ÓœlÒ*Ü˜CÐ!TÓUÐUð +ùò Gs
    A&²A&c                óX   — t         |    j                  t        j                  «       «       y r+   )r2   Úappendr4   r5   ©r8   s    r#   Úrecord_failurer?   A   s   € Üˆc�N×Ñœ$Ÿ.™.Ó*Õ+r%   c                ó0   — t         j                  | d «       y r+   )r2   Úpopr>   s    r#   Úclear_throttlerB   E   s   € Ü‡M�M�#�tÕr%   c           	     óº  — t        «       }t        j                  d«      }t        j                  d«      }t        j                  t
        «      t        |j                  ¬«      z   }| j                  dt        |«      |||f«       | j                  d|f«       | j                  d«       |j                  t        ||j                  dz  d|j                  d	d
¬«       |S )Né    é   )ÚdayszRINSERT INTO sessions (id, user_id, csrf_token, expires_at) VALUES (%s, %s, %s, %s)z4UPDATE users SET last_login_at = now() WHERE id = %sz-DELETE FROM sessions WHERE expires_at < now()i€Q TÚlaxú/)Úmax_ageÚhttponlyÚsecureÚsamesiteÚpath)r   ÚsecretsÚtoken_urlsafer   r9   r   r	   Úsession_daysÚexecuter1   Ú
set_cookieÚCOOKIEÚcookie_secure)ÚconnÚuser_idÚresponseÚsettingsr0   ÚcsrfÚexpiress          r#   Úcreate_sessionr[   L   sÇ   € Ü‹~€HÜ×!Ñ! "Ó%€EÜ× Ñ  Ó$€DÜ�l‰lœ3Ó¤)°×1FÑ1FÔ"GÑG€GØ‡L�LØ\Ü	�EÓ	˜G T¨7Ð3ôð 	‡L�LÐGÈ'ÈÔTØ‡L�LÐ@ÔAØ×ÑÜØØ×%Ñ%¨Ñ-ØØ×%Ñ%ØØð ô ð €Kr%   c                ó¬   — |j                   j                  t        «      }|r| j                  dt	        |«      f«       |j                  t        d¬«       y )Nz"DELETE FROM sessions WHERE id = %srH   )rM   )ÚcookiesÚgetrS   rQ   r1   Údelete_cookie)rU   ÚrequestrW   r0   s       r#   Údestroy_sessionra   c   sC   € Ø�O‰O×Ñ¤Ó'€EÙØ�‰Ð9¼JÀuÓ<MÐ;OÔPØ×Ñœ6¨ÐÕ,r%   c                  ód   — e Zd ZU ded<   ded<   ded<   ded<   ded<    ee¬	«      Zd
ed<   dd„Zy)ÚUserÚintÚidÚstrÚemailÚnameÚboolÚis_adminrY   )Údefault_factoryúdict[int, str]Úmembershipsc                óR   — | j                   ry| j                  j                  |«      S )NÚcenter_admin)rj   rm   r^   )ÚselfÚ	center_ids     r#   Úrole_inzUser.role_ins   s#   € Ø�=Š=Ø!Ø×Ñ×#Ñ# IÓ.Ð.r%   N)rq   rd   Úreturnz
str | None)Ú__name__Ú
__module__Ú__qualname__Ú__annotations__r   Údictrm   rr   © r%   r#   rc   rc   j   s/   … àƒGØƒJØ
ƒIØƒNØ
ƒIÙ"'¸Ô"=€K�Ó=ô/r%   rc   c                óV   — t        | d|f«      }|D �ci c]  }|d   |d   “Œ c}S c c}w )Nz:SELECT center_id, role FROM memberships WHERE user_id = %srq   Úrole)r   )rU   rV   ÚrowsÚrs       r#   Úload_membershipsr~   y   s6   € Ü�DÐVÐY`ÐXbÓc€DØ/3Ö4¨!ˆAˆk‰N˜A˜f™IÑ%Ò4Ð4ùÒ4s   “&c                ó.  — | j                   j                  t        «      }|st        dd«      ‚t	        |dt        |«      f«      }|st        dd«      ‚| j                  t        vrCt        j                  | j                  j                  t        d«      |d   «      st        dd«      ‚t        j                  t        «      |d	   z
  t        d
¬«      kD  r|j!                  d|d   f«       t#        |d   |d   |d   |d   |d   t%        ||d   «      ¬«      S )Ni‘  zNot signed inzÓSELECT s.id AS sid, s.csrf_token, s.last_seen_at, u.id, u.email, u.name, u.is_admin
           FROM sessions s JOIN users u ON u.id = s.user_id
           WHERE s.id = %s AND s.expires_at > now() AND u.is_activezSession expiredÚ Ú
csrf_tokené“  zMissing or invalid CSRF tokenÚlast_seen_atr   )Úminutesz6UPDATE sessions SET last_seen_at = now() WHERE id = %sÚsidre   rg   rh   rj   )re   rg   rh   rj   rY   rm   )r]   r^   rS   r   r   r1   ÚmethodÚSAFE_METHODSrN   Úcompare_digestÚheadersÚCSRF_HEADERr   r9   r   r	   rQ   rc   r~   )r`   rU   r0   Úrows       r#   Úcurrent_userrŒ   ~   s  € Ø�O‰O×Ñ¤Ó'€EÙÜ˜C Ó1Ð1Ü
Øð	Gô 
�EÓ	Ðó€Cñ Ü˜CÐ!2Ó3Ð3Ø‡~�~œ\Ñ)´'×2HÑ2HØ�‰×ÑœK¨Ó,¨c°,Ñ.?ô3ô ˜CÐ!@ÓAÐAÜ‡|�|”CÓ˜3˜~Ñ.Ñ.´À2Ô1FÒFØ�‰ÐMÐPSÐTYÑPZÈ}Ô]ÜØˆt‰9Ø�'‰lØ�‰[Ø�Z‘Ø�ÑÜ$ T¨3¨t©9Ó5ôð r%   c                óR   — | j                  |«      }|�|r|dk7  rt        dd«      ‚|S )zPRaise 403 unless the user belongs to the center (as center_admin if admin=True).ro   r‚   z%You do not have access to this center)rr   r   )Úuserrq   Úadminr{   s       r#   Úrequire_centerr�   ›   s1   € à�<‰<˜	Ó"€DØ€|™ $¨.Ò"8Ü˜CÐ!HÓIÐIØ€Kr%   c                ó4   — | j                   st        dd«      ‚y )Nr‚   zAdministrators only)rj   r   )rŽ   s    r#   Úrequire_adminr’   £   s   € Ø�=Š=Ü˜CÐ!6Ó7Ð7ð r%   c           
     ó:  — t        | |j                  rdnd|j                  rd n|j                  f«      }|j                  |j                  |j                  |j                  dœ|D �cg c]  }i |¥d|j                  |d   «      i¥‘Œ c}|j                  dœS c c}w )Nz0SELECT id, slug, name FROM centers ORDER BY namezxSELECT c.id, c.slug, c.name FROM centers c JOIN memberships m ON m.center_id = c.id WHERE m.user_id = %s ORDER BY c.name)re   rg   rh   rj   r{   re   )rŽ   ÚcentersrY   )r   rj   re   rg   rh   rr   rY   )rU   rŽ   r”   Úcs       r#   Ú
me_payloadr–   ¨   s�   € ÜØà�=Š=ñ 	;ð HØ—’‰ D§G¡G :ó€Gð —w‘w¨¯©¸T¿Y¹YÐTX×TaÑTaÑbØBIÖJ¸QÐ8�qÐ8˜& $§,¡,¨q°©wÓ"7Ó8ÒJØ—	‘	ñð ùâJs   Á'!B)r"   rf   rs   rf   )r(   rf   r"   rf   rs   ri   )r0   rf   rs   rf   )r8   rf   rs   ÚNone)rU   r   rV   rd   rW   r   rs   rf   )rU   r   r`   r   rW   r   rs   r—   )rU   r   rV   rd   rs   rl   )r`   r   rU   r   rs   rc   )F)rŽ   rc   rq   rd   r�   ri   rs   rf   )rŽ   rc   rs   r—   )rU   r   rŽ   rc   rs   rx   )9Ú__doc__Ú
__future__r   r,   rN   r4   Úcollectionsr   Údataclassesr   r   r   r   r	   Úargon2r
   Úargon2.exceptionsr   r   r   Úfastapir   r   r   r   Úpsycopgr   Údbr   r   r   rX   r   rS   rŠ   r‡   r   r    r$   r)   r1   Úlistr2   rw   r6   r7   r;   r?   rB   r[   ra   rc   r~   rŒ   r�   r’   r–   ry   r%   r#   ú<module>r¢      sÜ   ðòõ #ã Û Û Ý #ß (ß -Ñ -å !ß VÑ Vß =Ó =Ý ç ,Ñ ,Ý "à	€Ø€Ú)€Ø€áÓ€óóó6ñ %0°Ó$5€	Ð!Ó 5Ø€Ø€óVó,óóó.-ð ÷/ð /ó ð/ó5ñ
 7>¸hÓ6Gô ô:ó8ô
r%   