Ë
    âß²jL2  ã                  ó   — U d 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mZmZmZmZ ddlmZmZmZmZmZ  ej0                  e«      ZdZdZd	Zd
ZdZdZ dZ!dZ" ejF                  «       jH                  Z%da&de'd<   d!d„Z(d"d„Z) e(d«      Z*d#d„Z+d$d„Z, G d„ dee«      Z-d%d„Z.d&d„Z/d%d„Z0d'd„Z1ddlm2Z2 ddiZ3d „ Z4y)(u^  BasicAuthProvider â€” username/password dashboard auth (no OAuth IDP).

Login is a credential form (``supports_password`` + ``complete_password_login``); cookies,
verify, refresh, ws-tickets and logout are the shared framework. Sessions are stateless
HMAC-signed tokens (no IDP, no database); passwords use stdlib scrypt and login always hashes
even for an unknown username (no username-enumeration timing oracle). Config: ``dashboard.
basic_auth.{username,password_hash|password,secret,session_ttl_seconds}`` or the
``HERMES_DASHBOARD_BASIC_AUTH_*`` env vars (env wins when non-empty; see ``_settings``).
é    )ÚannotationsN)ÚOptional)ÚDashboardAuthProviderÚInvalidCredentialsErrorÚRefreshExpiredErrorÚSession)ÚNonInteractiveMixinÚSkipRegistrationÚload_config_sectionÚregister_providerÚresolve_env_or_cfgzdashboard-auth-basiciÀ¨  i �' i @  é   é   é    é   Ú ÚstrÚLAST_SKIP_REASONc           
     óp  — t        j                  t        «      }t        j                  | j                  d«      |t        t        t        t        d¬«      }t        j                  |«      j                  «       t        j                  |«      j                  «       }}dt        › dt        › dt        › d|› d|› �
S )a  Return a ``scrypt$n$r$p$<salt_b64>$<dk_b64>`` hash string. Public so operators can
    precompute ``password_hash`` for config.yaml (the plaintext then never sits at rest):
    ``python -c "from plugins.dashboard_auth.basic import hash_password; print(hash_password('pw'))"``.úutf-8r   ©ÚsaltÚnÚrÚpÚdklenÚmaxmemzscrypt$ú$)ÚsecretsÚtoken_bytesÚ_SCRYPT_SALT_BYTESÚhashlibÚscryptÚencodeÚ	_SCRYPT_NÚ	_SCRYPT_RÚ	_SCRYPT_PÚ_SCRYPT_DKLENÚbase64Ú	b64encodeÚdecode)Úpasswordr   ÚdkÚsalt_b64Údk_b64s        úJ/home/ubuntu/.hermes/hermes-agent/plugins/dashboard_auth/basic/__init__.pyÚhash_passwordr1   3   s”   € ô ×ÑÔ1Ó2€DÜ	�‰Ø�‰˜Ó  t¬y¼IÌÔZgÐpqô
s€Bä×'Ñ'¨Ó-×4Ñ4Ó6¼×8HÑ8HÈÓ8L×8SÑ8SÓ8Uˆf€HØ”Y�K˜q¤ ¨1¬Y¨K°q¸¸
À!ÀFÀ8ÐLÐLó    c           
     óÀ  — 	 |j                  d«      \  }}}}}}|dk7  ryt        |«      t        |«      t        |«      }
}	}t        j                  |«      }t        j                  |«      }	 t        j                  | j                  d«      |||	|
t        |«      d¬«      }t        j                  ||«      S # t        t
        f$ r Y yw xY w# t        t        f$ r Y yw xY w)z@Constant-time scrypt verify. False on any malformed hash string.r   r#   Fr   r   r   )ÚsplitÚintr)   Ú	b64decodeÚ
