Ë
    âß²j#  ã                  ó.  — U d Z ddlmZ ddlZddlmZmZmZ ddlm	Z	m
Z
mZ ddlmZmZmZmZmZmZmZmZmZmZmZ  ej0                  e«      ZdZdZd	Zd
Zdade d<    G d„ de«      Z!dd„Z"dd„Z#dd„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dddœZ,d„ Z-y)ut  NousDashboardAuthProvider â€” Nous Portal OAuth (authorization-code + PKCE).

Implements ``nous-account-service/docs/agent-dashboard-oauth-contract.md``; registers only
when a client_id (``dashboard.oauth.client_id`` / ``HERMES_DASHBOARD_OAUTH_CLIENT_ID``, shape
``agent:{instance_id}``) is configured. Access tokens are RS256 JWTs verified against the
Portal JWKS with ``aud`` = bare client_id. Portal issues a 24h *rotating* refresh token with
reuse detection: the middleware MUST persist ``Session.refresh_token`` back to the cookie on
every refresh or the next refresh replays a rotated token and revokes the whole session.
é    )ÚannotationsN)ÚAnyÚDictÚOptional)Ú
LoginStartÚProviderErrorÚSession)ÚJwtOAuthProviderÚSkipRegistrationÚexchange_tokenÚload_config_sectionÚpkce_login_startÚrefresh_token_fromÚregister_providerÚresolve_env_or_cfgÚsession_from_claimsÚvalidate_redirect_uriÚ
verify_jwtzdashboard-auth-nouszhttps://portal.nousresearch.comzagent_dashboard:accessé   Ú ÚstrÚLAST_SKIP_REASONc                  ór   — e Zd ZdZdZdZdd„Zdd„Zdd„Zdd„Z	dd„Z
d	d
dœ	 	 	 	 	 	 	 	 	 dd„Zdd„Zdd„Zy	)ÚNousDashboardAuthProviderz7Nous Portal OAuth via authorization-code + PKCE (S256).ÚnouszNous Researchc               ó(  — |j                  d«      st        d|›�«      ‚|| _        |t        d«      d  | _        |j                  d«      | _        | j                  › d�| _        | j                  › d�| _        | j                  › d�| _	        d | _
        y )Núagent:z?client_id must match contract shape 'agent:{instance_id}', got ú/z/.well-known/jwks.jsonz/oauth/authorizez/api/oauth/token)Ú
startswithÚ
ValueErrorÚ
_client_idÚlenÚ_agent_instance_idÚrstripÚ_portal_urlÚ	_jwks_urlÚ_authorize_urlÚ
_token_urlÚ_jwks_client)ÚselfÚ	client_idÚ
portal_urls      úI/home/ubuntu/.hermes/hermes-agent/plugins/dashboard_auth/nous/__init__.pyÚ__init__z"NousDashboardAuthProvider.__init__.   s�   € à×#Ñ# HÔ-ÜÐ`ÐajÐ`mÐnÓoÐoØ#ˆŒØ"+¬C°«M¨OÐ"<ˆÔØ%×,Ñ,¨SÓ1ˆÔØ ×,Ñ,Ð-Ð-CÐDˆŒØ!%×!1Ñ!1Ð 2Ð2BÐCˆÔØ!×-Ñ-Ð.Ð.>Ð?ˆŒØ!%ˆÕó    c               óf   — t        |«       t        | j                  | j                  t        |¬«      S )N)r+   ÚscopeÚredirect_uri)r   r   r'   r!   Ú_SCOPE)r*   r2   s     r-   Ústart_loginz%NousDashboardAuthProvider.start_login:   s'   € Ü˜lÔ+Ü × 3Ñ 3¸t¿¹ÔV\ÐkwÔxÐxr/   c                ó   — y ©N© ©r*   Úrefresh_tokens     r-   Úrevoke_sessionz(NousDashboardAuthProvider.revoke_session>   s   € ð r/   c                ó   — | j                   S r6   )r&   )r*   s    r-   Ú	_jwks_uriz#NousDashboardAuthProvider._jwks_uriE   s   € Ø�~‰~Ðr/   c                ó*   — d| j                   |dœd|ifS )Nr9   )Ú
grant_typer+   r9   zx-nous-refresh-token)r!   r8   s     r-   Ú_refresh_requestz*NousDashboardAuthProvider._refresh_requestH   s%   € ð
 +¸¿¹Ð[hÑiØ# ]Ð3ð5ð 	5r/   Nr   )ÚheadersÚprevious_refresh_tokenc          
     ó˜   — t        | j                  |||dddd¬«      \  }}| j                  |t        |«      | j	                  |«      «      S )NÚPortalzPortal token endpointÚaccess_tokenz*Portal token response missing access_token)r@   Úbad_request_excÚidpÚendpointÚ	token_keyÚmissing_msg)r   r(   Ú_sessionr   Ú_claims_for)r*   ÚdatarE   r@   rA   rD   Úpayloads          r-   Ú_grantz NousDashboardAuthProvider._grantP   sS   € ô !/Ø�O‰O˜T¨7ÀOØÐ#:ÀnØDô!FÑˆ�gð
 �}‰}˜\Ô+=¸gÓ+FÈ×HXÑHXÐYeÓHfÓgÐgr/   c                ó‚  — t        || j                  «       dg| j                  | j                  d¬«      }|j	                  d«      }|�*|| j
                  k7  rt        d|›d| j
                  ›�«      ‚|j	                  d«      }|€t        j                  dt        «       |S |t        k7  rt        d	|›d
