ó
    v´jü%  ã                  ó
  • S r SSKJr  SSKrSSKrSSKJr  SSKJr  SSK	J
r
Jr  SSKJr  SSKJrJrJrJr  SS	KJrJrJrJr  SS
KJr  SSKJr  \" 1 Sk5      rSrSrSr Sr! " S S5      r" " S S5      r#SS jr$SS jr%SS jr&g)a@  WorkOS sign-in for the hosted playground.

Every request the app serves passes the login gate. A request carrying a live
session cookie for the configured organization goes through; otherwise the
gate redirects a browser to ``/login`` or refuses an API call with 401. A few
paths are open without a session: the sign-in routes themselves, the health
route the load balancer and the startup page poll, and the static assets,
which are the app's code and carry no document data.

Four routes come with the gate. ``/login`` sends the browser to WorkOS,
``/callback`` finishes the sign-in WorkOS sends back and sets the cookie,
``/logout`` clears the cookie and ends the WorkOS session, and ``/api/me``
tells the client who is signed in so it can offer sign-out. The sign-in
itself, the organization check and the cookie all come from
``quber.playground.session``, which the wake function shares: a person who
signs in while no task is running gets the same cookie from the function,
and the gate here accepts it once the task is up.

The gate is a pure ASGI middleware rather than a Starlette
``BaseHTTPMiddleware`` so the answer and batch streams pass through untouched.
It checks the organization on every request, not only at sign-in, so a
cookie issued under another organization, or before the setting changed, is
refused like an expired one.

``install`` decides whether the sign-in is on. All four settings unset means
the developer host, where the app runs open. All four set means the hosted
playground. A partial set is a misconfiguration and is refused at startup,
because the alternative is a hosted app that silently runs open.

The gate is also where signed-in use is counted. Every request that passes
with a valid session is one unit of activity, and ``ActivityMeter`` reports
the count to CloudWatch once a minute when a namespace is configured. The
hosted service's idle alarm watches that metric rather than the load
balancer's request count, because a public hostname is never quiet: internet
scanners reach the sign-in page every few minutes, and only a session tells a
person apart from them.
é    )ÚannotationsN)ÚOptional)Ú	urlencode)ÚFastAPIÚRequest)Úlogger)ÚHTMLResponseÚJSONResponseÚRedirectResponseÚResponse)ÚASGIAppÚReceiveÚScopeÚSend)Úsession)ÚPlaygroundSettings>   ú/loginú/logoutú/healthzú	/callback)z/static/ÚAuthenticatedRequestsé<   z/static/qubera-mark-160.pngc                  ó6   • \ rS rSrSrSS jrS	S jrS	S jrSrg)
ÚActivityMeteré>   a  Counts requests that carried a valid session and reports the count to
CloudWatch once a minute. A minute with nothing to report is reported as
zero, so the metric has a datapoint whenever a task is up; the alarm's
missing-data rule covers the time nothing is up at all.c                óº   • Xl         X l        SU l        [        R                  " 5       U l        [        R                  " U R                  SSS9R                  5         g )Nr   zactivity-meterT)ÚtargetÚnameÚdaemon)	Ú	namespaceÚserviceÚcountÚ	threadingÚLockÚlockÚThreadÚrunÚstart)Úselfr    r!   s      Ú3/home/mande/repo/quber/src/quber/playground/auth.pyÚ__init__ÚActivityMeter.__init__D   sA   € Ø"ŒØŒØˆŒ
Ü—N’NÓ$ˆŒ	Ü×Ò §¡Ð/?ÈÑM×SÑSÕUó    c                óz   • U R                      U =R                  S-  sl        S S S 5        g ! , (       d  f       g = f)Né   )r%   r"   )r)   s    r*   ÚrecordÚActivityMeter.recordK   s    € Ø�Y‹YØ�JŠJ˜!‰O�J÷ �YŽYús   �,¬
:c                óš  • SS K nUR                  S5      n [        R                  " [        5        U R
                     U R                  Sso0l        S S S 5         UR                  U R                  [        SU R                  S./WSS./S9  Mw  ! , (       d  f       NC= f! [         a!  n[        R                  " SU5         S nAN9S nAff = f)	Nr   Ú
cloudwatchÚService)ÚNameÚValueÚCount)Ú
MetricNameÚ
Dimensionsr6   ÚUnit)Ú	NamespaceÚ
MetricDatazactivity metric not sent: {})Úboto3ÚclientÚtimeÚsleepÚACTIVITY_INTERVAL_SECONDSr%   r"   Úput_metric_datar    ÚACTIVITY_METRICr!   Ú	Exceptionr   Úwarning)r)   r=   r>   r"   Úexcs        r*   r'   ÚActivityMeter.runO   s³   € Ûà—‘˜lÓ+ˆØÜ�JŠJÔ0Ô1Ø—“Ø$(§J¡J°Ð!�”z÷ ðDØ×&Ñ&Ø"Ÿn™nô +:Ø4=ÈÏÉÑ+UÐ*VØ%*Ø$+ñ	ð ð 'ñ 
ñ ç•ûô ó DÜ—’Ð=¸s×CÑCûðDús#   ½BÁ2B Â
BÂ
C
Â)CÃC
)r"   r%   r    r!   N)r    Ústrr!   rH   ÚreturnÚNone)rI   rJ   )	Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r+   r0   r'   Ú__static_attributes__© r-   r*   r   r   >   s   † ñ?ô
Vô÷Dr-   r   c                  óB   • \ rS rSrSS.         SS jjrSS jrSrg)	Ú	LoginGateég   N)Úmeterc               ó4   • Xl         X l        X0l        X@l        g ©N)ÚappÚsecretÚorganization_idrU   )r)   rX   rY   rZ   rU   s        r*   r+   ÚLoginGate.__init__h   s   € ð ŒØŒØ.ÔØ�
r-   c              ƒ  óV  #   • US   S:w  a  U R                  XU5      I S h  v•N   g US   nU[        ;   d  UR                  [        5      (       a  U R                  XU5      I S h  v•N   g [	        X5      n[
        R                  " U R                  U R                  UR                  R                  [
        R                  5      5      nUbV  U R                  b  U R                  R                  5         XaR                  S0 5      S'   U R                  XU5      I S h  v•N   g UR                  S5      (       a  [        SS0S	S