ValueErrorÚ	TypeErrorr"   r#   r$   ÚlenÚMemoryErrorÚhmacÚcompare_digest)r,   ÚencodedÚschemeÚn_sÚr_sÚp_sr.   r/   r   r   r   r   ÚexpectedÚactuals                 r0   Ú_verify_passwordrD   >   sÝ   € ðØ29·-±-ÀÓ2DÑ/ˆ��S˜#˜x¨Ø�XÒØÜ�c“(œC ›H¤c¨#£hˆaˆ1ˆÜ×Ñ Ó)ˆÜ×#Ñ# FÓ+ˆðÜ—‘ §¡°Ó 8¸tÀqÈAÐQRÔZ]Ð^fÓZgÐpqÔrˆô ×Ñ˜v xÓ0Ð0øô œ	Ð"ò Ùðûô œÐ$ò Ùðús)   ‚B6  AB6 Á,4C Â6CÃCÃCÃCz'dummy-password-for-constant-time-verifyc                óþ   — t        j                  | d¬«      j                  «       }t        j                  ||t
        j                  «      j                  «       }t        j                  ||z   «      j                  «       S )N)ú,ú:)Ú
separators)ÚjsonÚdumpsr$   r;   Únewr"   Úsha256Údigestr)   Úurlsafe_b64encoder+   )ÚpayloadÚsecretÚrawÚsigs       r0   Ú_signrS   W   sX   € Ü
�*‰*�W¨Ô
4×
;Ñ
;Ó
=€CÜ
�(‰(�6˜3¤§¡Ó
/×
6Ñ
6Ó
8€CÜ×#Ñ# C¨#¡IÓ.×5Ñ5Ó7Ð7r2   c                ó  — 	 t        j                  | j                  «       «      }t        |«      t        k  ry|dt          |t         d }}t        j                  ||t        j                  «      j                  «       }t        j                  ||«      syt        j                  |«      }|j                  d«      |k7  s0|j                  dd«      t        t!        j                   «       «      k  ry|S # t        $ r Y yw xY w)z‹Return the payload if the signature is valid, ``kind`` matches and it
    is unexpired; ``None`` otherwise (including on any decode error).NÚkindÚexpr   )r)   Úurlsafe_b64decoder$   r9   Ú_SIG_LENr;   rK   r"   rL   rM   r<   rI   ÚloadsÚ	ExceptionÚgetr5   Útime)ÚtokenrP   rU   ÚblobrQ   rR   rB   rO   s           r0   Ú_unsignr_   ]   sÒ   € ð