t        › �«      ‚|S )NÚRS256zaccess token)Ú
algorithmsÚaudienceÚissuerÚlabelÚagent_instance_idz"agent_instance_id mismatch: token=z vs configured=Úoauth_contract_versionzjNous Portal token missing oauth_contract_version claim (contract says it should be %d); proceeding anyway.z#unsupported oauth_contract_version=z, expected )
r   Ú_get_jwks_clientr!   r%   Úgetr#   r   ÚloggerÚwarningÚ_EXPECTED_CONTRACT_VERSION)r*   rD   ÚclaimsÚtoken_instance_idÚcontract_versions        r-   rK   z%NousDashboardAuthProvider._claims_for\   sá   € ÜØ˜$×/Ñ/Ó1¸w¸iØ—_‘_Ø×#Ñ#¨>ô;ˆð #ŸJ™JÐ':Ó;ÐØÐ(Ð->À$×BYÑBYÒ-YÜØ4Ð5FÐ4IÈÐY]×YpÑYpÐXsÐtóvð và!Ÿ:™:Ð&>Ó?ÐØÐ#Ü�N‰NØ|Ü*ô,ð ˆð Ô!;Ò;ÜØ5Ð6FÐ5IÈÔUoÐTpÐqósð sàˆr/   c                ón   — t        | j                  |||t        |j                  d«      xs d«      ¬«      S )NÚorg_idr   )rD   r9   r`   )r   Únamer   rX   )r*   rD   r9   r\   s       r-   rJ   z"NousDashboardAuthProvider._sessionr   s9   € ä"Ø�I‰I�v¨LÈÔ^aÐbh×blÑblÐmuÓbvÒb|Ðz|Ó^}ôð 	r/   )r+   r   r,   r   ÚreturnÚNone)r2   r   rb   r   )r9   r   rb   rc   )rb   r   )r9   r   rb   z/tuple[Dict[str, str], Optional[Dict[str, str]]])
rL   zDict[str, str]rE   ztype[Exception]r@   zOptional[Dict[str, str]]rA   r   rb   r	   )rD   r   rb   úDict[str, Any])rD   r   r9   r   r\   rd   rb   r	   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__ra   Údisplay_namer.   r4   r:   r<   r?   rN   rK   rJ   r7   r/   r-   r   r   (   ss   „ ÙAà€DØ"€Ló
&óyóóó5ð nrØ&(ñ	hØ"ð	hØ8Gð	hØRjð	hà #ð	hð 
ó	hóô,r/   r   c                 ó.   — t        t        t        dd«      S )NÚ	dashboardÚoauth)r   rY   Ú_TAGr7   r/   r-   Ú_load_config_oauth_sectionrn   z   s   € Üœv¤t¨[¸'ÓBÐBr/   c                 óþ   — t        «       } t        d| j                  dd«      «      }t        d| j                  dd«      «      xs t        }|st	        d«      ‚|j                  d«      st	        d|›d	�d
¬«      ‚||dœS )z\Resolve NousDashboardAuthProvider kwargs; the skip reason names BOTH configuration surfaces.Ú HERMES_DASHBOARD_OAUTH_CLIENT_IDr+   r   ÚHERMES_DASHBOARD_PORTAL_URLr,   uy  HERMES_DASHBOARD_OAUTH_CLIENT_ID is not set (and dashboard.oauth.client_id in config.yaml is empty). The Nous Portal provisions this env var (shape 'agent:{instance_id}') when it deploys a Hermes Agent instance â€” set it to your provisioned client id (either as an env var or under dashboard.oauth.client_id in config.yaml), or pass --insecure to skip the OAuth gate entirely.r   z!HERMES_DASHBOARD_OAUTH_CLIENT_ID=zÀ doesn't match the contract shape 'agent:{instance_id}'. The Nous Portal provisions this value at deploy time; check your Fly app's secrets or override with the value from the Portal admin UI.rZ   )Úlevel)r+   r,   )rn   r   rX   Ú_DEFAULT_PORTAL_URLr   r   )Úsectionr+   r,   s      r-   Ú	_settingsru   ~   sš   € ä(Ó*€GÜ"Ð#EÀwÇ{Á{ÐS^Ð`bÓGcÓd€IÜ#Ð$AÀ7Ç;Á;È|Ð]_ÓC`ÓaÒxÔex€JÙÜð#ó$ð 	$ð ×Ñ Ô)ÜØ/°	¨}ð =fð gð ô	ð 	ð
 #°*Ñ=Ð=r/   c                óŒ   — da t        | t        t        t        t
        «      \  }a |�t        j                  d|d   |d   «       yy)zFRegister ``NousDashboardAuthProvider`` when a client_id is configured.r   NzBdashboard-auth-nous: registered provider (client_id=%s, portal=%s)r+   r,   )r   r   rY   rm   r   ru   Úinfo)ÚctxÚkwargss     r-   Úregisterrz   ”   sQ   € ð ÐÜ0°´f¼dÔD]Ô_hÓiÑ€FÐØÐÜ�‰ØPÐRXÐYdÑReÐgmÐnzÑg{õ	}ð r/   )úhermes_cli.dashboard_authÚDashboardAuthProvider)r{   ÚInvalidCodeError)r{   ÚRefreshExpiredError)r{   Úclassify_jwks_lookup_error)r|   r}   r~   r   c                óÌ   — t         j                  | «      }|€t        dt        ›d| ›�«      ‚dd l}ddlm}  |t        | g|¢­Ž  t        |j                  |d   «      |d   «      S )Nzmodule z has no attribute r   )Ú	warn_oncer   )	Ú_PLUGIN_COMPAT_LAZYrX   ÚAttributeErrorre   Ú	importlibÚhermes_cli.plugin_compatr�   ÚgetattrÚimport_module)ra   Útargetr„   r�   s       r-   Ú__getattr__r‰   ²   se   € Ü ×$Ñ$ TÓ*€FØ€~Ü˜w¤x lÐ2DÀTÀHÐMÓNÐNÛÝ2ÙŒh˜Ð&˜vÓ&Ü�9×*Ñ*¨6°!©9Ó5°v¸a±yÓAÐAr/   )rb   Údict)rb   rc   ).rh   Ú
__future__r   ÚloggingÚtypingr   r   r   Úhermes_cli.dashboard_authr   r   r	   Úplugins.dashboard_auth._sharedr
   r   r   r   r   r   r   r   r   r   r   Ú	getLoggerre   rY   rm   rs   r3   r[   r   Ú__annotations__r   rn   ru   rz   Úbase64ÚhashlibÚhttpxÚosÚsecretsÚurllib.parseÚurllibr‚   r‰   r7   r/   r-   ú<module>r™      s»   ðòõ #ã ß &Ñ &ç HÑ H÷÷ ÷ ñ ð 
ˆ×	Ñ	˜8Ó	$€Ø€à7Ð Ø	!€ØÐ àÐ �#Ó ôMÐ 0ô MódCó>ó,}ó Û Û Û 	Û Û ð TØIØOØ"]ñ	Ð óBr/   