9nOHUR                  SS5      R!                  5       nXH(       a  SU-   OS-   n	[#        S[%        SU	05      -   SS
9nU" XU5      I S h  v•N   g  GN~ GN= Nˆ N7f)NÚtypeÚhttpÚpathÚstateÚidentityz/api/Údetailzlogin requiredi‘  ©Ústatus_codeÚquery_stringr-   Ú?Ú z/login?Únexté.  )rX   Ú
OPEN_PATHSÚ
startswithÚOPEN_PREFIXESr   r   ÚverifyrY   rZ   ÚcookiesÚgetÚCOOKIErU   r0   Ú
setdefaultr
   Údecoder   r   )
r)   ÚscopeÚreceiveÚsendr_   Úrequestra   ÚresponseÚqueryr   s
             r*   Ú__call__ÚLoginGate.__call__u   sr  é € Ø�‰=˜FÓ"Ø—(‘(˜5¨4Ó0×0Ð0ØØ�V‰}ˆØ”:Ó §¡´×!?Ñ!?Ø—(‘(˜5¨4Ó0×0Ð0ØÜ˜%Ó)ˆÜ—>’> $§+¡+¨t×/CÑ/CÀWÇ_Á_×EXÑEXÔY`×YgÑYgÓEhÓiˆØÑØ�z‰zÑ%Ø—
‘
×!Ñ!Ô#Ø8@×Ñ˜W bÓ)¨*Ñ5Ø—(‘(˜5¨4Ó0×0Ð0ØØ�?‰?˜7×#Ñ#Ü# XÐ/?Ð$@ÈcÑR‰Hà—I‘I˜n¨cÓ2×9Ñ9Ó;ˆEØ­E˜S 5š[°rÑ:ˆFÜ'¨	´I¸vÀvÐ>NÓ4OÑ(OÐ]`ÑaˆHÙ�u tÓ,×,Ñ,ò) 1ò 1ñ 1ñ 	-ùsH   ‚F)¡F¢AF)Á%F"Á&B6F)ÄF%ÄA<F)ÆF'ÆF)Æ"F)Æ%F)Æ'F))rX   rU   rZ   rY   )
rX   r   rY   rH   rZ   rH   rU   zOptional[ActivityMeter]rI   rJ   )rs   r   rt   r   ru   r   rI   rJ   )rK   rL   rM   rN   r+   ry   rP   rQ   r-   r*   rS   rS   g   sA   † ð *.ñàðð ð	ð
 ðð 'ðð 
