mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-01 19:18:35 -04:00
fix(reminders): support OAuth SMTP accounts (#5649)
This commit is contained in:
@@ -301,7 +301,7 @@ async def dispatch_reminder(
|
|||||||
email_error = ""
|
email_error = ""
|
||||||
if channel == "email":
|
if channel == "email":
|
||||||
try:
|
try:
|
||||||
from routes.email_routes import _get_email_config
|
from routes.email_routes import _get_email_config, _smtp_ready
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from datetime import datetime as _dt
|
from datetime import datetime as _dt
|
||||||
@@ -311,7 +311,7 @@ async def dispatch_reminder(
|
|||||||
# account when no explicit choice is saved.
|
# account when no explicit choice is saved.
|
||||||
_acc_id = (settings.get("reminder_email_account_id") or "").strip() or None
|
_acc_id = (settings.get("reminder_email_account_id") or "").strip() or None
|
||||||
cfg = _get_email_config(account_id=_acc_id, owner=owner or "")
|
cfg = _get_email_config(account_id=_acc_id, owner=owner or "")
|
||||||
if not (cfg.get("smtp_host") and cfg.get("smtp_user") and cfg.get("smtp_password")):
|
if not _smtp_ready(cfg):
|
||||||
try:
|
try:
|
||||||
from core.database import SessionLocal as _SL, EmailAccount as _EA
|
from core.database import SessionLocal as _SL, EmailAccount as _EA
|
||||||
from sqlalchemy import and_, or_
|
from sqlalchemy import and_, or_
|
||||||
@@ -324,7 +324,7 @@ async def dispatch_reminder(
|
|||||||
q = q.filter(or_(_EA.owner == owner, and_(unowned, same_mailbox)))
|
q = q.filter(or_(_EA.owner == owner, and_(unowned, same_mailbox)))
|
||||||
for row in q.order_by(_EA.is_default.desc(), _EA.created_at.asc()).all():
|
for row in q.order_by(_EA.is_default.desc(), _EA.created_at.asc()).all():
|
||||||
trial = _get_email_config(account_id=row.id, owner=owner or "")
|
trial = _get_email_config(account_id=row.id, owner=owner or "")
|
||||||
if trial.get("smtp_host") and trial.get("smtp_user") and trial.get("smtp_password"):
|
if _smtp_ready(trial):
|
||||||
cfg = trial
|
cfg = trial
|
||||||
break
|
break
|
||||||
finally:
|
finally:
|
||||||
@@ -347,8 +347,8 @@ async def dispatch_reminder(
|
|||||||
missing.append("SMTP host")
|
missing.append("SMTP host")
|
||||||
if not cfg.get("smtp_user"):
|
if not cfg.get("smtp_user"):
|
||||||
missing.append("SMTP user")
|
missing.append("SMTP user")
|
||||||
if not cfg.get("smtp_password"):
|
if not (cfg.get("smtp_password") or cfg.get("oauth_provider")):
|
||||||
missing.append("SMTP password")
|
missing.append("SMTP credentials")
|
||||||
if not from_addr:
|
if not from_addr:
|
||||||
missing.append("from address")
|
missing.append("from address")
|
||||||
if not recipient:
|
if not recipient:
|
||||||
|
|||||||
@@ -2412,11 +2412,16 @@ async function initReminderSettings() {
|
|||||||
// what the Integrations panel manages. Treat the email channel as
|
// what the Integrations panel manages. Treat the email channel as
|
||||||
// configured if there's at least one account with SMTP set.
|
// configured if there's at least one account with SMTP set.
|
||||||
let emailAccounts = [];
|
let emailAccounts = [];
|
||||||
|
const smtpAccountReady = (account) => !!(
|
||||||
|
account.smtp_host
|
||||||
|
&& account.smtp_user
|
||||||
|
&& (account.has_smtp_password || account.oauth_provider === 'google')
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/email/accounts', { credentials: 'same-origin' });
|
const res = await fetch('/api/email/accounts', { credentials: 'same-origin' });
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const d = await res.json();
|
const d = await res.json();
|
||||||
emailAccounts = (d.accounts || []).filter(a => a.smtp_host && a.smtp_user && a.has_smtp_password);
|
emailAccounts = (d.accounts || []).filter(smtpAccountReady);
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
let smtpConfigured = emailAccounts.length > 0;
|
let smtpConfigured = emailAccounts.length > 0;
|
||||||
@@ -2520,7 +2525,7 @@ async function initReminderSettings() {
|
|||||||
const res = await fetch('/api/email/accounts', { credentials: 'same-origin' });
|
const res = await fetch('/api/email/accounts', { credentials: 'same-origin' });
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const d = await res.json();
|
const d = await res.json();
|
||||||
emailAccounts = (d.accounts || []).filter(a => a.smtp_host && a.smtp_user && a.has_smtp_password);
|
emailAccounts = (d.accounts || []).filter(smtpAccountReady);
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
smtpConfigured = emailAccounts.length > 0;
|
smtpConfigured = emailAccounts.length > 0;
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"""Regression coverage for passwordless Google OAuth reminder senders."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from routes.note_routes import dispatch_reminder
|
||||||
|
|
||||||
|
|
||||||
|
_REPO = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispatch_reminder_sends_with_google_oauth_without_smtp_password():
|
||||||
|
cfg = {
|
||||||
|
"account_name": "Workspace",
|
||||||
|
"smtp_host": "smtp.gmail.com",
|
||||||
|
"smtp_port": 587,
|
||||||
|
"smtp_security": "starttls",
|
||||||
|
"smtp_user": "alice@example.edu",
|
||||||
|
"smtp_password": "",
|
||||||
|
"from_address": "alice@example.edu",
|
||||||
|
"oauth_provider": "google",
|
||||||
|
}
|
||||||
|
sent = []
|
||||||
|
|
||||||
|
def fake_send(actual_cfg, sender, recipients, message):
|
||||||
|
sent.append((actual_cfg, sender, recipients, message))
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("src.settings.load_settings", return_value={}),
|
||||||
|
patch("routes.email_routes._get_email_config", return_value=cfg),
|
||||||
|
patch("routes.email_helpers._send_smtp_message", side_effect=fake_send),
|
||||||
|
patch("core.database.SessionLocal", side_effect=AssertionError("fallback lookup is not needed")),
|
||||||
|
):
|
||||||
|
result = asyncio.run(dispatch_reminder(
|
||||||
|
"Reminder: Submit report",
|
||||||
|
"The report is due today.",
|
||||||
|
note_id="",
|
||||||
|
owner="alice@example.edu",
|
||||||
|
queue_browser=False,
|
||||||
|
settings_override={
|
||||||
|
"reminder_channel": "email",
|
||||||
|
"reminder_llm_synthesis": False,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
|
assert result["email_sent"] is True
|
||||||
|
assert result["email_error"] == ""
|
||||||
|
assert len(sent) == 1
|
||||||
|
actual_cfg, sender, recipients, message = sent[0]
|
||||||
|
assert actual_cfg is cfg
|
||||||
|
assert sender == "alice@example.edu"
|
||||||
|
assert recipients == ["alice@example.edu"]
|
||||||
|
assert "Subject: Reminder (Odysseus): Submit report" in message
|
||||||
|
|
||||||
|
|
||||||
|
def test_reminder_settings_offer_oauth_smtp_accounts():
|
||||||
|
source = (_REPO / "static" / "js" / "settings.js").read_text(encoding="utf-8")
|
||||||
|
helper = source[source.index("const smtpAccountReady"):source.index("const smtpAccountReady") + 260]
|
||||||
|
|
||||||
|
assert "account.has_smtp_password || account.oauth_provider === 'google'" in helper
|
||||||
|
assert source.count(".filter(smtpAccountReady)") == 2
|
||||||
Reference in New Issue
Block a user