8b1451cdda
* feat(runtime): add application-scoped async runtime * refactor(cli): use owned runtime for session stats * refactor(onboard): use the owned async runtime * docs(runtime): record async bridge ownership * refactor(middleware): keep sync fallback synchronous * refactor(mcp): load tools on an owned runtime * refactor(cli): share owned runtime across entry points * refactor(channels): make inbound sync bridge explicit * refactor(stream): run Rich streaming on owned runtime * chore(runtime): remove nest-asyncio dependency * refactor(asyncio): require active loops in async code * docs(runtime): document final event loop ownership * fix(stream): cancel stalled owned streams * fix(cli): recover cleanly from stream cancellation * fix(runtime): drain executor work before shutdown * fix(runtime): terminate cancelled shell process trees * fix(models): let fallback bypass selector failures * fix(cli): reset interrupt handling between turns * docs: rm implementation spec * fix(serve): cancel active turns during shutdown * fix(runtime): protect settlement from waiter cancellation * fix(backends): reject empty shell commands * fix(runtime): terminate descendants after shell exit * fix(mcp): keep standalone discovery off channel loop * fix(cli): own and settle interactive prompt cancellation * fix(serve): keep channel sends off runtime loop * fix(stream): scope cancel context to iterator steps * refactor(serve): require the owned async runtime * fix(channels): keep interactive sends off runtime loop * fix(selector): surface fallback without log spam * test(runtime): normalize Windows shell marker * fix(cli): serialize interactive session turns * fix(shell): bound output drain after termination * fix(ui): do not retry owned runtime failures * fix(shell): allow signal-safe registry reentry * fix(shell): avoid terminating reused process ids * fix(channels): preserve streaming send order * fix(cli): report runtime shutdown timeouts cleanly * fix(mcp): guide async callers to async loader * docs(runtime): clarify reserved async bridge APIs * fix(runtime): bound code interpreter cleanup * test(shell): use active Python for drain regression --------- Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
93 lines
2.4 KiB
Python
93 lines
2.4 KiB
Python
"""Email credential validation."""
|
|
|
|
import imaplib
|
|
import logging
|
|
import smtplib
|
|
import ssl
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def validate_email_imap(
|
|
host: str,
|
|
port: int,
|
|
username: str,
|
|
password: str,
|
|
use_ssl: bool = True,
|
|
) -> tuple[bool, str]:
|
|
"""Validate IMAP credentials.
|
|
|
|
Returns:
|
|
Tuple of (is_valid, message).
|
|
"""
|
|
if not host or not username or not password:
|
|
return False, "host, username, and password are required"
|
|
|
|
import asyncio
|
|
|
|
loop = asyncio.get_running_loop()
|
|
|
|
def _check():
|
|
try:
|
|
if use_ssl:
|
|
ctx = ssl.create_default_context()
|
|
conn = imaplib.IMAP4_SSL(host, port, ssl_context=ctx)
|
|
else:
|
|
conn = imaplib.IMAP4(host, port)
|
|
conn.login(username, password)
|
|
conn.logout()
|
|
return True, "IMAP credentials valid"
|
|
except imaplib.IMAP4.error as e:
|
|
return False, f"IMAP auth failed: {e}"
|
|
except Exception as e:
|
|
return False, f"IMAP error: {e}"
|
|
|
|
return await loop.run_in_executor(None, _check)
|
|
|
|
|
|
async def validate_email_smtp(
|
|
host: str,
|
|
port: int,
|
|
username: str,
|
|
password: str,
|
|
use_tls: bool = True,
|
|
) -> tuple[bool, str]:
|
|
"""Validate SMTP credentials.
|
|
|
|
Returns:
|
|
Tuple of (is_valid, message).
|
|
"""
|
|
if not host or not username or not password:
|
|
return False, "host, username, and password are required"
|
|
|
|
import asyncio
|
|
|
|
loop = asyncio.get_running_loop()
|
|
|
|
def _check():
|
|
server = None
|
|
try:
|
|
if use_tls:
|
|
server = smtplib.SMTP(host, port, timeout=10)
|
|
server.starttls()
|
|
else:
|
|
ctx = ssl.create_default_context()
|
|
server = smtplib.SMTP_SSL(host, port, context=ctx, timeout=10)
|
|
server.login(username, password)
|
|
return True, "SMTP credentials valid"
|
|
except smtplib.SMTPAuthenticationError:
|
|
return False, "SMTP auth failed"
|
|
except Exception as e:
|
|
return False, f"SMTP error: {e}"
|
|
finally:
|
|
if server is not None:
|
|
try:
|
|
server.quit()
|
|
except Exception:
|
|
try:
|
|
server.close()
|
|
except Exception:
|
|
pass
|
|
|
|
return await loop.run_in_executor(None, _check)
|