feat: Postfaecher einrichten und Zahlungs-Mails abholen (Etappe B, Teil 2)
- Verschluesselung fuer Zugangsdaten: Ein IMAP-Passwort laesst sich nicht hashen, Certain muss es dem Mailserver vorlegen. Es liegt deshalb mit Fernet verschluesselt in der Datenbank, der Schluessel wird per HKDF aus session_secret abgeleitet und nirgends gespeichert. Wird das Geheimnis getauscht, meldet Certain das verstaendlich statt mit einem Stacktrace. - IMAP-Abruf ueber imaplib/email (Standardbibliothek). Ordnerliste, Verbindungstest mit klarer Fehlermeldung, Absender-Zuordnung auf Konten, danach als gelesen markieren oder verschieben. - Wiedererkennung ueber die Message-ID statt der UID – die vergibt der Server beim Verschieben neu. - Nichts wird stillschweigend gebucht: Eine Mail wird zum Eingangs-Eintrag. Fehlt das Zielkonto oder ist ein Feld unsicher, gilt sie als unklar. - Hintergrund-Thread beim Start, nach dem Muster des EZB-Kursabrufs: ein langsames Postfach darf den Dienst nie blockieren. Getestet gegen einen IMAP-Doppelgaenger, damit die Suite ohne Netz laeuft. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -540,3 +540,55 @@ class QuickBookingOut(BaseModel):
|
||||
ok: bool = True
|
||||
message: str
|
||||
item: InboxItemOut
|
||||
|
||||
|
||||
# --- Postfächer für den Mail-Abruf -------------------------------------------
|
||||
|
||||
|
||||
class MailSourceIn(BaseModel):
|
||||
name: str = Field(default="", max_length=80)
|
||||
host: str = Field(min_length=1, max_length=255)
|
||||
port: int = 993
|
||||
use_ssl: bool = True
|
||||
username: str = Field(min_length=1, max_length=255)
|
||||
# Beim Ändern weglassen, um das gespeicherte Passwort zu behalten.
|
||||
password: str | None = None
|
||||
folder: str = "INBOX"
|
||||
done_folder: str | None = None
|
||||
mark_seen: bool = True
|
||||
is_active: bool = True
|
||||
poll_minutes: int = Field(default=15, ge=5, le=1440)
|
||||
|
||||
|
||||
class MailSourceOut(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
host: str
|
||||
port: int
|
||||
use_ssl: bool
|
||||
username: str
|
||||
folder: str
|
||||
done_folder: str | None
|
||||
mark_seen: bool
|
||||
is_active: bool
|
||||
poll_minutes: int
|
||||
last_poll_at: datetime | None
|
||||
last_error: str | None
|
||||
# Das Passwort verlässt den Server nie – nur die Tatsache, dass eines da ist.
|
||||
password_set: bool = True
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class MailSenderIn(BaseModel):
|
||||
name: str = Field(default="", max_length=80)
|
||||
from_contains: str | None = Field(default=None, max_length=255)
|
||||
subject_contains: str | None = Field(default=None, max_length=255)
|
||||
account_id: int | None = None
|
||||
|
||||
|
||||
class MailSenderOut(MailSenderIn):
|
||||
id: int
|
||||
source_id: int
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
Reference in New Issue
Block a user