Ü×'Ñ'¨¯©«Ó7ˆÜˆt‹9œÒ ØØ˜
œ(˜Ð# T¬8¨)¨*Ð%5ˆSˆÜ—8‘8˜F C¬¯©Ó8×?Ñ?ÓAˆÜ×"Ñ" 3¨Ô1ØÜ—*‘*˜S“/ˆð ‡{�{�6Ó˜dÒ" g§k¡k°%¸Ó&;¼sÄ4Ç9Á9Ã;Ó?OÒ&OØØ€Nøô	 ò Ùðús   ‚5C2 ¸AC2 ÂC2 Ã2	C>Ã=C>c                  ób   — e Zd ZdZdZdZdZdZdZe	dœdd„Z
dd	„Zdd
„Zdd„Zdd„Zdd„Zdd„Zy)ÚBasicAuthProviderz?Username/password provider with stateless HMAC-signed sessions.ÚbasiczUsername & PasswordTz@BasicAuthProvider is password-only; use complete_password_login.zzBasicAuthProvider is password-only; there is no OAuth redirect flow. The login page POSTs to /auth/password-login instead.)Úttl_secondsc               óÈ   — |st        d«      ‚|st        d«      ‚t        |«      dk  rt        d«      ‚|| _        || _        || _        t        dt        |«      «      | _        y )Nzusername must be non-emptyzpassword_hash must be non-emptyr   z secret must be at least 16 bytesé<   )r7   r9   Ú	_usernameÚ_password_hashÚ_secretÚmaxr5   Ú_ttl)ÚselfÚusernameÚpassword_hashrP   rc   s        r0   Ú__init__zBasicAuthProvider.__init__}   sa   € ÙÜÐ9Ó:Ð:ÙÜÐ>Ó?Ð?Üˆv‹;˜ÒÜÐ?Ó@Ð@Ø!ˆŒØ+ˆÔØˆŒÜ˜œC Ó,Ó-ˆ�	r2   c               ó  — t        j                  |j                  d«      | j                  j                  d«      «      }t	        ||r| j
                  nt        «      }|r|st        d«      ‚| j                  | j                  «      S )Nr   zinvalid username or password)	r;   r<   r$   rf   rD   rg   Ú_DUMMY_HASHr   Ú_mint_session)rk   rl   r,   Úusername_okÚpassword_oks        r0   Úcomplete_password_loginz)BasicAuthProvider.complete_password_login‹   sl   € ô ×)Ñ)¨(¯/©/¸'Ó*BÀDÇNÁN×DYÑDYÐZaÓDbÓcˆÜ& xÉ°×1DÒ1DÔYdÓeˆÙ¡Ü)Ð*HÓIÐIØ×!Ñ! $§.¡.Ó1Ð1r2   c               ó¨   — t        || j                  d«      }|€y | j                  t        |j	                  dd«      «      t        |d   «      |d«      S )NÚaccessÚsubr   rV   )r_   rh   Ú_sessionr   r[   r5   )rk   Úaccess_tokenrO   s      r0   Úverify_sessionz BasicAuthProvider.verify_session—   sK   € Ü˜,¨¯©°hÓ?ˆØˆ?ØØ�}‰}œS §¡¨U°BÓ!7Ó8¼#¸gÀe¹nÓ:MÈ|Ð]_Ó`Ð`r2   c               óÌ   — |st        d«      ‚t        || j                  d«      }|€t        d«      ‚| j                  t	        |j                  d| j                  «      «      «      S )Nz#no refresh token present in sessionÚrefreshz refresh token expired or invalidrw   )r   r_   rh   rq   r   r[   rf   )rk   Úrefresh_tokenrO   s      r0   Úrefresh_sessionz!BasicAuthProvider.refresh_session�   sY   € ÙÜ%Ð&KÓLÐLÜ˜-¨¯©°yÓAˆØˆ?Ü%Ð&HÓIÐIØ×!Ñ!¤# g§k¡k°%¸¿¹Ó&HÓ"IÓJÐJr2   c                ó   — y )N© )rk   r}   s     r0   Úrevoke_sessionz BasicAuthProvider.revoke_session¥   s   € àr2   c                óð   — t        t        j                  «       «      }|| j                  z   }| j                  ||t	        |d|dœ| j
                  «      t	        |d|t        z   dœ| j
                  «      «      S )Nrv   )rw   rU   rV   r|   )r5   r\   rj   rx   rS   rh   Ú_REFRESH_TTL_SECONDS)rk   Úuser_idÚnowrV   s       r0   rq   zBasicAuthProvider._mint_session«   sj   € Ü”$—)‘)“+ÓˆØ�D—I‘I‰oˆØ�}‰}Ø�SÜ˜'¨8¸CÑ@À$Ç,Á,ÓOÜ˜'¨9¸SÔCWÑ=WÑXÐZ^×ZfÑZfÓgóið 	ir2   c           
     ó<   — t        |d|d| j                  |||¬«      S )Nr   )r„   ÚemailÚdisplay_nameÚorg_idÚproviderÚ
