+
    „WÝjÖ0  ã                  ó¸  a € 0 t $ R t^ RIHt ^ RIt^ RIHtHt ^ RIH	t	 ^ RI
Ht ^ RIHtHtHt ^ RIHt ^ RIHt ^ R	IHtHt ^ R
IHt ^ RIHtHtHtHtHtHtH t H!t!H"t" ^ RI#H$t$ ^ RI%H&t& ^ RI'H(t( ^ RI)H*t+H,t-H.t/H0t0H1t1H2t2 ]Pf                  ! ]44      t5R't6R]7R&   R R lt8R R lt9R R lt:R R lt;RRRR/R R llt<R R  lt=R! R" lt>R# R$ lt?RR/R% R& llt@R# )(aí  Auth-gate middleware for the dashboard.

Engaged when ``app.state.auth_required is True``; a no-op otherwise (loopback
mode is handled by the legacy ``_SESSION_TOKEN`` ``auth_middleware``). Allows
the auth-bootstrap routes and static assets through unauthenticated; for
everything else demands a bearer token or a valid session cookie and attaches
the verified :class:`Session` to ``request.state.session``. HTML routes are
redirected to ``/login``; ``/api/*`` routes get a 401 JSON envelope.
)ÚannotationsN)Ú	AwaitableÚCallable)Úquote)ÚRequest)ÚJSONResponseÚRedirectResponseÚResponse)Úrun_in_threadpool)Úlist_session_providers)Ú
AuditEventÚ	audit_log)ÚProviderError)	Úclear_session_cookiesÚclear_sso_attempt_cookieÚdetect_httpsÚread_session_cookiesÚread_session_providerÚread_sso_attempt_cookieÚset_session_cookiesÚset_session_provider_cookieÚset_sso_attempt_cookie)Úprefix_from_request)ÚPUBLIC_API_PATHS)Úrefresh_session_coalesced)Úaccess_token_max_ageÚ	client_ipÚextract_bearerÚis_safe_next_pathÚscan_session_providersÚunreachable_responseztuple[str, ...]Ú_GATE_PUBLIC_PREFIXESc               ó    € V ^8„  d   QhRRRR/# )é   ÚpathÚstrÚreturnÚbool© )Úformats   "ÚXC:\Users\johnn\AppData\Local\hermes\hermes-agent\hermes_cli\dashboard_auth\middleware.pyÚ__annotate__r+   .   s   € ÷ Hñ H˜#ð H $ñ Hó    c                óª   a € S \         9   ;'       gC    \        ;QJ d#    V 3R l\         4       F  '       g   K   R# 	  R# ! V 3R l\         4       4      # )z¶:data:`PUBLIC_API_PATHS` (shared with the legacy middleware) matched exactly so
``/api/status`` never exposes ``/api/status/extension``; :data:`_GATE_PUBLIC_PREFIXES`
prefix-matched.c              3  ó`   <"  € T F#  pSV8H  ;'       g    SP                  V4      x € K%  	  R # 5i)N)Ú
startswith)Ú.0Úpr$   s   & €r*   Ú	<genexpr>Ú"_path_is_public.<locals>.<genexpr>2   s.   øé € ð +HÙ1F¨Aˆ�‰	×'Ð'�T—_‘_ QÓ'Ô'Ó1Fùs   ƒ.”.TF)r   Úanyr!   )r$   s   fr*   Ú_path_is_publicr5   .   s\   ø€ ð Ô#Ñ#÷ Hð H§s£sô +HÝ1Fó+H§s¤sð H¢sð H sô +HÝ1Fó+Hó (Hð Hr,   c               ó    € V ^8„  d   QhRRRR/# )r#   Úrequestr   r&   r%   r(   )r)   s   "r*   r+   r+   6   s   € ÷ @ñ @˜wð @¨3ñ @r,   c                ó
  € V P                   P                  pV'       d   \        V4      '       g   R# \        V 4      pV'       d	   W!,           pV P                   P                  p\        V'       d   V RV 2RR7      # TRR7      # )aÌ  URL-encoded ``next`` value for the login redirect, or ``""``. Only same-origin paths outside
the auth flow and ``/api`` are kept (query preserved); dropped deep links fall back to the
SPA's ``sessionStorage["hermes.lastLocation"]``.

Behind a reverse proxy at a sub-path (``X-Forwarded-Prefix``, e.g. ``/hermes``), the prefix
is prepended AFTER validation so the post-login redirect lands within the mount
(``/hermes/sessions`` rather than bare ``/sessions``).Ú Ú?©Úsafe)Úurlr$   r   r   Úqueryr   )r7   r$   Úprefixr>   s   &   r*   Ú_safe_next_targetr@   6   sk   € ð �;‰;×Ñ€DßÔ(¨×.Ò.ÙÜ  Ó)€FßØ�}ˆØ�K‰K×Ñ€EÜ§e�D�6˜˜5˜'Ð"¸BÔ?Ð?°¸BÔ?Ð?r,   c               ó$   € V ^8„  d   QhRRRRRR/# )r#   r7   r   Úreasonr%   r&   r	   r(   )r)   s   "r*   r+   r+   H   s!   € ÷ <ñ <˜gð <°#ð <¸(ñ <r,   c          
     ó€  € \        V 4      p\        V 4      pV'       d   V RV 2MV R2pV P                  P                  P	                  R4      '       d^   VR8H  p\        \        P                  WP                  P                  \        V 4      R7       \        RV'       d   RMRR	R
RVRV/RR7      # \        VRR7      # )a  API routes -> 401 JSON with ``login_url``; HTML routes -> 302 -> /login. fetch() follows a
302 opaquely into the cross-origin OAuth dance, so API routes never get redirects; the SPA's
401 handler navigates to ``login_url`` on ``unauthenticated`` / ``session_expired``.z/login?next=ú/loginú/api/Úinvalid_or_expired_session)rB   r$   ÚipÚerrorÚsession_expiredÚunauthenticatedÚdetailÚUnauthorizedrB   Ú	login_urli‘  )Ústatus_codeé.  ©r=   rN   )r@   r   r=   r$   r/   r   r   ÚSESSION_REJECTEDÚ
_client_ipr   r   )r7   rB   Ú
next_paramr?   rM   Úexpireds   &$    r*   Ú_unauth_responserU   H   s½   € ô # 7Ó+€JÜ  Ó)€Fß7A�6�(˜, z lÑ3È&ÈÐQWÐGX€IØ‡{�{×Ñ×"Ñ" 7×+Ò+ØÐ8Ñ8ˆä”*×-Ñ-°fÇ;Á;×CSÑCSÜ Ó(õ	*äØ¯7Ñ'Ð8IÈ8ÐUcØ�v˜{¨Ið7ØDGôIð 	Iô  	°sÔ;Ð;r,   c               ó    € V ^8„  d   QhRRRR/# )r#   r7   r   r&   zResponse | Noner(   )r)   s   "r*   r+   r+   Z   s   € ÷ ñ  ð ¨Oñ r,   c                ót  € V P                   P                  P                  R4      '       d   R# \        V 4      '       d&   \	        V RR7      p\        V\        V 4      R7       V# \        4       p\        V4      ^8w  g   \        V^ ,          RR4      '       d   R# V^ ,          p\        V 4      p\        V 4      pV R\        VP                  R	R
7       2pV'       d   V RV 2p\        VRR7      p\        V\        V 4      VR7       \!        \"        P$                  VP                  R\'        V 4      R7       V# )u°  302 straight to ``/auth/login`` on an unauthenticated HTML load, or ``None``.

Only for a document load (not ``/api/*``) when exactly one interactive OAuth-style provider is
registered (a password provider must render the form) and the one-shot loop-guard cookie is
absent â€” a present marker means the portal had no session last time: clear it and fall back
to ``/login`` rather than ping-pong. Convenience, not a security check.
rE   NÚ	no_cookie©rB   ©r?   Úsupports_passwordFz/auth/login?provider=r9   r;   z&next=rO   rP   )Ú	use_httpsr?   Úauto_sso©ÚproviderrB   rG   )r=   r$   r/   r   rU   r   r   r   ÚlenÚgetattrr@   r   Únamer   r   r   r   r   ÚLOGIN_STARTrR   )r7   ÚrespÚ	providersr_   r?   rS   Ú
auth_logins   &      r*   Ú_auto_sso_responserg   Z   s  € ð ‡{�{×Ñ×"Ñ" 7×+Ò+ÙÜ˜w×'Ò'Ü °Ô<ˆÜ  Ô.AÀ'Ó.JÕKØˆÜ&Ó(€IÜ
ˆ9ƒ~˜Ôœg i°¥lÐ4GÈ×OÒOÙØ˜�|€HÜ  Ó)€FÜ" 7Ó+€JØ�8Ð0´°x·}±}È2Ô1NÐ0OÐP€JßØ"�| 6¨*¨Ð6ˆ
Ü 
¸Ô<€DÜ˜4¬<¸Ó+@ÈÕPÜŒj×$Ñ$¨x¯}©}ÀZÜ˜GÓ$õ&à€Kr,   Úprovider_hintÚauditTc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r#   r7   r   Úaccess_tokenr%   rh   ú
str | Noneri   r'   r(   )r)   s   "r*   r+   r+   x   s2   € ÷ cñ cØðcØ'*ðcØ;EðcØUYñcr,   c               óZ   a aa€ VV 3R lp\        TV3R lS'       d   RMR\        VR7      # )u  Run ``verify_session`` across the provider stack; Session or ``None``. ``audit=False`` is
the native-app bearer path (no cookie, no server-side refresh â€” the desktop rotates via
``/auth/native/refresh``); 503-on-outage semantics come from :func:`scan_session_providers`.c                ó|   <€ S'       d3   \        \        P                  V P                  R \	        S4      R7       R# R# )Úprovider_unreachabler^   N)r   r   ÚSESSION_VERIFY_FAILURErb   rR   )r_   ri   r7   s   &€€r*   Ú_audit_unreachableÚ0_verify_access_token.<locals>._audit_unreachable}   s,   ø€ ßÜ”j×7Ñ7À(Ç-Á-Ø3¼
À7Ó8K÷Mñ r,   c                ó(   <€ V P                  SR 7      # ))rk   )Úverify_session)r1   rk   s   &€r*   Ú<lambda>Ú&_verify_access_token.<locals>.<lambda>ƒ   s   ø€  ×!1Ñ!1¸|Ð!1Ô!Lr,   Úverifyzbearer verify)ÚphaseÚlogÚon_unreachable)r   Ú_log)r7   rk   rh   ri   rq   s   fd$d r*   Ú_verify_access_tokenr|   x   s,   ú€ ö
Mô
 "ØÔLß‰h _¼$ÐOaôcð cr,   c               ó$   € V ^8„  d   QhRRRRRR/# )r#   r7   r   r_   r%   r&   r	   r(   )r)   s   "r*   r+   r+   ‡   s"   € ÷ ñ  Gð Èsð ÐW_ñ r,   c           
   ƒ  ó0  "  € W P                   n        V! V 4      G Rj  x€L
 p\        WBP                  VP                  \        V4      \        V 4      \        V 4      VR7       \        \        P                  W2P                  \        V 4      R7       V#  Lu5i)zÈServe the request under a just-rotated session and write the rotated cookies back. Writing
the ROTATED RT is mandatory: Portal runs reuse detection, so replaying the stale RT would
revoke the session.N)rk   Úrefresh_tokenÚaccess_token_expires_inr\   r?   r_   )r_   Úuser_idrG   )ÚstateÚsessionr   rk   r   Ú_expires_in_secondsr   r   r   r   ÚREFRESH_SUCCESSr�   rR   )r7   Ú	call_nextÚnew_sessionr_   Úresponses   &&&& r*   Ú_serve_refreshedr‰   ‡   s~   é € ð (‡M�MÔÙ˜wÓ'×'€HÜØ×7Ñ7À{×G`ÑG`Ü 3°KÓ @ÌLÐY`ÓLaÜ" 7Ó+°hõ@ô Œj×(Ñ(°8×EXÑEXÜ˜GÓ$õ&à€Oñ (ùs   ‚BžBŸA6Bc               ó    € V ^8„  d   QhRRRR/# )r#   r7   r   r&   r	   r(   )r)   s   "r*   r+   r+   –   s   € ÷ ñ  wð °8ñ r,   c                ó–   € \        \        P                  R\        V 4      R7       \	        V RR7      p\        V\        V 4      R7       V# )z�Refresh failed (or no RT): structured 401/redirect and clear the dead cookies under the
active prefix so the deletion Path matches the set Path.Úno_provider_recognises)rB   rG   rF   rY   rZ   )r   r   rp   rR   rU   r   r   )r7   rˆ   s   & r*   Ú_session_expired_responser�   –   sA   € ô Œj×/Ñ/Ð8PÜ˜GÓ$õ&ä Ð0LÔM€HÜ˜(Ô+>¸wÓ+GÕHØ€Or,   c               ó$   € V ^8„  d   QhRRRRRR/# )r#   r7   r   r†   z(Callable[[Request], Awaitable[Response]]r&   r	   r(   )r)   s   "r*   r+   r+       s$   € ÷ ;ñ ;Øð;Ø!Ið;ØNVñ;r,   c              ƒ  ó.  "  € \        V P                  P                  RR4      '       g   V! V 4      G Rj  x€L
 # \        V P                  RR4      '       gC   \        V P                  RR4      '       g&   \        V P                  P
                  4      '       d   V! V 4      G Rj  x€L
 # \        V 4      pV'       d@    \        WRR7      pTe!   Y0P                  n        T! T 4      G Rj  x€L
 # \        T RR7      # \        V 4      w  rV\        V 4      pV'       g'   V'       g   \        V 4      pVe   V# \        V R	R7      # Rp	V'       d    \        WVR
7      p	V	f@    \!        \"        WVR7      G Rj  x€L
 p
T
f   \%        T 4      # \'        Y.T
O5!  G Rj  x€L
 # W�P                  n        V! V 4      G Rj  x€L
 pV'       g=   V	P(                  '       d+   \+        W¹P(                  \-        V 4      \/        V 4      R7       V#  ELÍ ELa  \         d   p\        \        T4      4      u Rp?# Rp?ii ; i ELK  \         d   p\        \        T4      4      u Rp?# Rp?ii ; i Lï  \         d   p\        \        T4      4      u Rp?# Rp?ii ; i Lù LÛ5i)z6Engaged only when ``app.state.auth_required is True``.Úauth_requiredFNÚtoken_authenticatedÚnative_http_principal)rk   ri   rF   rY   rX   )rk   rh   )r   rh   )r_   r\   r?   )ra   Úappr‚   r5   r=   r$   Ú_extract_bearerr|   r   r    r%   rƒ   rU   r   r   rg   r
   Ú_attempt_refreshr�   r‰   r_   r   r   r   )r7   r†   ÚbearerÚbearer_sessionÚeÚatÚ_rtrh   Úautorƒ   Ú	refreshedrˆ   s   &&          r*   Úgated_auth_middlewarer�       s;  é € ô �7—;‘;×$Ñ$ o°u×=Ò=Ù˜wÓ'×'Ð'ô 	�—‘Ð4°e×<Ò<Ü�w—}‘}Ð&=¸t×DÒDÜ˜wŸ{™{×/Ñ/×0Ò0Ù˜wÓ'×'Ð'ô
 ˜WÓ%€Fßð	0Ü1°'ÐV[Ô\ˆNð Ò%Ø$2�M‰MÔ!Ù" 7Ó+×+Ð+Ü Ð0LÔMÐMä" 7Ó+�G€BÜ)¨'Ó2€Mß—cä! 'Ó*ˆØÒ'ˆtÐZÔ-=¸gÈkÔ-ZÐZð €Gß	ð	0Ü*¨7ÐS`ÔaˆGð ‚ð	0Ü/Ü  'ÈMô[÷ [ˆIð
 ÒÜ,¨WÓ5Ð5Ü% gÐE¸9ÓE×EÐEà#‡M�MÔÙ˜wÓ'×'€Hß˜W×-×-Ð-Ü#Ø×/Ñ/¼<ÈÓ;PÜ& wÓ/õ	1ð €Oòo (ò (øô ô 	0Ü'¬¨A«Ó/Õ/ûð	0úò ,øô ô 	0Ü'¬¨A«Ó/Õ/ûð	0úñ[øäô 	0ä'¬¨A«Ó/Õ/ûð	0úñ
 Fñ (ùs   ‚3JµH¶A.JÂ$HÂ%JÂ=H Ã
JÃ)H4Ã*8JÄ#'JÅH7 ÅJÅI% Å3I#Å4I% Å8JÆJÆJÆ7JÆ8JÇJÇ-JÈJÈH1ÈH,È&H1È'JÈ,H1È1JÈ7I ÉIÉI ÉJÉI É JÉ#I% É%JÉ0J	ÊJÊJÊ	JÊJÊJc               ó    € V ^8„  d   QhRRRR/# )r#   r7   r   rh   rl   r(   )r)   s   "r*   r+   r+   Þ   s   € ÷ ?ñ ?˜gð ?È
ñ ?r,   c          
     óx   a € V'       g   R# V 3R lp\        Y;'       g    RR\        V! R4      V! R4      R7      # )aW  Rotate an expired session via the refresh token; ``(Session, provider_name)`` or ``None``.
Concurrent requests carrying the same stale RT are coalesced (``refresh_singleflight``): a
burst of parallel fetches after AT expiry must not replay a rotated RT into the provider's
reuse detection. ``RefreshExpiredError`` rejects that candidate only (Basic raises it for
foreign opaque tokens too); if none succeeds and any raised ``ProviderError`` it is
re-raised so the caller returns 503 without clearing cookies. Synchronous: the gate runs it
in the threadpool so a slow IdP never blocks the event loop.Nc                ó   <a € V V3R  l# )c                ód   <€ \        \        P                  V P                  S\	        S4      R 7      # )r^   )r   r   ÚREFRESH_FAILURErb   rR   )r_   rB   r7   s   &€€r*   ru   Ú:_attempt_refresh.<locals>._audit_failure.<locals>.<lambda>ê   s#   ø€ ¤	Ü×&Ñ&°·±ÀvÜ˜'Ó"õ!$r,   r(   )rB   r7   s   f€r*   Ú_audit_failureÚ(_attempt_refresh.<locals>._audit_failureé   s   ù€ õ$ð 	$r,   r9   ÚrefreshÚrefresh_expiredro   )rx   ry   Úon_rejectedrz   )r   r{   )r7   r   rh   r¤   s   f$$ r*   r•   r•   Þ   s@   ø€ ÷ Ùõ$ô
 %Ø×*Ð*¨°)ÄÙ"Ð#4Ó5Ù%Ð&<Ó=ô?ð ?r,   )z/auth/loginz/auth/callbackz/auth/native/authorizez/auth/native/tokenz/auth/native/refreshz/auth/password-loginz/auth/logoutrD   z/api/auth/providersz/api/mcp/oauth/callback/z/assets/z/dashboard-plugins/z/favicon.icoz/ds-assets/z/fonts/z/fonts-terminal/)AÚ__conditional_annotations__Ú__doc__Ú
__future__r   ÚloggingÚtypingr   r   Úurllib.parser   Úfastapir   Úfastapi.responsesr   r   r	   Ústarlette.concurrencyr
   Úhermes_cli.dashboard_authr   Úhermes_cli.dashboard_auth.auditr   r   Úhermes_cli.dashboard_auth.baser   Ú!hermes_cli.dashboard_auth.cookiesr   r   r   r   r   r   r   r   r   Ú hermes_cli.dashboard_auth.prefixr   Ú&hermes_cli.dashboard_auth.public_pathsr   Ú.hermes_cli.dashboard_auth.refresh_singleflightr   Ú'hermes_cli.dashboard_auth.request_utilsr   r„   r   rR   r   r”   r   r   r    Ú	getLoggerÚ__name__r{   r!   Ú__annotations__r5   r@   rU   rg   r|   r‰   r�   r�   r•   )r©   s   @r*   Ú<module>r½      sÆ   øðôõ #ã ß &Ý å ß FÑ FÝ 3å <ß AÝ 8÷9÷ 9õ 9õ AÝ CÝ T÷÷ ð
 ×Ò˜Ó"€ð*eÐ �ó eõHõ@õ$<õ$ð<cØHLðcØ\`÷cõõõ;ð|?ÐUY÷ ?ñ ?r,   