õ÷-r-   rS   c                ó
  • U R                   R                  S5      =(       d    U R                  R                  n[        R
                  " XR                   R                  S5      =(       d    U R                  R                  5      $ )zuThe site the browser used. The load balancer terminates TLS, so the
browser's scheme arrives in the forwarded header.úx-forwarded-protoÚhost)Úheadersro   ÚurlÚschemer   Úbase_urlÚnetloc)rv   r€   s     r*   Úrequest_baserƒ   Ž   sW   € ð �_‰_× Ñ Ð!4Ó5×K¸¿¹×9KÑ9K€FÜ×Ò˜F§O¡O×$7Ñ$7¸Ó$?×$UÀ7Ç;Á;×CUÑCUÓVÐVr-   c                ó>   • U R                   R                  S5      S:H  $ )zgWhether to mark the cookie Secure: it is then sent back only over HTTPS,
which is every hosted request.r|   Úhttps)r~   ro   )rv   s    r*   Úsecurer†   •   s   € ð �?‰?×ÑÐ2Ó3°wÑ>Ð>r-   c                ó’  ^^• UR                   UR                  UR                  UR                  4n[	        U5      (       d  SS jnU R                  SUS/SS9  g[        U5      (       d  [        S5      eS U 5       u  pEnm[        R                  " XEUTS9mUR                  (       a  [        UR                  S	5      OS
nSSU4S jjjnSSU4S jjjn	SU4S jjn
SS jnU R                  SUS/[        SS9  U R                  SU	S/SS9  U R                  SU
S/SS9  U R                  SUS/SS9  U R                  [        TXgS9  g)a  Put the sign-in in front of ``app`` when settings ask for it.

Returns whether the sign-in is on. Raises when the four settings are only
partly set, so a hosted app never starts open by accident. ``/api/me`` is
added either way, answering an empty object on the developer host.
c                 ó   • 0 $ rW   rQ   rQ   r-   r*   Úme_openÚinstall.<locals>.me_openª   s   € ØˆIr-   z/api/meÚGETF)ÚmethodsÚinclude_in_schemazžWORKOS_API_KEY, WORKOS_CLIENT_ID, WORKOS_ORGANIZATION_ID and PLAYGROUND_SESSION_SECRET must be set together; the sign-in is on only when all four are present.c              3  ó8   #   • U  H  n[        U5      v •  M     g 7frW   )rH   )Ú.0Úvs     r*   Ú	<genexpr>Úinstall.<locals>.<genexpr>´   s   é € Ð2JÂ6¸a´3°q·6°6Â6ùs   ‚)Úapi_keyÚ	client_idrZ   Úsession_secretzquber-playgroundNc                ób   >• [        [        R                  " T[        U 5      U[        5      5      $ rW   )r	   r   Ú
login_pagerƒ   ÚMARK_SRC)rv   rh   Úconfigs     €r*   ÚloginÚinstall.<locals>.login¾   s%   ø€ ÜœG×.Ò.¨v´|ÀGÓ7LÈdÔT\Ó]Ó^Ð^r-   c           	     óÄ  >• [         R                  " T[        U 5      X[        5      nUR                  b  UR
                  cH  [        R                  " SUR                  5        [        UR                  =(       d    SUR                  S9$ [        UR
                  SS9nUR                  R                  S[         R                  " UR                  [        U 5      S95        U$ )Nzsign-in not accepted: status {}rg   rc   i/  ú
set-cookie©r†   )r   Úcomplete_sign_inrƒ   r˜   ÚtokenÚlocationr   ÚinfoÚstatusr	   Úpager   r~   ÚappendÚcookie_headerr†   )rv   Úcoder`   Úoutcomerw   r™   s        €r*   ÚcallbackÚinstall.<locals>.callbackÃ   s¦   ø€ Ü×*Ò*¨6´<ÀÓ3HÈ$ÔW_Ó`ˆØ�=‰=Ñ  G×$4Ñ$4Ñ$<Ü�KŠKÐ9¸7¿>¹>ÔJÜ §¡× 2°ÀÇÁÑOÐOÜ# G×$4Ñ$4À#ÑFˆØ×Ñ×Ñ ¬g×.CÒ.CÀGÇMÁMÔZ`ÐahÓZiÑ.jÔkØˆr-   c                ó’  >• [         R                  " TU R                  R                  [         R                  5      5      n[        U 5      nU(       a2  UR                  (       a!  [         R                  " UR                  U5      OSn[        USS9nUR                  R                  S[         R                  " [        U 5      S95        U$ )NÚ/ri   rc   r�   rž   )r   Úsigned_identityrn   ro   rp   rƒ   ÚsidÚ
logout_urlr   r~   r¥   Úclear_cookie_headerr†   )rv   ra   Úbaser   rw   rY   s        €r*   ÚlogoutÚinstall.<locals>.logoutÌ   sŒ   ø€ Ü×*Ò*¨6°7·?±?×3FÑ3FÄwÇ~Á~Ó3VÓWˆÜ˜GÓ$ˆÞ;CÈÏÏ”×#Ò# H§L¡L°$Ô7ÐZ]ˆÜ# F¸Ñ<ˆØ×Ñ×Ñ ¬g×.IÒ.IÔQWÐX_ÓQ`Ñ.aÔbØˆr-   c                óV   • [        U R                  SS 5      nUb  SUR                  0$ 0 $ )Nra   Úemail)Úgetattrr`   rµ   )rv   ra   s     r*   ÚmeÚinstall.<locals>.meÔ   s.   € Ü/6°w·}±}ÀjÐRVÓ/WˆØ,4Ñ,@�˜Ÿ™Ð(ÐHÀbÐHr-   r   )rŒ   Úresponse_classr�   r   r   )rY   rZ   rU   T)rI   údict[str, str]rW   )rv   r   rh   úOptional[str]rI   r	   )NN)rv   r   r§   r»   r`   r»   rI   r   )rv   r   rI   r   )rv   r   rI   rº   )Úworkos_api_keyÚworkos_client_idÚworkos_organization_idr•   ÚanyÚadd_api_routeÚallÚRuntimeErrorr   ÚWorkOSÚactivity_namespacer   r	   Úadd_middlewarerS   )rX   ÚsettingsÚvaluesr‰   r“   r”   rZ   rU   rš   r©   r²   r·   r™   rY   s               @@r*   ÚinstallrÈ   ›   sn  ù€ ð 	×ÑØ×!Ñ!Ø×'Ñ'Ø×Ñð	€Fô ˆv�;‰;ô	ð 	×Ñ˜) W°u°gÐQVÐÑWØÜˆv�;‰;ÜðVó
ð 	
ñ 3KÁ6Ó2JÑ/€G˜¨Ü�^Š^Ø¸oÐ^dñ€Fð
 ×&×&ô 	�h×1Ñ1Ð3EÔFàð 
÷_ñ _÷
ñ ÷ôIð ×Ñ�h °¨wÄ|ÐglÐÑmØ×Ñ�k 8°e°WÐPUÐÑVØ×Ñ�i °%°ÈEÐÑRØ×Ñ�i ¨e¨WÈÐÑNØ×Ñ”y¨ÀÐÑ^Ør-   )rv   r   rI   rH   )rv   r   rI   Úbool)rX   r   rÆ   r   rI   rÉ   )'rO   Ú
__future__r   r#   r?   Útypingr   Úurllib.parser   Úfastapir   r   Úlogurur   Ústarlette.responsesr	   r
   r   r   Ústarlette.typesr   r   r   r   Úquber.playgroundr   Úquber.settingsr   Ú	frozensetrj   rl   rC   rA   r˜   r   rS   rƒ   r†   rÈ   rQ   r-   r*   Ú<module>rÔ      s}   ðñ$õL #ã Û Ý Ý "ç $Ý ß VÓ Vß 9Ó 9å $Ý -áÒEÓF€
Ø€Ø)€ØÐ à(€÷&Dñ &D÷R$-ñ $-ôNWô?õBr-   