expires_atry   r}   )r   Úname)rk   r„   rV   ry   r}   s        r0   rx   zBasicAuthProvider._session³   s*   € ÜØ 2°GÀBÐQU×QZÑQZØ¨À]ôTð 	Tr2   N)
rl   r   rm   r   rP   Úbytesrc   r5   ÚreturnÚNone)rl   r   r,   r   rŽ   r   )ry   r   rŽ   zOptional[Session])r}   r   rŽ   r   )r}   r   rŽ   r�   )r„   r   rŽ   r   )
r„   r   rV   r5   ry   r   r}   r   rŽ   r   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__rŒ   rˆ   Úsupports_passwordÚ_NOT_INTERACTIVEÚ_NO_START_LOGINÚ_DEFAULT_TTL_SECONDSrn   rt   rz   r~   r�   rq   rx   r€   r2   r0   ra   ra   r   sT   „ ÙIà€DØ(€LØÐØYÐð	@ð ð `tõ 
.ó2óaóKóóiôTr2   ra   c                 ó.   — t        t        t        dd«      S )NÚ	dashboardÚ
basic_auth)r   ÚloggerÚ_TAGr€   r2   r0   Ú_load_config_basic_auth_sectionr�   »   s   € Üœv¤t¨[¸,ÓGÐGr2   c                ó\  — t        d| j                  d«      «      }|s*t        j                  d«       t	        j
                  d«      S t        j                  t        j                  fD ]  }	  ||«      }t        |«      dk\  r|c S Œ |j                  d«      S # t        t        f$ r Y ŒBw xY w)uË   Resolve the token-signing secret (base64, hex, or raw text). When unset, generates
    a random per-process secret (sessions then don't survive a restart or span multiple
    workers â€” logged at INFO).Ú"HERMES_DASHBOARD_BASIC_AUTH_SECRETrP   zõdashboard-auth-basic: no 'secret' configured; generating a random per-process signing key. Sessions will not survive a restart or span multiple workers. Set dashboard.basic_auth.secret (or HERMES_DASHBOARD_BASIC_AUTH_SECRET) for stable sessions.r   r   r   )r   r[   r›   Úinfor   r    r)   r6   r�   Úfromhexr9   r7   r8   r$   )Úcfg_sectionrQ   ÚdecoderÚdecodeds       r0   Ú_resolve_secretr¥   ¿   s°   € ô ÐAÀ;Ç?Á?ÐS[ÓC\Ó
]€CÙÜ�‰ðGô	Hô
 ×"Ñ" 2Ó&Ð&Ü×$Ñ$¤e§m¡mÐ4ò ˆð	Ù˜c“lˆGÜ�7‹|˜rÒ!Ø’ð "ðð �:‰:�gÓÐøô œIÐ&ò 	Ùð	ús   Á,BÂB+Â*B+c                 ó   ‡— t        «       Šdˆfd„}  | dd«      } | dd«      } | dd«      } | dd	«      }|st        d
«      ‚|s|st        dd¬«      ‚t        j                  j	                  dd«      j                  «       }|r!t        |«      }t        j                  d«       n"|s t        |«      }t        j                  d«       	 |rt        |«      nt        }||t        ‰«      |dœS # t        $ r	 t        }Y Œ!w xY w)zNResolve BasicAuthProvider kwargs from env/config; raises ``SkipRegistration``.c                ó<   •— t        | ‰j                  |d«      «      S )Nr   )r   r[   )Úenv_nameÚcfg_keyÚsections     €r0   Úsettingz_settings.<locals>.settingÙ   s   ø€ Ü! (¨G¯K©K¸ÀÓ,DÓEÐEr2   Ú$HERMES_DASHBOARD_BASIC_AUTH_USERNAMErl   Ú)HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASHrm   Ú$HERMES_DASHBOARD_BASIC_AUTH_PASSWORDr,   Ú'HERMES_DASHBOARD_BASIC_AUTH_TTL_SECONDSÚsession_ttl_secondsa*  dashboard.basic_auth.username is not set (and HERMES_DASHBOARD_BASIC_AUTH_USERNAME is empty). Set a username and a password (or password_hash) under dashboard.basic_auth in config.yaml to enable username/password dashboard login, or use the OAuth provider, or pass --insecure to skip the auth gate.uË   dashboard.basic_auth.username is set but neither password_hash nor password is configured. Provide one of them (password_hash is preferred â€” compute it with plugins.dashboard_auth.basic.hash_password).Úwarning)Úlevelr   zbdashboard-auth-basic: hashed env-supplied password in-memory (overrides any config password_hash).z§dashboard-auth-basic: hashed plaintext password in-memory. For production, precompute dashboard.basic_auth.password_hash and remove the plaintext password from config.)rl   rm   rP   rc   )r¨   r   r©   r   rŽ   r   )r�   r
   ÚosÚenvironr[   Ústripr1   r›   r    r5   r—   r7   r¥   )r«   rl   rm   Ú	plaintextÚttl_rawÚplaintext_from_envÚttlrª   s          @r0   Ú	_settingsrº   Õ   s  ø€ ä-Ó/€GõFñ Ð=¸zÓJ€HÙÐGÈÓY€MÙÐ>À
ÓK€IÙÐ?ÐAVÓW€GÙÜðZó[ð 	[ñ
 ¡Üð@ð ô	ð 	ô Ÿ™Ÿ™Ð(NÐPRÓS×YÑYÓ[ÐÙÜ%Ð&8Ó9ˆÜ�‰ÐxÕyÙÜ% iÓ0ˆÜ�‰ð=ô	>ð#Ù%Œc�'ŒlÔ+?ˆð !°=ÌOÐ\cÓLdÐuxÑyÐyøô ò #Ü"Šð#ús   ÃC+ Ã+C=Ã<C=c                ó„   — da t        | t        t        t        t
        «      \  }a |�t        j                  d|d   «       yy)z‰Register ``BasicAuthProvider`` when username + (password or
    password_hash) are configured; a no-op for OAuth / ``--insecure`` setups.r   Nz@dashboard-auth-basic: registered password provider (username=%s)rl   )r   r   r›   rœ   ra   rº   r    )ÚctxÚkwargss     r0   Úregisterr¾      sE   € ð ÐÜ0°´f¼dÔDUÔW`ÓaÑ€FÐØÐÜ�‰ÐVÐX^Ð_iÑXjÕkð r2   )ÚAnyÚ
LoginStart)zhermes_cli.dashboard_authrÀ   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_LAZYr[   ÚAttributeErrorr�   Ú	importlibÚhermes_cli.plugin_compatrÂ   ÚgetattrÚimport_module)rŒ   ÚtargetrÅ   rÂ   s       r0   Ú__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ÐAr2   )r,   r   rŽ   r   )r,   r   r=   r   rŽ   Úbool)rO   ÚdictrP   r�   rŽ   r   )r]   r   rP   r�   rU   r   rŽ   zOptional[dict])rŽ   rÌ   )r¢   rÌ   rŽ   r�   )rŽ   r�   )5r“   Ú
__future__r   r)   r"   r;   rI   Úloggingr³   r   r\   Útypingr   Úhermes_cli.dashboard_authr   r   r   r   Úplugins.dashboard_auth._sharedr	   r
   r   r   r   Ú	getLoggerr�   r›   rœ   r—   rƒ   r%   r&   r'   r(   r!   rL   Údigest_sizerX   r   Ú__annotations__r1   rD   rp   rS   r_   ra   r�   r¥   rº   r¾   r¿   rÃ   rÊ   r€   r2   r0   ú<module>rÕ      s
  ðòõ #ã Û Û Û Û Û 	Û Û Ý ç rÓ r÷gõ gð 
ˆ×	Ñ	˜8Ó	$€Ø€ð $Ð Ø(Ð ð €	Ø€	Ø€	Ø€ØÐ ð ˆ7�>‰>Ó×'Ñ'€àÐ �#Ó ó
Mó1ñ( ÐEÓF€ó
8óô*DTÐ+Ð-Bô DTóRHóó,(zóVlõ ð Ð=ðÐ ó
Br2   